package sale.multiwindow;

import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import javax.swing.*;
//import javax.swing.JInternalFrame;

import java.awt.Dimension;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Rectangle;

import java.util.*;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeEvent;

import java.io.*;

import sale.*;

/**
  * A MultiWindow is a {@link JFrame} capable of displaying contents in multiple frames.
  *
  * <p>Those frames can be displayed either as tabbed panels or as multiple internal frames on a
  * {@link javax.swing.JDesktopPane}. The view mode can be chosen by client programs by calling the {@link #setViewMode}
  * method or by the user using the &quot;Window&quot; menu.</p>
  *
  * <p>To add a frame, you have to get a {@link MultiWindowHandle} ({@link #getNewHandle}) and set its
  * {@link FormSheet} and {@link MenuSheet}. The display will be updated automatically.</p>
  *
  * @author Sven Matznick
  * @author Stephan Gambke
  * @version 2.0 08/06/1999
  * @since v2.0
  */
public class MultiWindow extends JFrame implements ChangeListener, PropertyChangeListener {

// ATTENTION!!!! WHEN DEFINING NEW ATTRIBUTES MAKE SURE THEY ARE STORED IN save() AND READ IN
// load() !!!

  /**
    * Next valid handle ID.
    *
    * @serial
    */
  private int m_nHandleCount = 0;

  /**
    * Current view mode.
    *
    * @serial
    */
  private int m_nViewMode;

  /**
    * Current frame arrangement.
    *
    * @serial
    */
  private int m_nArrangement = OVERLAPPED;

  /**
    * The map of handles. The keys are the handles' IDs.
    *
    * @serial
    */
  private Map m_mpmwhHandles = new HashMap();

  /**
    * The list of currently visible handles.
    *
    * @serial
    */
  private List m_lmwhVisibles = new LinkedList();

  /**
    * The current global MenuSheet.
    *
    * @serial
    */
  private MenuSheet m_msCurrentMenuSheet;

  /**
    * The ID of the currently selected frame.
    *
    * @serial
    */
  private Integer m_iSelectedFrame;

  /**
    * The MenuSheetItem used to toggle the view mode of the MultiWindow.
    *
    * @serial
    */
  private MenuSheetItem m_msiToggleViewMode = null;

  /**
    * The MultiWindowActions registered with this MultiWindow.
    *
    * @serial
    */
  private Vector m_vcActions = new Vector();

// ATTENTION!!!! WHEN DEFINING NEW ATTRIBUTES MAKE SURE THEY ARE STORED IN save() AND READ IN
// load() !!!

  /**
    * Special {@link sale.Action Actions} are necessary for
    * {@link MultiWindow MultiWindow}-{@link sale.MenuSheet MenuSheets} in order for the serialization to work
    * properly. Whenever creating Actions that refer a MultiWindow directly, always use this class instead of
    * simply deriving Action and let it have a direct reference to the MultiWindow.
    *
    * <p><strong>JDK1.3 Note</strong> Apparently all JDK1.3 compilers have abandoned the practice of not
    * creating implicit references to enclosing instances if they are not necessary. Therefore, implementing
    * the action as an anonymous inner class extending MultiWindowAction may still lead to 
    * NotSerializableExceptions being thrown. In such cases attempt to implement the actions as static
    * top-level classes derived from MultiWindowAction.</p>
    */
  public static abstract class MultiWindowAction implements sale.Action {

    private void writeObject (ObjectOutputStream oos) throws IOException {
      util.Debug.print ("Writing sale.multiwindow.MultiWindow$MultiWindowAction.", -1);

      oos.defaultWriteObject();

      util.Debug.print ("Done writing sale.multiwindow.MultiWindow$MultiWindowAction.", -1);
    }

    /**
      * The MultiWindow referenced by this Action. {@link #doAction} must always use this reference instead of
      * a reference created via the inner class mechanism.
      */
    protected transient MultiWindow m_mwReference;

    /**
      * Create a new MultiWindowAction referencing the given MultiWindow.
      */
    public MultiWindowAction (MultiWindow mwReference) {
      super();

      m_mwReference = mwReference;

      m_mwReference.registerAction (this);
    }
  }

  /**
    * The MultiWindow is not serializable. Instead, use the {@link #save} method.
    *
    * @override Never
    */
  private void writeObject (ObjectOutputStream oos) throws IOException {
    throw new java.io.NotSerializableException ("sale.multiwindow.MultiWindow");
  }

  /**
    * The MultiWindow is not serializable. Instead, use the {@link #load} method.
    *
    * @override Never
    */
  private void readObject (ObjectInputStream ois) throws IOException, ClassNotFoundException {
    throw new java.io.NotSerializableException ("sale.multiwindow.MultiWindow");
  }

  /**
    * Create a new MultiWindow without title and with the view mode initially set to
    * MULTIFRAME.
    */
  public MultiWindow() {
    this ("", MULTIFRAME);
  }

