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: "OK" and * "Cancel".</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: "OK" and * "Cancel".</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 "OK" as well * as a "Cancel" 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 "OK" button was clicked. * * @override Sometimes Override this method if you want to implement behavior that is to be executed when * the standard "OK" button was pressed. The default implementation closes the FormSheet. */ public void ok() { m_fCancelled = false; close(); } /** * Hook method called whenever the standard "Cancel" button was clicked. * * @override Sometimes Override this method if you want to implement behavior that is to be executed when * the standard "Cancel" 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(); } }); } }; }