001    package sale;
002    
003    import java.util.*;
004    
005    import javax.swing.*;
006    import javax.swing.JComponent;
007    import javax.swing.JButton;
008    import javax.swing.JPanel;
009    
010    import java.awt.event.*;
011    
012    import java.io.*;
013    
014    import java.awt.Image;
015    
016    /**
017     * A FormSheet to be displayed in a FormSheetContainer.
018     *
019     * <p>FormSheets comprise a caption, a JComponent of arbitrary complexity, and a button bar. FormSheets will
020     * be displayed by {@link FormSheetContainer FormSheetContainers}, which define what the FormSheet finally
021     * looks like on the screen. Usually, the FormSheet's caption will become part of the FormSheetContainer's
022     * frame caption; the FormSheet's component will take up most of the client space of the FormSheetContainer's
023     * frame; and the button bar will be displayed at the bottom side of the FormSheetContainer's frame.
024     * </p>
025     *
026     * <p>However, FormSheets are designed to make transparent the final display of the GUI, and, thus, you as an
027     * application developer do not need to worry about the final layout. All you need to know is what components
028     * you want to present and which buttons you want to put into the button bar. Buttons in the button bar are
029     * associated an {@link Action} that will be performed when the button is clicked. In the
030     * {@link Action#doAction doAction()} method of that Action, you will have access to the {@link SalesPoint}
031     * and {@link SaleProcess} in whose context the FormSheet is displayed, if any. There is also a special
032     * ActionListener, {@link ActionActionListener}, that you can use to associate any button in a FormSheet with
033     * an Action.</p>
034     *
035     * <p>To actually display a FormSheet, you need a {@link Display} on which you can call
036     * {@link Display#setFormSheet} or {@link Display#popUpFormSheet}.</p>
037     *
038     * @author Steffen Zschaler
039     * @version 2.0 21/05/1999
040     * @since v1.0
041     */
042    public class FormSheet extends Object implements Serializable {
043    
044        /**
045         * A button in the FormSheet's button bar.
046         *
047         * @see FormSheet
048         *
049         * @author Steffen Zschaler
050         * @version 2.0 21/05/1999
051         * @since v2.0
052         */
053        public static class FormButton implements ActionListener, Serializable {
054    
055            /**
056             * The buttons caption.
057             *
058             * @serial
059             */
060            private String m_sCaption;
061    
062            /**
063             * The unique ID used to identify the button in the FormSheet.
064             *
065             * @serial
066             */
067            private int m_nID;
068    
069            /**
070             * The button's peer used to display the button. Will be lazyly created when it is
071             * first asked for.
072             */
073            protected transient JButton m_jbPeer;
074    
075            /**
076             * The FormSheet owning this button.
077             *
078             * @serial
079             */
080            private FormSheet m_fsOwner;
081    
082            /**
083             * The action associated with this button.
084             *
085             * @serial
086             */
087            private Action m_aAction;
088    
089            /**
090             * Can this button be clicked?
091             *
092             * @serial
093             */
094            private boolean m_fEnabled;
095    
096            /**
097             * Is this button visible?
098             *
099             * @serial
100             */
101            private boolean m_fVisible;
102    
103            /**
104             * The index of this button in the add sequence of its FormSheet. Used to sort the
105             * buttons when filling the button panel.
106             *
107             * @serial
108             */
109            int m_nAddIndex = 0;
110    
111            /**
112             * The Images associated with the icons of this Button( [0]:DefaultImage, [1]:PressedImage,
113             * [2]:DisabledImage, [3]:PressedDiabledImage ).
114             *
115             * @serial
116             */
117            protected Image m_aiImages[] = null;
118    
119            /**
120             * The Mnemonic of this Button.
121             *
122             * @serial
123             */
124            protected char m_cMnemonic = '\0';
125    
126            /**
127             * The ToolTip of this Button.
128             *
129             * @serial
130             */
131            protected String m_sToolTip = "";
132    
133            /**
134             * The monitor synchronizing access to the peers.
135             */
136            private transient Object m_oPeerLock = null;
137    
138            /**
139             * Return the monitor used to synchronized access to the peers.
140             *
141             * @override Never
142             */
143            protected Object getPeerLock() {
144                if (m_oPeerLock == null) {
145                    m_oPeerLock = new Object();
146                }
147    
148                return m_oPeerLock;
149            }
150    
151            /**
152             * Create a new, initially enabled FormButton.
153             *
154             * @param sCaption the caption of the button.
155             * @param nID a unique ID that can be used to identify the button in its FormSheet.
156             * @param aAction an action to perform when the button was clicked.
157             */
158            public FormButton(String sCaption, int nID, Action aAction) {
159                super();
160    
161                m_sCaption = sCaption;
162                m_nID = nID;
163                m_aAction = aAction;
164                m_fEnabled = true;
165                m_fVisible = true;
166                m_jbPeer = null;
167            }
168    
169            // Helpmethod for setting an ImageIcon
170            private void setIcon(ImageIcon iiImageIcon, int nIndex) {
171                if (m_aiImages == null) {
172                    m_aiImages = new Image[4];
173    
174                }
175                m_aiImages[nIndex] = iiImageIcon.getImage();
176    
177                synchronized (getPeerLock()) {
178                    if (m_jbPeer != null) {
179                        switch (nIndex) {
180                            case DEFAULT_IMAGE:
181                                m_jbPeer.setIcon(iiImageIcon);
182                                break;
183                            case SELECTED_IMAGE:
184                                m_jbPeer.setSelectedIcon(iiImageIcon);
185                                break;
186                            case DISABLED_IMAGE:
187                                m_jbPeer.setDisabledIcon(iiImageIcon);
188                                break;
189                            case DISABLED_SELECTED_IMAGE:
190                                m_jbPeer.setDisabledSelectedIcon(iiImageIcon);
191                                break;
192                        }
193    
194                        m_jbPeer.validate();
195                    }
196                }
197            }
198    
199            /**
200             * Notify this button that it has been attached to, or detached from, a FormSheet.
201             *
202             * @override Never
203             *
204             * @param fs the FormSheet the button has been attached to. If <code>null</code>,
205             * the button has been detached from a FormSheet.
206             */
207            public void attach(FormSheet fs) {
208                m_fsOwner = fs;
209    
210                if (m_jbPeer != null) {
211                    m_jbPeer.removeActionListener(this);
212                    m_jbPeer = null;
213                }
214            }
215    
216            /**
217             * Get the FormSheet this button is attached to.
218             *
219             * @override Never
220             */
221            public FormSheet getFormSheet() {
222                return m_fsOwner;
223            }
224    
225            /**
226             * Hook method called when the FormSheet is hidden. Used to resolve circular
227             * references with the peer, in order to help the garbage collector.
228             *
229             * @override Never
230             */
231            public void hide() {
232                if (m_jbPeer != null) {
233                    m_jbPeer.removeActionListener(this);
234                    m_jbPeer = null;
235                }
236            }
237    
238            /**
239             * Set the caption of the button. If there is a peer, its caption is also changed.
240             *
241             * @override Never
242             *
243             * @param sCaption the new caption.
244             */
245            public void setCaption(String sCaption) {
246                m_sCaption = sCaption;
247    
248                if (m_jbPeer != null) {
249                    m_jbPeer.setText(sCaption);
250                    m_jbPeer.validate();
251                }
252            }
253    
254            /**
255             * Set the mnemonic of this Button.
256             *
257             * @override Never
258             *
259             * @param cMnemonic the new mnemonic.
260             */
261            public void setMnemonic(char cMnemonic) {
262                m_cMnemonic = cMnemonic;
263    
264                synchronized (getPeerLock()) {
265                    if (m_jbPeer != null) {
266                        m_jbPeer.setMnemonic(cMnemonic);
267                        m_jbPeer.validate();
268                    }
269                }
270            }
271    
272            /**
273             * Set the ToolTip of this Button.
274             *
275             * @override Never
276             *
277             * @param s the new ToolTip-Text.
278             */
279            public void setToolTipText(String s) {
280                m_sToolTip = s;
281    
282                synchronized (getPeerLock()) {
283                    if (m_jbPeer != null) {
284                        m_jbPeer.setToolTipText(s);
285                        m_jbPeer.validate();
286                    }
287                }
288            }
289    
290            /**
291             * Set the default icon of this MenuSheetItem.
292             *
293             * <p>If there is a peer it will reflect the changes immediately.</p>
294             *
295             * @override Never
296             *
297             * @param iiImageIcon the new icon.
298             */
299            public void setDefaultIcon(ImageIcon iiImageIcon) {
300                setIcon(iiImageIcon, DEFAULT_IMAGE);
301            }
302    
303            /**
304             * Set the selected icon of this MenuSheetItem.
305             *
306             * <p>If there is a peer it will reflect the changes immediately.</p>
307             *
308             * @override Never
309             *
310             * @param iiImageIcon the new icon.
311             */
312            public void setSelectedIcon(ImageIcon iiImageIcon) {
313                setIcon(iiImageIcon, SELECTED_IMAGE);
314            }
315    
316            /**
317             * Set the disabled icon of this MenuSheetItem.
318             *
319             * <p>If there is a peer it will reflect the changes immediately.</p>
320             *
321             * @override Never
322             *
323             * @param iiImageIcon the new icon.
324             */
325            public void setDisabledIcon(ImageIcon iiImageIcon) {
326                setIcon(iiImageIcon, DISABLED_IMAGE);
327            }
328    
329            /**
330             * Set the disabled selected icon of this MenuSheetItem.
331             *
332             * <p>If there is a peer it will reflect the changes immediately.</p>
333             *
334             * @override Never
335             *
336             * @param iiImageIcon the new icon.
337             */
338            public void setDisabledSelectedIcon(ImageIcon iiImageIcon) {
339                setIcon(iiImageIcon, DISABLED_SELECTED_IMAGE);
340            }
341    
342            /**
343             * Get the caption of the button.
344             *
345             * @override Never
346             */
347            public String getCaption() {
348                return m_sCaption;
349            }
350    
351            /**
352             * Set the enabled state of the button.
353             *
354             * @override Never
355             *
356             * @param fEnabled the new enabled state of the button.
357             */
358            public void setEnabled(boolean fEnabled) {
359                m_fEnabled = fEnabled;
360    
361                if (m_jbPeer != null) {
362                    m_jbPeer.setEnabled(fEnabled);
363                }
364            }
365    
366            /**
367             * Return the enabled state of this button.
368             *
369             * @override Never
370             */
371            public boolean isEnabled() {
372                return m_fEnabled;
373            }
374    
375            /**
376             * Set the visible state of the button.
377             *
378             * @override Never
379             *
380             * @param fVisible the new enabled state of the button.
381             */
382            public void setVisible(boolean fVisible) {
383                m_fVisible = fVisible;
384    
385                if (m_jbPeer != null) {
386                    m_jbPeer.setVisible(fVisible);
387                }
388            }
389    
390            /**
391             * Return the visible state of this button.
392             *
393             * @override Never
394             */
395            public boolean isVisible() {
396                return m_fVisible;
397            }
398    
399            /**
400             * Get the unique ID of this button.
401             *
402             * @override Never
403             */
404            public int getID() {
405                return m_nID;
406            }
407    
408            /**
409             * Get the JButton peer of this button. If there is not yet a peer, create one.
410             * Otherwise, just return the peer that already exists.
411             *
412             * @override Sometimes Override this method if you want to change the appearance of the button's peer.
413             */
414            public JButton getPeer() {
415                if (m_jbPeer == null) {
416                    m_jbPeer = new JButton(m_sCaption);
417                    m_jbPeer.addActionListener(this);
418    
419                    m_jbPeer.setEnabled(m_fEnabled);
420                    m_jbPeer.setVisible(m_fVisible);
421    
422                    if (m_cMnemonic != '\0') {
423                        m_jbPeer.setMnemonic(m_cMnemonic);
424    
425                    }
426                    if (m_sToolTip.compareTo("") != 0) {
427                        m_jbPeer.setToolTipText(m_sToolTip);
428                    }
429    
430                    if (m_aiImages != null) {
431                        // add DefaultIcon, if any
432                        if (m_aiImages[DEFAULT_IMAGE] != null) {
433                            m_jbPeer.setIcon(new ImageIcon((Image)m_aiImages[DEFAULT_IMAGE]));
434                            // add PressedIcon, if any
435                        }
436                        if (m_aiImages[SELECTED_IMAGE] != null) {
437                            m_jbPeer.setSelectedIcon(new ImageIcon((Image)m_aiImages[SELECTED_IMAGE]));
438                            // add DisabledIcon, if any
439                        }
440                        if (m_aiImages[DISABLED_IMAGE] != null) {
441                            m_jbPeer.setDisabledIcon(new ImageIcon((Image)m_aiImages[DISABLED_IMAGE]));
442                            // add DisabledSelectedIcon, if any
443                        }
444                        if (m_aiImages[DISABLED_SELECTED_IMAGE] != null) {
445                            m_jbPeer.setDisabledSelectedIcon(new ImageIcon((Image)m_aiImages[
446                                    DISABLED_SELECTED_IMAGE]));
447                        }
448                    }
449                }
450    
451                return m_jbPeer;
452            }
453    
454            /**
455             * Get the Mnemonic of this Button.
456             *
457             * @override Never
458             *
459             * @return the mnemonic of this Button.
460             */
461            public char getMnemonic() {
462                return m_cMnemonic;
463            }
464    
465            /**
466             * Get the ToolTip of this Button.
467             *
468             * @override Never
469             *
470             * @return the ToolTip-String of this Button.
471             */
472            public String getToolTipText() {
473                return m_sToolTip;
474            }
475    
476            /**
477             * Get the default icon of this Button.
478             *
479             * @override Never
480             *
481             * @return the default icon of this Button.
482             */
483            public ImageIcon getDefaultIcon() {
484                return (m_aiImages != null) ? new ImageIcon((Image)m_aiImages[DEFAULT_IMAGE]) : null;
485            }
486    
487            /**
488             * Get the selected icon of this Button.
489             *
490             * @override Never
491             *
492             * @return the pressed icon of this Button.
493             */
494            public ImageIcon getSelectedIcon() {
495                return (m_aiImages != null) ? new ImageIcon((Image)m_aiImages[SELECTED_IMAGE]) : null;
496            }
497    
498            /**
499             * Get the disabled item of this Button.
500             *
501             * @override Never
502             *
503             * @return the disabled icon of this Button.
504             */
505            public ImageIcon getDisabledIcon() {
506                return (m_aiImages != null) ? new ImageIcon((Image)m_aiImages[DISABLED_IMAGE]) : null;
507            }
508    
509            /**
510             * Get the disabled selected item of this Button.
511             *
512             * @override Never
513             *
514             * @return the disabled selected icon of this Button.
515             */
516            public ImageIcon getDisabledSelectedIcon() {
517                return (m_aiImages != null) ? new ImageIcon((Image)m_aiImages[DISABLED_SELECTED_IMAGE]) : null;
518            }
519    
520            /**
521             * Set the action that is performed when this button is clicked.
522             *
523             * @override Never
524             *
525             * @param aAction the action to be performed, when this button is clicked.
526             *
527             * @return the previously attached action, if any.
528             */
529            public Action setAction(Action aAction) {
530                Action aOld = m_aAction;
531    
532                m_aAction = aAction;
533    
534                return aOld;
535            }
536    
537            /**
538             * ActionListener interface method, invoked when the peer was clicked. Performs
539             * the currently associated action.
540             *
541             * @override Never
542             *
543             * @see #setAction
544             */
545            public void actionPerformed(ActionEvent e) {
546                final Action aTemp = m_aAction;
547    
548                if (aTemp != null) {
549                    new Thread("ActionPerfomer: FormButton: \"" + getCaption() + "\"") {
550                        public void run() {
551                            try {
552                                aTemp.doAction(m_fsOwner.getProcess(), m_fsOwner.getSalesPoint());
553                            }
554                            catch (ThreadDeath td) {
555                                throw td;
556                            }
557                            catch (Throwable t) {
558                                System.err.println("Exception occured during event dispatching: FormButton \"" +
559                                        getCaption() + "\":");
560                                t.printStackTrace();
561                            }
562                        }
563                    }
564    
565                    .start();
566                }
567            }
568    
569            // A Tag that will identify the Image for the DefaultIcon
570            private final static int DEFAULT_IMAGE = 0;
571            // A Tag that will identify the Image for the SelectedIcon
572            private final static int SELECTED_IMAGE = 1;
573            // A Tag that will identify the Image for the DisabledIcon
574            private final static int DISABLED_IMAGE = 2;
575            // A Tag that will identify the Image for the DisabledSelectedIcon
576            private final static int DISABLED_SELECTED_IMAGE = 3;
577        }
578    
579        /**
580         * Flag indicating whether {@link Display#setFormSheet} should wait
581         * for the FormSheet to be closed.
582         *
583         * @serial
584         */
585        private boolean m_fWaitResponse;
586    
587        /**
588         * The FormSheet's caption.
589         *
590         * @serial
591         */
592        private String m_sCaption;
593    
594        /**
595         * The FormSheetContentCreator(s) that created the contents of this FormSheet.
596         *
597         * @serial
598         */
599        private FormSheetContentCreator m_fsccContentCreator;
600    
601        /**
602         * The FormSheet's component.
603         */
604        private transient JComponent m_jcmpComponent;
605        /**
606         * The monitor used to synchronize access to the FormSheet's component.
607         */
608        private transient Object m_oComponentLock;
609        /**
610         * Get the monitor to be used when accessing this FormSheet's component.
611         *
612         * <p>{@link FormSheetContainer FormSheetContainers} can use this monitor when displaying the FormSheet, to
613         * make sure, they don't loose any changes about the component.</p>
614         *
615         * @override Never
616         */
617        public Object getComponentLock() {
618            if (m_oComponentLock == null) {
619                m_oComponentLock = new Object();
620            }
621    
622            return m_oComponentLock;
623        }
624    
625        /**
626         * The buttons in this FormSheet's button bar.
627         */
628        private transient Map m_mpfbButtons; // written and read by writeObject and readObject, resp.
629        /**
630         * The monitor used to synchronize access to the FormSheet's buttons.
631         */
632        private transient Object m_oButtonsLock;
633        /**
634         * Get the monitor used to synchronize access to the FormSheet's button bar.
635         *
636         * <p>{@link sale.FormSheetContainer FormSheetContainers} can use this lock to make
637         * sure, they don't loose any button change events while making the FormSheet visible.
638         * </p>
639         *
640         * @override Never
641         */
642        public Object getButtonsLock() {
643            if (m_oButtonsLock == null) {
644                m_oButtonsLock = new Object();
645            }
646    
647            return m_oButtonsLock;
648        }
649    
650        /**
651         * The SalesPoint currently attached to this FormSheet.
652         *
653         * @serial
654         */
655        private SalesPoint m_spAttached;
656    
657        /**
658         * The process attached to this FormSheet.
659         *
660         * @serial
661         */
662        private SaleProcess m_pAttached;
663    
664        /**
665         * The FormSheetContainer displaying this FormSheet.
666         *
667         * @serial
668         */
669        private FormSheetContainer m_fscDisplay;
670        /**
671         * The monitor used to synchronize access to the FormSheetContainer.
672         */
673        private transient Object m_oDisplayLock;
674        /**
675         * Get the monitor used to synchronize access to the display.
676         *
677         * <p>Subclasses of FormSheet can use this lock when defining further events that
678         * must be handled by the {@link FormSheetContainer}.</p>
679         *
680         * @override Never
681         */
682        protected Object getDisplayLock() {
683            if (m_oDisplayLock == null) {
684                m_oDisplayLock = new Object();
685            }
686    
687            return m_oDisplayLock;
688        }
689    
690        /**
691         * The add index of the last button added.
692         *
693         * @serial
694         */
695        private int m_nLastAddIndex = 0;
696    
697        /**
698         * True if this FormSheet was canelled.
699         *
700         * @serial
701         */
702        protected boolean m_fCancelled = false;
703    
704        /**
705         * First writes all the default serializable fields. Then, if there is no {@link FormSheetContentCreator},
706         * writes all the buttons in the button bar.
707         */
708        private void writeObject(ObjectOutputStream oos) throws IOException {
709            oos.defaultWriteObject();
710    
711            if (m_fsccContentCreator == null) {
712                oos.writeObject(m_mpfbButtons);
713            }
714        }
715    
716        /**
717         * First reads all the default serializable fields. Then, if there is no {@link FormSheetContentCreator},
718         * reads all the buttons in the button bar. Otherwise, a call to the FormSheetContentCreator's
719         * {@link FormSheetContentCreator#createFormSheetContent} method will be
720         * {@link ObjectInputStream#registerValidation scheduled} with a priority of {@link OIV#FORMSHEET_PRIO}.
721         */
722        private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
723            ois.defaultReadObject();
724    
725            if (m_fsccContentCreator == null) {
726                m_mpfbButtons = (Map)ois.readObject();
727            } else {
728                ois.registerValidation(new ObjectInputValidation() {
729                    public void validateObject() {
730                        createFromContentCreator();
731                    }
732                }
733    
734                , OIV.FORMSHEET_PRIO);
735            }
736        }
737    
738        /**
739         * Create a new FormSheet. {@link Display#setFormSheet} will block until this FormSheet is closed.
740         *
741         * <p>By default, a FormSheet has two standard buttons: "OK" and
742         * "Cancel".</p>
743         *
744         * @param sCaption the caption of the FormSheet.
745         * @param jcmpComponent the component of the FormSheet.
746         *
747         * @see #ok
748         * @see #cancel
749         */
750        public FormSheet(String sCaption, JComponent jcmpComponent) {
751            this(sCaption, jcmpComponent, true);
752        }
753    
754        /**
755         * Create a new FormSheet.
756         *
757         * <p>By default, a FormSheet has two standard buttons: "OK" and
758         * "Cancel".</p>
759         *
760         * @param sCaption the caption of the FormSheet.
761         * @param jcmpComponent the component of the FormSheet.
762         * @param fWaitResponse if true,  {@link Display#setFormSheet} will
763         * block until this FormSheet is closed.
764         *
765         * @see #ok
766         * @see #cancel
767         */
768        public FormSheet(String sCaption, JComponent jcmpComponent, boolean fWaitResponse) {
769            super();
770    
771            m_sCaption = sCaption;
772            m_fWaitResponse = fWaitResponse;
773    
774            m_mpfbButtons = new HashMap();
775    
776            DEFAULT_CONTENT_CREATOR.createFormSheetContent(this);
777    
778            // needs to go to the end so that DEFAULT_CONTENT_CREATOR doesn't remove it again!
779            m_jcmpComponent = jcmpComponent;
780        }
781    
782        /**
783         * Create a new FormSheet, using a content creator.
784         *
785         * <p>When the FormSheet is being serialized, only the content creator will be serialized. When the FormSheet
786         * gets deserialized, the content creator is called to restore the FormSheet's content.</p>
787         *
788         * @param sCaption the FormSheet's caption
789         * @param fscc the FormSheetContentCreator that will create the FormSheet's contents
790         * @param fWaitResponse if true,  {@link sale.Display#setFormSheet} will
791         * block until this FormSheet is closed.
792         */
793        public FormSheet(String sCaption, FormSheetContentCreator fscc, boolean fWaitResponse) {
794            this(sCaption, (JComponent)null, fWaitResponse);
795    
796            addContentCreator(fscc);
797        }
798    
799        /**
800         * Create the FormSheet's contents from the contents creator.
801         *
802         * @override Never
803         */
804        private final void createFromContentCreator() {
805            synchronized (getButtonsLock()) {
806                if ((m_mpfbButtons != null) && (m_mpfbButtons.size() > 0)) {
807                    removeAllButtons();
808                } else {
809                    m_mpfbButtons = new HashMap();
810                }
811            }
812    
813            m_fsccContentCreator.createFormSheetContent(this, true);
814        }
815    
816        /**
817         * Add a contents creator for this FormSheet.
818         *
819         * <p>When the contents creator is used to create the FormSheet's contents, all contents creators that have
820         * ever been added to the FormSheet will be called in the order in which they were added. This ensures, that
821         * you can subclass FormSheets that use contents creators properly, extending their contents by simply
822         * adding another contents creator.
823         *
824         * <p>In the first contents creator you can assume a <code>null</code> component, a "OK" as well
825         * as a "Cancel" button.</p>
826         *
827         * @override Never
828         *
829         * @param fscc the new FormSheetContentCreator. Must not be <code>null</code>.
830         *
831         * @see #ok
832         * @see #cancel
833         */
834        public final void addContentCreator(FormSheetContentCreator fscc) {
835            boolean fCreateComplete = false;
836    
837            if (m_fsccContentCreator != null) {
838                fscc.setParent(m_fsccContentCreator);
839            } else {
840                fscc.setParent(DEFAULT_CONTENT_CREATOR);
841                fCreateComplete = true;
842            }
843            m_fsccContentCreator = fscc;
844    
845            fscc.createFormSheetContent(this, fCreateComplete);
846        }
847    
848        /**
849         * Set the component for this FormSheet.
850         *
851         * <p>If the FormSheet is being displayed, an {@link FormSheetContainer#onFormSheetComponentChanged}
852         * event is fired, so that the change can affect the display instantaneously.</p>
853         *
854         * @override Never
855         *
856         * @param jcmpComponent the new component
857         *
858         * @return the previous component of this FormSheet, if any.
859         */
860        public JComponent setComponent(JComponent jcmpComponent) {
861            synchronized (getComponentLock()) {
862                JComponent jcmpOld = m_jcmpComponent;
863    
864                m_jcmpComponent = jcmpComponent;
865    
866                synchronized (getDisplayLock()) {
867                    if (m_fscDisplay != null) {
868                        m_fscDisplay.onFormSheetComponentChanged(this, m_jcmpComponent);
869                    }
870                }
871    
872                return jcmpOld;
873            }
874        }
875    
876        /**
877         * Get the component of this FormSheet.
878         *
879         * @override Never
880         */
881        public JComponent getComponent() {
882            synchronized (getComponentLock()) {
883                return m_jcmpComponent;
884            }
885        }
886    
887        /**
888         * Set the caption for this FormSheet.
889         *
890         * <p>If the FormSheet is being displayed, an {@link FormSheetContainer#onFormSheetCaptionChanged} event is
891         * fired, so that the change can affect the display instantaneously.</p>
892         *
893         * @override Never
894         *
895         * @param sCaption the new caption.
896         */
897        public void setCaption(String sCaption) {
898            m_sCaption = sCaption;
899    
900            synchronized (getDisplayLock()) {
901                if (m_fscDisplay != null) {
902                    m_fscDisplay.onFormSheetCaptionChanged(this, m_sCaption);
903                }
904            }
905        }
906    
907        /**
908         * Get the FormSheet's caption.
909         *
910         * @override Never
911         */
912        public String getCaption() {
913            return m_sCaption;
914        }
915    
916        /**
917         * Return whether the cancel button was used to close the FormSheet.
918         *
919         * @override Never
920         */
921        public boolean isCancelled() {
922            return m_fCancelled;
923        }
924    
925        /**
926         * Close the FormSheet. This will issue a call to {@link FormSheetContainer#closeFormSheet}.
927         *
928         * @override Never
929         */
930        public void close() {
931            synchronized (getDisplayLock()) {
932                if (m_fscDisplay != null) {
933                    m_fscDisplay.closeFormSheet(this);
934                }
935            }
936        }
937    
938        /**
939         * Return true if {@link Display#setFormSheet} should block until the FormSheet is closed.
940         *
941         * @override Never Instead, set the <code>waitResponse</code> property by calling {@link #setWaitResponse}
942         * before making the FormSheet visible.
943         */
944        public boolean waitResponse() {
945            return m_fWaitResponse;
946        }
947    
948        /**
949         * Set the <code>waitResponse</code> property of this FormSheet.
950         *
951         * <p>The <code>waitResponse</code> property decides whether or not {@link Display#setFormSheet} should
952         * block until the FormSheet is closed.</p>
953         *
954         * <p>The new value of the <code>waitResponse</code> property will have no effect before the FormSheet is
955         * displayed the next time, by calling <code>setFormSheet()</code>.
956         *
957         * @override Never
958         *
959         * @param fWaitResponse the new value for the <code>waitResponse</code> property. If true
960         * {@link sale.Display#setFormSheet} should block until the FormSheet is closed.
961         */
962        public void setWaitResponse(boolean fWaitResponse) {
963            m_fWaitResponse = fWaitResponse;
964        }
965    
966        /**
967         * Attach a SalesPoint to this FormSheet.
968         *
969         * <p>You will usually not call this method directly, it is called by the Framework.</p>
970         *
971         * @override Never
972         *
973         * @param sp the SalesPoint to be attached.
974         *
975         * @return the previously attached SalesPoint, if any.
976         */
977        public SalesPoint attach(SalesPoint sp) {
978            SalesPoint spOld = m_spAttached;
979    
980            m_spAttached = sp;
981    
982            return spOld;
983        }
984    
985        /**
986         * Detach the current SalesPoint from this FormSheet.
987         *
988         * <p>You will usually not call this method directly, it is called by the Framework.</p>
989         *
990         * @override Never
991         *
992         * @return the detached SalesPoint, if any.
993         */
994        public SalesPoint detachSalesPoint() {
995            return attach((SalesPoint)null);
996        }
997    
998        /**
999         * Get the currently attached SalesPoint.
1000         *
1001         * @override Never
1002         */
1003        public SalesPoint getSalesPoint() {
1004            return m_spAttached;
1005        }
1006    
1007        /**
1008         * Attach a process to this FormSheet.
1009         *
1010         * <p>You will usually not call this method directly, it is called by the Framework.</p>
1011         *
1012         * @override Never
1013         *
1014         * @param p the process to be attached.
1015         *
1016         * @return the previously attached process, if any.
1017         */
1018        public SaleProcess attach(SaleProcess p) {
1019            SaleProcess pOld = m_pAttached;
1020    
1021            m_pAttached = p;
1022    
1023            return pOld;
1024        }
1025    
1026        /**
1027         * Detach the current process from this FormSheet.
1028         *
1029         * <p>You will usually not call this method directly, it is called by the Framework.</p>
1030         *
1031         * @override Never
1032         *
1033         * @return the detached process, if any.
1034         */
1035        public SaleProcess detachProcess() {
1036            return attach((SaleProcess)null);
1037        }
1038    
1039        /**
1040         * Get the currently attached process.
1041         *
1042         * @override Never
1043         */
1044        public SaleProcess getProcess() {
1045            return m_pAttached;
1046        }
1047    
1048        /**
1049         * Attach a FormSheetContainer to this FormSheet.
1050         *
1051         * <p>You will usually not call this method directly, it is called by the Framework.</p>
1052         *
1053         * @override Never
1054         *
1055         * @param fsc the FormSheetContainer to be attached.
1056         *
1057         * @return the previously attached FormSheetContainer, if any.
1058         */
1059        public FormSheetContainer attach(FormSheetContainer fsc) {
1060            synchronized (getDisplayLock()) {
1061                FormSheetContainer fscOld = m_fscDisplay;
1062    
1063                m_fscDisplay = fsc;
1064    
1065                if (fsc == null) {
1066                    for (Iterator i = buttonIterator(); i.hasNext(); ) {
1067                        ((FormButton)i.next()).hide();
1068                    }
1069                }
1070    
1071                return fscOld;
1072            }
1073        }
1074    
1075        /**
1076         * Detach the current FormSheetContainer from this FormSheet.
1077         *
1078         * <p>You will usually not call this method directly, it is called by the Framework.</p>
1079         *
1080         * @override Never
1081         *
1082         * @return the detached FormSheetContainer, if any.
1083         */
1084        public FormSheetContainer detachDisplay() {
1085            return attach((FormSheetContainer)null);
1086        }
1087    
1088        /**
1089         * Get the currently attached FormSheetContainer.
1090         *
1091         * @override Never
1092         */
1093        public FormSheetContainer getDisplay() {
1094            synchronized (getDisplayLock()) {
1095                return m_fscDisplay;
1096            }
1097        }
1098    
1099        ///////////////////////////////////////////////////////////////////////////////////
1100        // Button management
1101        ///////////////////////////////////////////////////////////////////////////////////
1102    
1103        /**
1104         * Add a button to the FormSheet's button bar.
1105         *
1106         * <p>If the FormSheet is being displayed, an {@link FormSheetContainer#onFormSheetButtonAdded} event is
1107         * fired, so that the change can affect the display instantaneously.</p>
1108         *
1109         * @override Never
1110         *
1111         * @param sCaption the caption of the button
1112         * @param nID the ID of the button. This ID will later be used to identify the button and, therefore, must
1113         * be unique for this FormSheet. If there is already a button in this FormSheet that has the same ID, a
1114         * {@link DuplicateButtonIDError} will be thrown.
1115         * @param aAction the action to be associated with the button.
1116         *
1117         * @exception DuplicateButtonIDError if a button with the same ID already exists.
1118         */
1119        public void addButton(String sCaption, int nID, Action aAction) {
1120            addButton(new FormButton(sCaption, nID, aAction));
1121        }
1122    
1123        /**
1124         * Add a button to the FormSheet's button bar.
1125         *
1126         * <p>If the FormSheet is being displayed, an {@link FormSheetContainer#onFormSheetButtonAdded} event is
1127         * fired, so that the change can affect the display instantaneously.</p>
1128         *
1129         * @override Never
1130         *
1131         * @param fb the button to be added. The button's ID will later be used to identify it and, therefore, must
1132         * be unique for this FormSheet. If there is already a button in this FormSheet that has the same ID, a
1133         * {@link DuplicateButtonIDError} will be thrown.
1134         *
1135         * @exception DuplicateButtonIDError if a button with the same ID already exists.
1136         */
1137        public void addButton(FormButton fb) {
1138            synchronized (getButtonsLock()) {
1139                if (getButton(fb.getID()) != null) {
1140                    throw new DuplicateButtonIDError("In FormSheet \"" + getCaption() + "\" button #" + fb.getID() +
1141                            " already exists.");
1142                }
1143    
1144                m_mpfbButtons.put(new Integer(fb.getID()), fb);
1145                fb.m_nAddIndex = m_nLastAddIndex++;
1146                fb.attach(this);
1147    
1148                synchronized (getDisplayLock()) {
1149                    if (m_fscDisplay != null) {
1150                        m_fscDisplay.onFormSheetButtonAdded(this, fb);
1151                    }
1152                }
1153            }
1154        }
1155    
1156        /**
1157         * Remove a button from the FormSheet's button bar.
1158         *
1159         * <p>If the FormSheet is being displayed, an {@link FormSheetContainer#onFormSheetButtonRemoved} event is
1160         * fired, so that the change can affect the display instantaneously.</p>
1161         *
1162         * @override Never
1163         *
1164         * @param nID the ID of the button to be removed. If the button does not exist, nothing happens.
1165         *
1166         * @return the removed button, if any.
1167         */
1168        public FormButton removeButton(int nID) {
1169            synchronized (getButtonsLock()) {
1170                FormButton fbOld = (FormButton)m_mpfbButtons.remove(new Integer(nID));
1171    
1172                if (fbOld != null) {
1173                    synchronized (getDisplayLock()) {
1174                        if (m_fscDisplay != null) {
1175                            m_fscDisplay.onFormSheetButtonRemoved(this, fbOld);
1176                        }
1177                    }
1178    
1179                    fbOld.attach(null);
1180                }
1181    
1182                return fbOld;
1183            }
1184        }
1185    
1186        /**
1187         * Remove all buttons from the FormSheet's button bar.
1188         *
1189         * <p>If the FormSheet is being displayed, an {@link FormSheetContainer#onFormSheetButtonsCleared} event is
1190         * fired, so that the change can affect the display instantaneously.</p>
1191         *
1192         * @override Never
1193         */
1194        public void removeAllButtons() {
1195            synchronized (getButtonsLock()) {
1196                for (Iterator i = buttonIterator(); i.hasNext(); ) {
1197                    ((FormButton)i.next()).attach(null);
1198                }
1199    
1200                m_mpfbButtons = new HashMap();
1201    
1202                synchronized (getDisplayLock()) {
1203                    if (m_fscDisplay != null) {
1204                        m_fscDisplay.onFormSheetButtonsCleared(this);
1205                    }
1206                }
1207            }
1208        }
1209    
1210        /**
1211         * Get a button from the FormSheet's button bar.
1212         *
1213         * @override Never
1214         *
1215         * @param nID the ID of the button to be returned.
1216         */
1217        public FormButton getButton(int nID) {
1218            synchronized (getButtonsLock()) {
1219                return (FormButton)m_mpfbButtons.get(new Integer(nID));
1220            }
1221        }
1222    
1223        /**
1224         * Return a fail-fast, readonly iterator iterating over the buttons in the button bar.
1225         *
1226         * <p>The buttons will not be returned in the order in which they where added, but in
1227         * a random order. To get them sorted in order of adding, see {@link #buttonIterator(boolean)}.</p>
1228         *
1229         * @override Never
1230         */
1231        public Iterator buttonIterator() {
1232            return buttonIterator(false);
1233        }
1234    
1235        /**
1236         * Return a readonly iterator iterating over the buttons in the button bar.
1237         *
1238         * @override Never
1239         *
1240         * @param fSorted if true, the buttons will be returned in the order in which they
1241         * were added to the FormSheet.
1242         */
1243        public Iterator buttonIterator(boolean fSorted) {
1244            Iterator iReturn;
1245    
1246            synchronized (getButtonsLock()) {
1247                if (fSorted) {
1248                    List lfbButtons = new ArrayList(m_mpfbButtons.values());
1249    
1250                    Collections.sort(lfbButtons, new Comparator() {
1251                        public int compare(Object o1, Object o2) {
1252                            FormButton fb1 = (FormButton)o1;
1253                            FormButton fb2 = (FormButton)o2;
1254    
1255                            return (fb1.m_nAddIndex - fb2.m_nAddIndex);
1256                        }
1257                    });
1258    
1259                    iReturn = lfbButtons.iterator();
1260                } else {
1261                    iReturn = m_mpfbButtons.values().iterator();
1262                }
1263            }
1264    
1265            class I implements Iterator, Serializable {
1266                private Iterator m_i;
1267    
1268                public I(Iterator i) {
1269                    m_i = i;
1270                }
1271    
1272                public boolean hasNext() {
1273                    return m_i.hasNext();
1274                }
1275    
1276                public Object next() {
1277                    return m_i.next();
1278                }
1279    
1280                public void remove() {
1281                    throw new UnsupportedOperationException(
1282                            "Please use the FormSheet's removeButton() method, not the iterator's remove() method.");
1283                }
1284            }
1285    
1286            return new I(iReturn);
1287        }
1288    
1289        /**
1290         * Called by the Framework to generate the button bar's representation.
1291         *
1292         * @override Never
1293         *
1294         * @param jp the panel to be filled. The buttons will be added in the order in which
1295         * they where added to the FormSheet.
1296         */
1297        public void fillBtnPanel(JPanel jp) {
1298            synchronized (getButtonsLock()) {
1299                for (Iterator i = buttonIterator(true); i.hasNext(); ) {
1300                    FormButton fb = (FormButton)i.next();
1301                    jp.add(fb.getPeer());
1302                }
1303            }
1304        }
1305    
1306        /**
1307         * Hook method called whenever the standard "OK" button was clicked.
1308         *
1309         * @override Sometimes Override this method if you want to implement behavior that is to be executed when
1310         * the standard "OK" button was pressed. The default implementation closes the FormSheet.
1311         */
1312        public void ok() {
1313            m_fCancelled = false;
1314            close();
1315        }
1316    
1317        /**
1318         * Hook method called whenever the standard "Cancel" button was clicked.
1319         *
1320         * @override Sometimes Override this method if you want to implement behavior that is to be executed when
1321         * the standard "Cancel" button was pressed. The default implementation marks the FormSheet
1322         * cancelled and closes it.
1323         *
1324         * @see #isCancelled
1325         */
1326        public void cancel() {
1327            m_fCancelled = true;
1328            close();
1329        }
1330    
1331        public String toString() {
1332            return "FormSheet{\"" + getClass().getName() + "\"} caption=\"" + getCaption() + "\", waitResponse=" +
1333                    waitResponse();
1334        }
1335    
1336        ////////////////////////////////////////////////////////////////////////////////////////////////////////////
1337        /// STATIC PART
1338        ////////////////////////////////////////////////////////////////////////////////////////////////////////////
1339    
1340        /**
1341         * Button ID used for the standard OK button.
1342         */
1343        public static final int BTNID_OK = -2;
1344    
1345        /**
1346         * Button ID used for the standard Cancel button.
1347         */
1348        public static final int BTNID_CANCEL = -1;
1349    
1350        /**
1351         * The default FormSheetContentCreator, that creates the default OK and Cancel button.
1352         */
1353        private static final FormSheetContentCreator DEFAULT_CONTENT_CREATOR = new FormSheetContentCreator() {
1354            protected void createFormSheetContent(final FormSheet fs) {
1355                fs.setComponent(null);
1356                fs.removeAllButtons();
1357    
1358                fs.addButton("OK", BTNID_OK, new Action() {
1359                    public void doAction(SaleProcess p, SalesPoint sp) {
1360                        fs.ok();
1361                    }
1362                });
1363    
1364                fs.addButton("Cancel", BTNID_CANCEL, new Action() {
1365                    public void doAction(SaleProcess p, SalesPoint sp) {
1366                        fs.cancel();
1367                    }
1368                });
1369            }
1370        };
1371    }