  /**
    * Create a new MultiWindow with the given title and with the view mode initially set to
    * MULTIFRAME.
    *
    * @param sTitle the title
    */
  public MultiWindow(String sTitle) {
    this (sTitle, MULTIFRAME);
  }

  /**
    * Create a new MultiWindow with the given title and view mode.
    *
    * @param sTitle the title
    * @param nViewMode the view mode
    */
  public MultiWindow(String sTitle, int nViewMode) {
    super (sTitle);

    m_nViewMode = NONE;
    if (nViewMode == MULTIFRAME) {
      transformToPane();
    }
    else {
      transformToTab();
    }

    setDefaultMenuSheet();

    pack();
  }

  /**
    * Internal communications method: Register a MultiWindowAction so that it can be properly serialized.
    *
    * @param mwa the action
    *
    * @override Never
    */
  void registerAction (MultiWindowAction mwa) {
    m_vcActions.add (mwa);
  }

  /**
    * Save the current state of the MultiWindow.
    *
    * @param oos the stream into which to save the state.
    *
    * @override Sometimes Override this method whenever you added attributes.
    *
    * @exception IOException if an error occurs while saving.
    */
  public void save (ObjectOutputStream oos) throws IOException {
    oos.writeInt (m_nHandleCount);
    oos.writeInt (m_nViewMode);
    oos.writeInt (m_nArrangement);
    oos.writeObject (m_mpmwhHandles);
    oos.writeObject (m_lmwhVisibles);
    oos.writeObject (m_msCurrentMenuSheet);
    oos.writeObject (m_iSelectedFrame);
    oos.writeObject (m_msiToggleViewMode);
    oos.writeObject (m_vcActions);

    oos.writeObject (getTitle());
    oos.writeObject (getBounds());
    oos.writeBoolean (isVisible());
  }

  /**
    * Load the state of this MultiWindow from a stream.
    *
    * @override Sometimes Override this method whenever you added attributes.
    *
    * @param ois the input stream to restore the state from.
    *
    * @exception IOException if an error occurs while reading.
    * @exception ClassNotFoundException if an error occurs while reading.
    */
  public void load (ObjectInputStream ois) throws IOException, ClassNotFoundException {
    m_nHandleCount = ois.readInt();
    final int nViewMode = ois.readInt();
    m_nArrangement = ois.readInt();
    m_mpmwhHandles = (Map) ois.readObject();
    m_lmwhVisibles = (List) ois.readObject();
    m_msCurrentMenuSheet = (MenuSheet) ois.readObject();
    m_iSelectedFrame = (Integer) ois.readObject();
    m_msiToggleViewMode = (MenuSheetItem) ois.readObject();
    m_vcActions = (Vector) ois.readObject();

    setTitle ((String) ois.readObject());
    try {
      for (Iterator i = m_vcActions.iterator(); i.hasNext();) {
        ((MultiWindowAction) i.next()).m_mwReference = this;
      }

      for (Iterator i = m_mpmwhHandles.values().iterator(); i.hasNext();) {
        MultiWindowHandle mwh = (MultiWindowHandle) i.next();
        mwh.setOwner (this);
        mwh.getFrame().addPropertyChangeListener (this);
      }
    }
    catch (Throwable t) {}

    final Rectangle rcBounds = (Rectangle) ois.readObject();
    final boolean fVisible = ois.readBoolean();

    ois.registerValidation (new ObjectInputValidation() {
      public void validateObject()
        throws InvalidObjectException {
        try {
          m_nViewMode = NONE;
          setViewMode (nViewMode);
        }
        catch (Throwable t) {
          throw new InvalidObjectException (t.toString());
        }

        setBounds (rcBounds);
        setVisible (fVisible);
        validate();
      }
    },
                              OIV.MULTIWINDOW_PRIO);
  }

  /**
    * Return the MultiWindow management MenuSheet for this MultiWindow.
    *
    * <p>The MultiWindow management MenuSheet contains the following items for managing the look of the
    * MultiWindow:</p>
    *
    * <table>
    *   <tr>
    *     <td>Item text</td>
    *     <td>Item tag</td>
    *     <td>Item action</td>
    *     <td>Comments</td>
    *   </tr>
    *   <tr>
    *     <td>Toggle view (MULTIFRAME/TABBED)</td>
    *     <td>{@link #TOGGLE_VIEW_TAG}</td>
    *     <td>Toggle between multiframe and tabbed view.</td>
    *     <td>The text in the parantheses will give the type of view, that will be set when the user selects
    *         this item. You can define the three parts of this item's text in the following static attributes:
    *         {@link #s_sToggleViewPrefix} and {@link #s_asViewModeTexts}.</td>
    *   </tr>
    *   <tr>
    *     <td><i>Separator</i></td>
    *     <td>{@link #SEPARATOR_TAG}</td>
    *     <td>None.</td>
    *     <td></td>
    *   </tr>
    *   <tr>
    *     <td>Cascade</td>
    *     <td>{@link #CASCADE_TAG}</td>
    *     <td>Cascade the internal frames. When in tabbed view mode, first change view mode.</td>
    *     <td></td>
    *   </tr>
    *   <tr>
    *     <td>Tile horizontally</td>
    *     <td>{@link #TILE_HORIZ_TAG}</td>
    *     <td>Tile internal frames horizontally. When in tabbed view mode, first change view mode.</td>
    *     <td></td>
    *   </tr>
    *   <tr>
    *     <td>Tile vertically</td>
    *     <td>{@link #TILE_VERT_TAG}</td>
    *     <td>Tile internal frames vertically. When in tabbed view mode, first change view mode.</td>
    *     <td></td>
    *   </tr>
    * </table>
    *
    * <p>You will find this MenuSheet in your {@link sale.Shop Shop's} MenuSheet, with a caption of
    * &quot;MultiWindow&quot; and a tag of {@link #MULTIWINDOW_MENU_TAG}. Please note that the MultiWindow
    * will try to merge in the current display's MenuSheet before a MenuSheet with exactly this tag when in
    * the tabbed view mode. So, if you remove the MultiWindow menu, make sure there will be a
    * {@link sale.MenuSheetObject} in the Shop's menu with that tag, or the MenuSheets will be merged to the
    * end of the Shop's MenuSheet.</p>
    *
    * <p>This method is used by {@link #setDefaultMenuSheet}.</p>
    *
    * @override Sometimes
    *
    * @return a MenuSheet representing the default MultiWindow menu
    */
  public MenuSheet getMultiWindowMenuSheet() {
    MenuSheet msMWMenu = new MenuSheet("MultiWindow", MULTIWINDOW_MENU_TAG);

    // item to toggle view mode
    m_msiToggleViewMode = new MenuSheetItem (getToggleViewMenuText(),
                                               TOGGLE_VIEW_TAG,
                                               new ToggleViewModeAction (this));
/*                                             new MultiWindowAction (this) {
      public void doAction (SaleProcess p, SalesPoint sp) {
        m_mwReference.setViewMode ((m_mwReference.getViewMode() == MULTIFRAME)?(TABBED):(MULTIFRAME));
      }
    });*/
    msMWMenu.add (m_msiToggleViewMode);

    msMWMenu.add (new MenuSheetSeparator (SEPARATOR_TAG));

    // item to cascade the internal frames
    msMWMenu.add (new MenuSheetItem ("Cascade", CASCADE_TAG, new CascadeAction (this)));
/*    new MultiWindowAction (this) {
      public void doAction (SaleProcess p, SalesPoint sp) {
        if (m_mwReference.getViewMode() == TABBED) {
          m_mwReference.setViewMode (MULTIFRAME);
        }
        m_mwReference.arrangeFrames(OVERLAPPED);
      }
    }));*/

    // item to tile the internal frames horizontally
    msMWMenu.add (new MenuSheetItem ("Tile horizontally", TILE_HORIZ_TAG, new TileHorizontallyAction (this)));
/*    new MultiWindowAction (this) {
      public void doAction (SaleProcess p, SalesPoint sp) {
        if (m_mwReference.getViewMode() == TABBED) {
          m_mwReference.setViewMode (MULTIFRAME);
        }
        m_mwReference.arrangeFrames(TILED_HORIZONTALLY);
      }
    }));*/

    // item to tile the internal frames vertically
    msMWMenu.add (new MenuSheetItem ("Tile vertically", TILE_VERT_TAG, new TileVerticallyAction (this)));
/*    new MultiWindowAction (this) {
      public void doAction (SaleProcess p, SalesPoint sp) {
        if (m_mwReference.getViewMode() == TABBED) {
          m_mwReference.setViewMode (MULTIFRAME);
        }
        m_mwReference.arrangeFrames(TILED_VERTICALLY);
      }
    }));*/

    return msMWMenu;
  }

  /**
    * Helper method building the toggle view mode menu item's text.
    *
    * @override Never
    */
  private String getToggleViewMenuText() {
    return (s_sToggleViewPrefix +
            ((m_nViewMode == MULTIFRAME)?
             (s_asViewModeTexts[TABBED]):
             (s_asViewModeTexts[MULTIFRAME])));
  }

  /**
    * Sets the default MenuSheet.
    *
    * <p>This method uses {@link #getMultiWindowMenuSheet} to get the MenuSheet to be set.</p>
    *
    * @override Never
    */
  public void setDefaultMenuSheet() {
    MenuSheet msContainer = new MenuSheet ("MWMenuBar", "__TAG:_MWMENUBAR_");
    msContainer.add (getMultiWindowMenuSheet());

    setMenuSheet (msContainer);
  }

  /**
    * Sets the given MenuSheet as a global MenuSheet in the MultiWindow.
    *
    * @param msNewMenuSheet the MenuSheet to be set
    *
    * @override Never
    */
  public void setMenuSheet (MenuSheet msNewMenuSheet) {
    if (m_msCurrentMenuSheet != null) {
      m_msCurrentMenuSheet.mergePeers (null, null);
      m_msCurrentMenuSheet.setVisible (false);
    }

    m_msCurrentMenuSheet = msNewMenuSheet;

    if (m_msCurrentMenuSheet != null) {

      JMenuBar jmbNewMB = msNewMenuSheet.getMenuBar();

      m_msCurrentMenuSheet.setVisible (true);
      setJMenuBar (jmbNewMB);
    }
    else {
      setJMenuBar (null);
    }

    if (m_nViewMode == TABBED) {
      stateChanged (new ChangeEvent (getContentPane().getComponents()[0]));
    }

    validate();
  }

  /**
    * Get the current global MenuSheet.
    *
    * @override Never
    */
  public MenuSheet getCurrentMenuSheet() {
    return m_msCurrentMenuSheet;
  }

  /**
    * Get the current view mode.
    *
    * @return an int value representing the view mode
    *
    * @override Never
    *
    * @see #TABBED
    * @see #MULTIFRAME
    */
  public int getViewMode() {
    return m_nViewMode;
  }

  /**
    * Set a new view mode.
    *
    * @param nViewMode the view mode to be set
    *
    * @override Never
    *
    * @see #TABBED
    * @see #MULTIFRAME
    * @see #arrangeFrames
    */
  public void setViewMode (int nViewMode) {
    if (m_nViewMode != nViewMode) {
      if (nViewMode == MULTIFRAME) {
        transformToPane ();
      }
      if (nViewMode == TABBED) {
        transformToTab ();
      }
    }

    if (m_msiToggleViewMode != null) {
      m_msiToggleViewMode.setCaption (getToggleViewMenuText());
    }
  }

  /**
    * Set the arrangement of the frames in the MultiWindow.
    *
    * <p>If the MultiWindow is in multiframe mode, the new arrangement
    * will be brought to effect at once. Otherwise it will be carried out
    * the next time the view mode is changed to multiframe.</p>
    *
    * @param nArrangement the new Arrangement
    *
    * @override Never
    */
  public void arrangeFrames (int nArrangement) {

    if (getHandleCount() > 0) {

      int x = 0;
      int y = 0;
      int nWidth = getContentPane().getWidth();
      int nHeight = getContentPane().getHeight();

      switch (nArrangement) {
      case OVERLAPPED:
        // Remember selected handle; this one must be handled last !
        MultiWindowHandle mwhSelected = (MultiWindowHandle) m_mpmwhHandles.get (m_iSelectedFrame);
        for (Iterator i = m_lmwhVisibles.iterator(); i.hasNext();) {
          // build framed form for each element

          MultiWindowHandle mwh = (MultiWindowHandle) i.next();

          if (mwh == mwhSelected) {
            continue;
          }

          JInternalFrame internal = (JInternalFrame) mwh.getFrame();
          internal.setSize (internal.getPreferredSize());
          internal.setLocation (x, y);
          internal.moveToFront();
          x += 30; // increase coordinates, to make sure we get overlapping frames.
          y += 30;

          if ((x > getWidth() - 100) ||
              (y > getHeight() - 100)) {
            // Wrap around if frame left content pane.

            x = 0; y = 0;
          }
        }

        // Handle selected frame
        if (mwhSelected != null) {
          JInternalFrame internal = (JInternalFrame) mwhSelected.getFrame();
          internal.setSize (internal.getPreferredSize());
          internal.setLocation (x, y);
          internal.moveToFront();
        }

        break;

      case TILED_HORIZONTALLY:
        nWidth /= getVisibleHandleCount(); // frame width

        for (Iterator i = m_lmwhVisibles.iterator(); i.hasNext();) {
          MultiWindowHandle mwh = (MultiWindowHandle) i.next();
          JInternalFrame internal = (JInternalFrame) mwh.getFrame();
          internal.setBounds (x, 0, nWidth, nHeight);
          x += nWidth;
        }

        break;

      case TILED_VERTICALLY:
        nHeight /= getVisibleHandleCount(); // frame height

        for (Iterator i = m_lmwhVisibles.iterator(); i.hasNext();) {
          MultiWindowHandle mwh = (MultiWindowHandle) i.next();
          JInternalFrame internal = (JInternalFrame) mwh.getFrame();
          internal.setBounds (0, y, nWidth, nHeight);
          y += nHeight;
        }
      }

      validate();
      m_nArrangement = nArrangement;
    }
  }

  /**
    * Returns a new {@link sale.Display} and opens a new frame for it.
    *
    * <p>This Display is a {@link MultiWindowHandle} and may be casted for extended
    * functionality. However, casting will break transparency and, thus, is usually not a good
    * idea.</p>
    *
    * <p>A FormSheet and a MenuSheet should be set soon since the new frame is opened
    * immediately and would otherwise remain empty.</p>
    *
    * <p>The display returned is initially invisible and must be made visible explicitly by calling one of
    * the <code>makeVisible()</code> methods.</p>
    *
    * @return a Display representing the new handle.
    *
    * @override Never
    *
    * @see #makeVisible(sale.Display)
    * @see #makeVisible(java.util.List, sale.Display)
    */
  public Display getNewHandle() {

    MultiWindowHandle mwhNew = new MultiWindowHandle (new Integer (m_nHandleCount++));

    mwhNew.getFrame().addPropertyChangeListener (this);

    mwhNew.setOwner (this);

    m_mpmwhHandles.put (mwhNew.getID(), mwhNew);

    return mwhNew;
  }

  /**
    * Make visible the specified displays if they are members of this MultiWindow.
    *
    * <p>All displays that are currently visible will become invisible prior to making the new set of displays
    * visible.</p>
    *
    * @param lmwhHandles the list of displays to be made visible.
    * @param dSelected the Display that is to become the selected display. Must be contained in
    * <code>lmwhHandles</code>.
    *
    * @override Never
    */
  public void makeVisible (List lmwhHandles,
                           Display dSelected) {

    m_lmwhVisibles = new ArrayList (lmwhHandles);

    if (m_lmwhVisibles.contains (dSelected)) {
      m_iSelectedFrame = ((MultiWindowHandle) dSelected).getID();
    }
    else {
      if (m_lmwhVisibles.size() > 0) {
        m_iSelectedFrame = ((MultiWindowHandle) m_lmwhVisibles.get (0)).getID();
      }
      else {
        m_iSelectedFrame = new Integer (-1);
      }
    }

    switch (m_nViewMode) {
    case MULTIFRAME:
      transformToPane();
      break;
    case TABBED:
      transformToTab();
      break;
    }
  }

  /**
    * Add the given display to the list of visible displays. The given display will also become the selected
    * display.
    *
    * @param d the display to become visible.
    *
    * @override Never
    */
  public void makeVisible (Display d) {
    MultiWindowHandle mwh = (MultiWindowHandle) d;

    if (m_mpmwhHandles.get (mwh.getID()) != mwh) {
      return;
    }

    if (!m_lmwhVisibles.contains (mwh)) {
      m_lmwhVisibles.add (mwh);

      if (m_lmwhVisibles.size() == 1) {
        m_iSelectedFrame = mwh.getID();
      }

      switch (m_nViewMode) {
      case MULTIFRAME:
        JDesktopPane jdp = (JDesktopPane) getContentPane();

        mwh.transformToPane();

        jdp.add (mwh.getFrame(), mwh.getID().intValue());

        if (m_lmwhVisibles.size() == 1) {
          jdp.moveToFront (mwh.getFrame());
          mwh.getFrame().requestFocus();
          try {
            mwh.getFrame().setSelected (true);
          }
          catch (java.beans.PropertyVetoException e) {}
        }

        break;

      case TABBED:
        JTabbedPane jtp = (JTabbedPane) getContentPane().getComponents()[0];
        mwh.transformToTab();

        jtp.add (mwh.getContentPane(), mwh.getFrame().getTitle());

        if (m_lmwhVisibles.size() == 1) {
          jtp.setSelectedComponent (mwh.getContentPane());
          mwh.getContentPane().requestFocus();
        }
      }
    }
  }

  /**
    * Remove the given display from the list of visible displays.
    *
    * @override Never
    *
    * @param d the display to become invisible.
    */
  public void makeInVisible (Display d) {
    MultiWindowHandle mwh = (MultiWindowHandle) d;

    if (m_lmwhVisibles.contains (mwh)) {
      m_lmwhVisibles.remove (mwh);

      if (m_iSelectedFrame == mwh.getID()) {
        if (m_lmwhVisibles.size() >= 1) {
          m_iSelectedFrame = ((MultiWindowHandle) m_lmwhVisibles.get (0)).getID();
        }
        else {
          m_iSelectedFrame = new Integer (-1);
        }
      }

      switch (m_nViewMode) {
      case MULTIFRAME:
        transformToPane();

        break;

      case TABBED:
        transformToTab();
      }
    }
  }

  /**
    * Get all registered {@link sale.Display displays}.
    *
    * @override Never
    */
  public Iterator iterator() {
    return m_mpmwhHandles.values().iterator();
  }

  /**
    * Get the number of registered MultiWindowHandles.
    *
    * @override Never
    */
  public int getHandleCount() {
    return m_mpmwhHandles.size();
  }

  /**
    * Count all currently visible displays.
    *
    * @override Never
    */
  public int getVisibleHandleCount() {
    return m_lmwhVisibles.size();
  }

  /**
    * Get the currently selected display.
    *
    * @override Never
    */
  public Display getSelectedDisplay() {
    return (Display) m_mpmwhHandles.get (m_iSelectedFrame);
  }

  /**
    * Set the currently selected display.
    *
    * @param d the display to become selected. Must be visible.
    *
    * @override Never
    */
  public void setSelectedDisplay (Display d) {
    if (m_lmwhVisibles.contains (d)) {
      MultiWindowHandle mwh = (MultiWindowHandle) d;

      m_iSelectedFrame = mwh.getID();

      switch (m_nViewMode) {
      case MULTIFRAME:
        JDesktopPane jdp = (JDesktopPane) getContentPane();

        jdp.moveToFront (mwh.getFrame());
        mwh.getFrame().requestFocus();
        try {
          mwh.getFrame().setSelected (true);
        }
        catch (java.beans.PropertyVetoException e) {}

        break;

      case TABBED:
        JTabbedPane jtp = (JTabbedPane) getContentPane().getComponents()[0];

        jtp.setSelectedComponent (mwh.getContentPane());
      }
    }
  }

  /**
    * Transform the MultiWindow to multiframe view.
    *
    * <p>Should not be called directly but rather via
    * {@link #setViewMode setViewMode(MULTIFRAME)}.</p>
    *
    * @override Never
    */
  protected void transformToPane() {

    if (m_nViewMode == MULTIFRAME) {
      JDesktopPane jdp = (JDesktopPane) getContentPane();
      jdp.removeAll();
    }

    m_nViewMode = MULTIFRAME;

    // Create new desptop pane and make it be our content pane.
    JDesktopPane jDeskField = new JDesktopPane();
    jDeskField.setOpaque (true);
    jDeskField.setPreferredSize (getContentPane().getSize());
    setContentPane (jDeskField);

    // Un-merge MenuSheets
    setJMenuBar (m_msCurrentMenuSheet.mergePeers (null, null));

    // Rebuild internal frames, if any. Only use visible handles.
    if (getHandleCount() > 0) {
      for (Iterator i = m_lmwhVisibles.iterator(); i.hasNext();) {
        MultiWindowHandle mwh = (MultiWindowHandle) i.next();

        mwh.transformToPane();

        JInternalFrame internal = (JInternalFrame) mwh.getFrame();

        jDeskField.add (internal);
        internal.moveToFront();
      }

      MultiWindowHandle mwhSelected = (MultiWindowHandle) m_mpmwhHandles.get (m_iSelectedFrame);
      if (mwhSelected != null) {
        mwhSelected.getFrame().moveToFront();
        mwhSelected.getContentPane().requestFocus();
        try {
          mwhSelected.getFrame().setSelected (true);
        }
        catch (java.beans.PropertyVetoException e) {}
      }
    }

    pack();
  }

  /**
    * Transforms the MultiWindow to tabbed view.
    *
    * <p>Should not be called directly but rather via
    * {@link #setViewMode setViewMode(TABBED)}.</p>
    *
    * @override Never
    */
  protected void transformToTab() {

    if (m_nViewMode == TABBED) {
      JTabbedPane jtp = (JTabbedPane) getContentPane().getComponents()[0];
      jtp.removeChangeListener (this);
      jtp.removeAll();
    }

    Dimension d = getContentPane().getSize();

    // Create new panel and make it be our content pane.
    // This is done to ensure, we paint something also where the tabbed pane does not.
    JPanel jp = new JPanel();
    jp.setPreferredSize (d);
    jp.setLayout (new BorderLayout());
    setContentPane(jp);

    // Create the tabbed pane and add a panel for each visible handle, if there are any.
    JTabbedPane jtp = new JTabbedPane();

    if (getHandleCount() > 0) {
      for (Iterator i = m_lmwhVisibles.iterator(); i.hasNext();) {
        MultiWindowHandle mwh = (MultiWindowHandle) i.next();

        mwh.transformToTab();

        jtp.add (mwh.getContentPane(), mwh.getFrame().getTitle());
      }
    }

    m_nViewMode = TABBED;
    jtp.setPreferredSize (d);
    jtp.addChangeListener (this);
    jp.add (jtp);

    MultiWindowHandle mwhSelected = (MultiWindowHandle) m_mpmwhHandles.get (m_iSelectedFrame);
    if (mwhSelected != null) {
      jtp.setSelectedComponent (mwhSelected.getContentPane());
    }

    // explicitly trigger event to make sure, menus are merged correctly.
    stateChanged (new ChangeEvent (jtp));

    pack();
  }

  /**
    * Implementation of the method in {@link javax.swing.event.ChangeListener}.
    *
    * <p><strong>ATTENTION</strong>: This method is public as an implementation detail and must not be called
    * directly!</p>
    *
    * @override Never
    */
  public void stateChanged (ChangeEvent evt) {

    if (m_nViewMode == TABBED) {             // only merge in tab view
      MenuSheet msTab = null;
      m_iSelectedFrame = new Integer(-1);

      // find current selected component
      Component cmpTab = ((JTabbedPane) evt.getSource()).getSelectedComponent();

      // try to find the associated MultiWindowHandle and its MenuSheet
      for (Iterator i = m_mpmwhHandles.values().iterator(); i.hasNext() && (msTab == null);) {
        MultiWindowHandle mwh = (MultiWindowHandle) i.next();

        if (mwh.getContentPane() == cmpTab) {
          msTab = mwh.getMenuSheet();
          m_iSelectedFrame = mwh.getID();
        }
      }

      if (msTab == null) {
        if (m_msCurrentMenuSheet != null) {
          setJMenuBar (m_msCurrentMenuSheet.mergePeers (null, null));
        }
        else {
          setJMenuBar (null);
        }
      }
      else {
        if (m_msCurrentMenuSheet != null) {
          setJMenuBar(m_msCurrentMenuSheet.mergePeers (msTab, MULTIWINDOW_MENU_TAG));
        }
        else {
          setJMenuBar (msTab.getMenuBar());
        }
      }

      if (m_msCurrentMenuSheet != null) {
        m_msCurrentMenuSheet.setVisible (true);
      }

      if (msTab != null) {
        msTab.setVisible (true);
      }

      validate();
    }
  }

  /**
    * Implementation of the method in {@link PropertyChangeListener}.
    *
    * <p><strong>ATTENTION</strong>: This method is public as an implementation detail and must not be called
    * directly!</p>
    *
    * @override Never
    */
  public void propertyChange (PropertyChangeEvent e) {
    if ((e.getSource() instanceof JInternalFrame) &&
        (e.getPropertyName().equals ("maximum")) &&
        (((Boolean) e.getNewValue()).booleanValue())) {

      // if an internal frame has been maximized, restore it and switch to tabbed view.
      setViewMode (TABBED);

      try {
        ((JInternalFrame) e.getSource()).setMaximum (false);
      }
      catch (java.beans.PropertyVetoException ex) {}
    }
  }

  /**
    * Internal communication method: a MenuSheet changed.
    *
    * @override Never
    */
  void onMenuSheetChanged (MultiWindowHandle mwh, MenuSheet msNewMenuSheet) {
    if (m_nViewMode == TABBED) {
      if (mwh.getID() == m_iSelectedFrame) {
        setJMenuBar (m_msCurrentMenuSheet.mergePeers (msNewMenuSheet, MULTIWINDOW_MENU_TAG));
        validate();
      }
    }
    else mwh.getFrame().pack();
  }

  /**
    * Internal communication method: a caption changed.
    *
    * @override Never
    */
  void onDisplayCaptionChanged (MultiWindowHandle mwh, String sNewCaption) {
    if (m_nViewMode == TABBED) {
      if (m_lmwhVisibles.contains (mwh)) {
        JTabbedPane jtp = ((JTabbedPane) getContentPane().getComponents()[0]);

        jtp.setTitleAt (jtp.indexOfComponent (mwh.getContentPane()), sNewCaption);

        validate();
      }
    }
  }

  /**
    * Internal communication method: a component changed.
    *
    * @override Never
    */
  void onComponentChanged (MultiWindowHandle mwh, JComponent jcmpNewComponent) {
    if (m_nViewMode == TABBED) {
      ((JTabbedPane) getContentPane().getComponents()[0]).validate();
    }
    else mwh.getFrame().pack();
  }


  /**
    * Internal communication method: the focus moved to another display.
    *
    * @override Never
    */
  void onDisplayFocusGained (MultiWindowHandle mwh, Integer intID) {
    m_iSelectedFrame = intID;
  }

  /**
    * Internal communication method: a display is closing.
    *
    * @override Never
    */
  void onDisplayClosing (MultiWindowHandle mwh) {
    makeInVisible (mwh);

    mwh.getFrame().removePropertyChangeListener (this);

    m_mpmwhHandles.remove (mwh.getID());
  }

  /**
    * Closes the given display.
    *
    * <p>Additionally, any FormSheet that is still open on the display will be forced to close.</p>
    *
    * @override Never
    *
    * @param dWhich the display to close
    */
  public void closeDisplay (Display dWhich) {
    ((MultiWindowHandle) dWhich).closeDisplay();
  }

  /**
    * Constant for the multiframe view mode.
    * Should be used as parameter for {@link #setViewMode} and
    * for recognizing the return values of {@link #getViewMode}.
    */
  public static final int MULTIFRAME = 0;

  /**
    * Constant for the tabbed view mode.
    * Should be used as parameter for {@link #setViewMode} and
    * for recognizing the return values of {@link #getViewMode}.
    */
  public static final int TABBED = 1;

  /**
    * No view mode yet.
    */
  private static final int NONE = -1;

  /**
    * Constant for cascaded arrangement of the frames in multiframe view mode.
    * Should be used as parameter for {@link #arrangeFrames}.
    */
  public static final int OVERLAPPED = 1;

  /**
    * Constant for vertically tiled arrangement of the frames in multiframe view mode.
    * Should be used as parameter for {@link #arrangeFrames}.
    */
  public static final int TILED_VERTICALLY = 2;

  /**
    * Constant for horizontally tiled arrangement of the frames in multiframe view mode.
    * Should be used as parameter for {@link #arrangeFrames}.
    */
  public static final int TILED_HORIZONTALLY = 3;

  /**
    * Constant used as tag for the MultiWindowMenu.
    * Use this constant to gain access to the menu and manipulate it.
    */
  public static final String MULTIWINDOW_MENU_TAG = "__TAG:_MULTIWINDOW_MENU_";

  /**
    * Constant used as tag for the &quot;Toggle View Mode&quot; menu option.
    * Use this constant to gain access to the menu and manipulate it.
    */
  public static final String TOGGLE_VIEW_TAG = "__TAG:_MULTIWINDOW_TOGGLE_VIEW_";

  /**
    * Constant used as tag for the separator in the multi window menu.
    * Use this constant to gain access to the menu and manipulate it.
    */
  public static final String SEPARATOR_TAG = "__TAG:_MULTIWINDOW_SEPARATOR_";

  /**
    * Constant used as tag for the &quot;Cascade&quot; option.
    * Use this constant to gain access to the menu and manipulate it.
    */
  public static final String CASCADE_TAG = "__TAG:_MULTIWINDOW_CASCADE_";

  /**
    * Constant used as tag for the &quot;Tile horizontally&quot; option.
    * Use this constant to gain access to the menu and manipulate it.
    */
  public static final String TILE_HORIZ_TAG = "__TAG:_MULTIWINDOW_TILE_HORIZ_";

  /**
    * Constant used as tag for the &quot;Tile vertically&quot; option.
    * Use this constant to gain access to the menu and manipulate it.
    */
  public static final String TILE_VERT_TAG = "__TAG:_MULTIWINDOW_TILE_VERT_";

  /**
    * The text that is used to create the &quot;Toggle View Mode&quot; option.
    * Set this text before showing the MultiWindow to localize your MenuSheet.
    */
  public static String s_sToggleViewPrefix = "Toggle View ";

  /**
    * The texts describing the different view modes for purposes of the &quot;Toggle View Mode&quot; option.
    * Set these texts before showing the MultiWindow to localize your MenuSheet.
    */
  public static String[] s_asViewModeTexts = {"(MULTIFRAME)", "(TABBED)"};
}

class ToggleViewModeAction extends MultiWindow.MultiWindowAction {
  public ToggleViewModeAction (MultiWindow owner) {
    super (owner);
  }

  public void doAction (SaleProcess p, SalesPoint sp) {
    m_mwReference.setViewMode ((m_mwReference.getViewMode() == MultiWindow.MULTIFRAME)?(MultiWindow.TABBED):(MultiWindow.MULTIFRAME));
  }
}

class CascadeAction extends MultiWindow.MultiWindowAction {
  public CascadeAction (MultiWindow owner) {
    super (owner);
  }
  
  public void doAction (SaleProcess p, SalesPoint sp) {
    if (m_mwReference.getViewMode() == MultiWindow.TABBED) {
      m_mwReference.setViewMode (MultiWindow.MULTIFRAME);
    }
    m_mwReference.arrangeFrames(MultiWindow.OVERLAPPED);
  }
}

class TileHorizontallyAction extends MultiWindow.MultiWindowAction {
  public TileHorizontallyAction (MultiWindow owner) {
    super (owner);
  }
  
  public void doAction (SaleProcess p, SalesPoint sp) {
    if (m_mwReference.getViewMode() == MultiWindow.TABBED) {
      m_mwReference.setViewMode (MultiWindow.MULTIFRAME);
    }
    m_mwReference.arrangeFrames(MultiWindow.TILED_HORIZONTALLY);
  }
}

class TileVerticallyAction extends MultiWindow.MultiWindowAction {
  public TileVerticallyAction (MultiWindow owner) {
    super (owner);
  }
  
  public void doAction (SaleProcess p, SalesPoint sp) {
    if (m_mwReference.getViewMode() == MultiWindow.TABBED) {
      m_mwReference.setViewMode (MultiWindow.MULTIFRAME);
    }
    m_mwReference.arrangeFrames(MultiWindow.TILED_VERTICALLY);
  }
}