001    package sale;
002    
003    import java.util.*;
004    import java.io.IOException;
005    
006    import java.awt.Rectangle;
007    
008    import javax.swing.*;
009    import users.*;
010    import log.*;
011    
012    import sale.events.*;
013    
014    import data.DataBasket;
015    import data.Stock;
016    import data.Catalog;
017    
018    import util.*;
019    
020    /**
021     * A single point of sale in a shop.
022     *
023     * <p>SalesPoints represent single points of sale in a {@link Shop} at which user interaction occurs.
024     * Examples for such SalesPoints are cash counters, box offices, ticket vending machines etc.</p>
025     *
026     * <p>Normally, you will have at least one SalesPoint in your application.</p>
027     *
028     * <p>Services available at SalesPoints are implemented as {@link SaleProcess processes}. There can be at most
029     * one process running at a SalesPoint at any given point of time.</p>
030     *
031     * <p>SalesPoints are {@link ProcessContext process contexts} to the processes running at them. They provide
032     * data and log access, as well as a display and current user. When a SalesPoint is created, a
033     * {@link Display display} is attached to it, which will be used by the process.</p>
034     *
035     * <p>A {@link User user} can be attached to the SalesPoint. Its capabilities will determine what can and cannot
036     * be done at the SalesPoint.</p>
037     *
038     * @author Steffen Zschaler
039     * @version 2.0 15/07/1999
040     * @since v1.0
041     */
042    public class SalesPoint extends Object implements ProcessContext, FormSheetListener, SerializableListener {
043    
044        /**
045             * ID for serialization.
046             */
047            private static final long serialVersionUID = 127266087990287650L;
048    
049            /**
050         * The process currently running on this SalesPoint, if any.
051         *
052         * @serial
053         */
054        protected SaleProcess m_pCurProcess;
055    
056        /**
057         * The monitor synchronizing process access.
058         */
059        private transient Object m_oProcessLock;
060    
061        /**
062         * Return the monitor synchronizing process access.
063         *
064         * @override Never
065         */
066        protected final Object getProcessLock() {
067            if (m_oProcessLock == null) {
068                m_oProcessLock = new Object();
069            }
070    
071            return m_oProcessLock;
072        }
073    
074        /**
075         * The name of this SalesPoint.
076         *
077         * @serial
078         */
079        private String m_sName;
080    
081        /**
082         * ID of this SalesPoint. As SalesPoints can share the same name, the ID is used as unique
083         * identifier.
084         *
085         * @serial
086         */
087        private int m_iID = -1;
088    
089        /**
090         * A stack which saves the process on this SalesPoint. When a new process is started on the SalesPoint
091         * the currently running process is pushed onto the stack.
092         */
093        private Stack<SaleProcess> m_stkProcess = new Stack<SaleProcess>();
094    
095    
096        /**
097         * The display currently attached to this SalesPoint, if any. This will be saved/restored by the Shop.
098         */
099        private transient Display m_dDisplay;
100    
101        /**
102         * The monitor synchronizing access to the display.
103         */
104        private transient Object m_oDisplayLock;
105    
106        /**
107         * Return the monitor synchronizing access to the display.
108         *
109         * @override Never
110         */
111        private final Object getDisplayLock() {
112            if (m_oDisplayLock == null) {
113                m_oDisplayLock = new Object();
114            }
115    
116            return m_oDisplayLock;
117        }
118    
119        /**
120         * The status display currently attached to this SalesPoint, if any.
121         *
122         * @serial
123         */
124        private Display m_dStatus;
125    
126        /**
127         * The monitor synchronizing access to the status display.
128         */
129        private transient Object m_oStatusDisplayLock;
130    
131        /**
132         * Return the monitor synchronizing access to the status display.
133         *
134         * @override Never
135         */
136        private final Object getStatusDisplayLock() {
137            if (m_oStatusDisplayLock == null) {
138                m_oStatusDisplayLock = new Object();
139            }
140    
141            return m_oStatusDisplayLock;
142        }
143    
144        /**
145         * Flag indicating whether or not the SalesPoint is currently suspended.
146         *
147         * @serial
148         */
149        private boolean m_fSuspended = false;
150    
151        /**
152         * The monitor synchronizing access to the closing-process.
153         */
154        private transient Object m_oCloseLock;
155    
156        /**
157         * Return the monitor synchronizing access to the closing-process.
158         *
159         * @override Never
160         */
161        private final Object getCloseLock() {
162            if (m_oCloseLock == null) {
163                m_oCloseLock = new Object();
164            }
165    
166            return m_oCloseLock;
167        }
168    
169        /**
170         * Flag indicating whether or not the SalesPoint is currently closing.
171         *
172         * @serial
173         */
174        private boolean m_fIsClosing = false;
175    
176        /**
177         * The DataBasket currently attached to this SalesPoint, if any.
178         *
179         * @serial
180         */
181        private DataBasket m_dbBasket;
182    
183        /**
184         * The monitor synchronizing access to the DataBasket.
185         */
186        private transient Object m_oBasketLock;
187    
188        /**
189         * Return the monitor synchronizing access to the DataBasket.
190         *
191         * @override Never
192         */
193        private final Object getBasketLock() {
194            if (m_oBasketLock == null) {
195                m_oBasketLock = new Object();
196            }
197    
198            return m_oBasketLock;
199        }
200    
201        /**
202         * The User currently attached to this SalesPoint, if any.
203         *
204         * @serial
205         */
206        private User m_usrUser;
207    
208        /**
209         * The monitor synchronizing access to the User.
210         */
211        private transient Object m_oUserLock;
212    
213        /**
214         * The SalesPoints Framebounds.
215         */
216        private Rectangle m_rSalesPointFrameBounds = null;
217    
218        //private static int m_iInt = 0;
219    
220        /**
221         * Return the monitor synchronizing access to the User.
222         *
223         * @override Never
224         */
225        private final Object getUserLock() {
226            if (m_oUserLock == null) {
227                m_oUserLock = new Object();
228            }
229    
230            return m_oUserLock;
231        }
232    
233        /**
234         * SalesPoint store no data except the default serializable fields. This method exists only for debugging
235         * purposes.
236         */
237        private void writeObject(java.io.ObjectOutputStream oos) throws java.io.IOException {
238            util.Debug.print("Writing SalesPoint: \"" + getName() + "\".", -1);
239    
240            oos.defaultWriteObject();
241    
242            util.Debug.print("Finished writing SalesPoint: \"" + getName() + "\".", -1);
243        }
244    
245        /**
246         * Create a new SalesPoint.
247         *
248         * @param sName the name of the SalesPoint.
249         */
250        public SalesPoint(String sName) {
251            super();
252    
253            m_sName = sName;
254        }
255    
256        /**
257         * Return the name of this SalesPoint.
258         *
259         * @override Never
260         */
261        public String getName() {
262            return m_sName;
263        }
264    
265        /**
266         * Return the ID of this SalesPoint;
267         */
268        public int getID() {
269            return m_iID;
270        }
271    
272        /**
273         * Computes a new ID for this SalesPoint.<br/>
274         *
275         * @param points The SalesPoints to be taken into consideration when computing a unique ID.
276         *
277         * @override never
278         */
279        public void createNewID(Collection<SalesPoint> points) {
280            boolean found = false;
281            do {
282                int i = new Double(Math.random() * 1000000000).intValue();
283                if (points == null) {
284                    m_iID = i;
285                    found = true;
286                    break;
287                }
288                Iterator<SalesPoint> it = points.iterator();
289    
290                int spid = -1;
291                while (it.hasNext() && spid != i) {
292                    Object o = it.next();
293                    if (o instanceof SalesPoint) {
294                        spid = ((SalesPoint)o).getID();
295                    }
296                }
297                //iterated over all points, but id was not found -> use it, it is unique
298                if (spid != i) {
299                    m_iID = i;
300                    found = true;
301                }
302                //id already used, try again
303            } while (!found);
304        }
305    
306    
307        /**
308         * Check whether this SalesPoint can be closed. Unless the SalesPoint has been
309         * {@link #suspend suspended} before calling <code>canQuit</code> the result of this method may not
310         * be reliable.
311         *
312         * <p>By default, if a process runs on the SalesPoint its {@link SaleProcess#canQuit canQuit}
313         * method is called. If no process is running, and <code>fNoPersistence</code> is <code>true</code>,
314         * {@link #onCanQuit} is called which opens a MsgForm to ask the user whether he/she really wants to close
315         * the SalesPoint. If <code>fNoPersistence</code> is <code>false</code> and no process is running on the
316         * SalesPoint, the default implementation always returns <code>true</code>.
317         *
318         * <p>This method is usually not called directly.</p>
319         *
320         * @override Sometimes See above for an description of the default implementation.
321         *
322         * @param fNoPersistence <code>true</code> if the call to <code>canQuit</code> resulted from an
323         * explicit call to {@link #quit} or from a call to {@link Shop#shutdown Shop.shutdown (false)}. If
324         * <code>fNoPersistence == false</code> you can assume, the state of the SalesPoint will be made persistent
325         * before it is closed.
326         */
327        protected boolean canQuit(boolean fNoPersistence) {
328            synchronized (getProcessLock()) {
329                if (m_pCurProcess != null) {
330                    return m_pCurProcess.canQuit(fNoPersistence);
331                } else {
332                    if (fNoPersistence) {
333                        return onCanQuit();
334                    } else {
335                        return true;
336                    }
337                }
338            }
339        }
340    
341        /**
342         * Hook method called to determine whether a SalesPoint with no process running on it can be closed by an
343         * explicit quit call. In this method you can assume that the state of the SalesPoint will not be saved and
344         * therefore will not be restoreable.
345         *
346         * @override Sometimes The default implementation opens a {@link sale.stdforms.MsgForm} asking the user if
347         * they really want to close the SalesPoint.
348         *
349         * @return true if the SalesPoint can be closed, false otherwise.
350         */
351        protected boolean onCanQuit() {
352            JDisplayDialog jddConfirmer = new JDisplayDialog();
353    
354            final boolean[] abResult = {
355                    true};
356            final sale.stdforms.MsgForm mf = new sale.stdforms.MsgForm("Closing \"" + getName() + "\"",
357                    "Are you sure, you want to close this SalesPoint?");
358            mf.removeAllButtons();
359            mf.addButton("Yes", 0, new sale.Action() {
360                            private static final long serialVersionUID = -2170479158438829524L;
361                            public void doAction(SaleProcess p, SalesPoint sp) {
362                    mf.close();
363                }
364            });
365    
366            mf.addButton("No", 1, new sale.Action() {
367                            private static final long serialVersionUID = -1797203559731519531L;
368                            public void doAction(SaleProcess p, SalesPoint sp) {
369                    abResult[0] = false;
370                    mf.close();
371                }
372            });
373    
374            jddConfirmer.setVisible(true);
375            try {
376                jddConfirmer.setFormSheet(mf);
377            }
378            catch (InterruptedException ie) {
379                return false;
380            }
381    
382            return abResult[0];
383        }
384    
385        /**
386         * Close the SalesPoint.
387         *
388         * <p>First {@link #suspend suspends} the SalesPoint, then calls {@link #canQuit}. If that returns
389         * <code>false</code>, <code>quit</code> will {@link #resume} the SalesPoint and return. Otherwise,
390         * it {@link SaleProcess#quit quits} the current process, if any, and
391         * {@link Shop#removeSalesPoint removes the SalesPoint from the Shop}.</p>
392         *
393         * @override Never
394         */
395        public void quit() {
396            SaleProcess p = null;
397    
398            synchronized (getCloseLock()) {
399                if (!m_fIsClosing) {
400                    m_fIsClosing = true;
401    
402                    synchronized (getProcessLock()) {
403                        try {
404                            suspend();
405                        }
406                        catch (InterruptedException e) {}
407    
408                        if (!canQuit(true)) {
409                            resume();
410                            m_fIsClosing = false;
411                            return;
412                        }
413    
414                        p = m_pCurProcess;
415                        m_pCurProcess = null;
416                    }
417    
418                    if (p != null) {
419                        try {
420                            // This will resume the process and block until it is finished.
421                            p.quit(true);
422                        }
423                        catch (InterruptedException e) {}
424                    }
425    
426                    Shop.getTheShop().removeSalesPoint(this);
427                }
428            }
429        }
430    
431        // logging
432        /**
433         * Hook method called when the SalesPoint is added to a Shop. You can write a log entry here.
434         *
435         * @override Sometimes The default implementation does nothing.
436         *
437         * @see Shop#addSalesPoint
438         * @see Log
439         * @see LogEntry
440         */
441        protected void logSalesPointOpened() {}
442    
443        /**
444         * Hook method called when the SalesPoint is removed from a Shop. You can write a log entry here.
445         *
446         * @override Sometimes The default implementation does nothing.
447         *
448         * @see Shop#removeSalesPoint
449         * @see Log
450         * @see LogEntry
451         */
452        protected void logSalesPointClosed() {}
453    
454        // Process management
455    
456        /**
457         * Starts a process on this SalesPoint.
458         *
459         * <p>The process will run in the context of this SalesPoint, and with the
460         * DataBasket attached to this SalesPoint.</p>
461         *
462         * <p>If there is already a process running on this SalesPoint it will be suspended until
463         * the new process has finished.</p>
464         *
465         * @override Never
466         *
467         * @param p the process to be run.
468         */
469        public final void runProcess(SaleProcess p) {
470            runProcess(p, getBasket());
471        }
472    
473    
474        /**
475         * Starts a process on this SalesPoint.
476         *
477         * <p>The process will run in the context of this SalesPoint, and with the
478         * DataBasket attached to this SalesPoint.</p>
479         *
480         * <p>If there is already a process running on this SalesPoint it will be suspended until
481         * the new process has finished.</p>
482         *
483         * @override Never
484         *
485         * @param p the process to be run.
486         * @param db the DataBasket to be attached to the new process
487         */
488        public final void runProcess(SaleProcess p, DataBasket db) {
489            synchronized (getProcessLock()) {
490                if (!m_fSuspended) {
491                    try {
492                        if (m_pCurProcess != null) {
493                            m_pCurProcess.suspend();
494                        }
495                    }
496                    catch (InterruptedException ex) {
497                    }
498                    m_stkProcess.push(m_pCurProcess);
499                    m_pCurProcess = p;
500                    p.attach((ProcessContext)this);
501                    p.attach(db);
502                    p.start();
503                }
504            }
505    
506        }
507    
508        /**
509         * Get the process currently running on this SalesPoint, if any.
510         *
511         * @override Never
512         */
513        public final SaleProcess getCurrentProcess() {
514            synchronized (getProcessLock()) {
515                return m_pCurProcess;
516            }
517        }
518    
519        /**
520         * Suspend the SalesPoint.
521         *
522         * <p>If a process is running on the SalesPoint, it is {@link SaleProcess#suspend suspended}.
523         * The method will only return when the SalesPoint has been properly suspended.</p>
524         *
525         * @override Never
526         *
527         * @exception InterruptedException if an interrupt occurred while waiting for the SalesPoint to be
528         * suspended.
529         */
530        public void suspend() throws InterruptedException {
531            synchronized (getProcessLock()) {
532    
533                m_fSuspended = true;
534    
535                if (m_pCurProcess != null) {
536                    m_pCurProcess.suspend();
537                }
538            }
539        }
540    
541        /**
542         * Resume the SalesPoint.
543         *
544         * <p>If a process is running on the SalesPoint, it is {@link SaleProcess#resume resumed}.</p>
545         *
546         * @override Never
547         */
548        public void resume() {
549            synchronized (getProcessLock()) {
550    
551                if (m_pCurProcess != null) {
552                    m_pCurProcess.resume();
553                }
554    
555                m_fSuspended = false;
556            }
557        }
558    
559        // User management
560        /**
561         * Attach a user to this SalesPoint.
562         *
563         * <p>The user attached to a SalesPoint can be accessed by processes running
564         * on the SalesPoint an can be used to determine capabilities etc.</p>
565         *
566         * @override Never
567         *
568         * @param usr the user to be attached.
569         * @return the user that was previously attached to this SalesPoint, if any.
570         */
571        public User attach(User usr) {
572            synchronized (getUserLock()) {
573                User usrOld = m_usrUser;
574    
575                m_usrUser = usr;
576    
577                return usrOld;
578            }
579        }
580    
581        /**
582         * Detach any user currently attached to this SalesPoint.
583         *
584         * @override Never
585         *
586         * @return the user that was previously attached to this SalesPoint, if any.
587         */
588        public User detachUser() {
589            return attach((User)null);
590        }
591    
592        /**
593         * Get the user currently attached to this SalesPoint.
594         *
595         * @override Never
596         *
597         * @return the user currently attached to this SalesPoint, if any.
598         */
599        public User getUser() {
600            synchronized (getUserLock()) {
601                return m_usrUser;
602            }
603        }
604    
605        // DataBasket management
606        /**
607         * Attach a DataBasket to this SalesPoint.
608         *
609         * @override Never
610         *
611         * @param db the DataBasket to be attached.
612         * @return the DataBasket that was previously attached to this SalesPoint, if any.
613         */
614        public DataBasket attach(DataBasket db) {
615            synchronized (getBasketLock()) {
616                DataBasket dbOld = m_dbBasket;
617    
618                m_dbBasket = db;
619    
620                return dbOld;
621            }
622        }
623    
624        /**
625         * Detach any DataBasket currently attached to this SalesPoint.
626         *
627         * @override Never
628         *
629         * @return the DataBasket that was previously attached to this SalesPoint, if any.
630         */
631        public DataBasket detachBasket() {
632            return attach((DataBasket)null);
633        }
634    
635        /**
636         * Get the DataBasket currently attached to this SalesPoint.
637         *
638         * @override Never
639         *
640         * @return the DataBasket currently attached to this SalesPoint, if any.
641         */
642        public DataBasket getBasket() {
643            synchronized (getBasketLock()) {
644                return m_dbBasket;
645            }
646        }
647    
648        // display management
649        /**
650         * Attach a status display to the SalesPoint.
651         *
652         * <p>This display can be used to give status information about the SalesPoint. It can also
653         * be used to trigger background processes for the SalesPoint. It should not be used to trigger
654         * processes on the SalesPoint.</p>
655         *
656         * <p>If the given display is not <code>null</code>, it must be {@link Display#isUseableDisplay useable}.</p>
657         *
658         * @override Never
659         *
660         * @param dStatus the new status display for this SalesPoint.
661         *
662         * @return the previous status display, if any.
663         */
664        protected Display attachStatusDisplay(Display dStatus) {
665            synchronized (getStatusDisplayLock()) {
666                if (m_dStatus != null) {
667                    m_dStatus.closeFormSheet();
668                    m_dStatus.setMenuSheet(null);
669                }
670    
671                Display dReturn = m_dStatus;
672                m_dStatus = dStatus;
673    
674                setStatusFormSheet(getDefaultStatusFormSheet());
675                setStatusMenuSheet(getDefaultStatusMenuSheet());
676    
677                return dReturn;
678            }
679        }
680    
681        /**
682         * Detach the current status display.
683         *
684         * @override Never
685         *
686         * @return the previous status display, if any.
687         */
688        protected Display detachStatusDisplay() {
689            return attachStatusDisplay(null);
690        }
691    
692        /**
693         * Set a FormSheet in the SalesPoint's status display.
694         *
695         * <p>Status display FormSheet's are always nonmodal, which is why this method returns
696         * immediately after setting the FormSheet and can never throw an InterruptedException.</p>
697         *
698         * @override Never
699         *
700         * @param fs the FormSheet to be set.
701         */
702        protected void setStatusFormSheet(FormSheet fs) {
703            synchronized (getStatusDisplayLock()) {
704                if (m_dStatus == null) {
705                    return;
706                }
707    
708                if (fs != null) {
709                    fs.setWaitResponse(false);
710                    fs.attach(this);
711                }
712    
713                try {
714                    m_dStatus.setFormSheet(fs);
715                }
716                catch (InterruptedException e) {}
717            }
718        }
719    
720        /**
721         * Set a MenuSheet in the SalesPoint's status display.
722         *
723         * @override Never
724         *
725         * @param ms the MenuSheet to be set.
726         */
727        protected void setStatusMenuSheet(MenuSheet ms) {
728            synchronized (getStatusDisplayLock()) {
729                if (m_dStatus == null) {
730                    return;
731                }
732    
733                if (ms != null) {
734                    ms.attach(this);
735                }
736    
737                m_dStatus.setMenuSheet(ms);
738            }
739        }
740    
741        /**
742         * Get the default status MenuSheet for this SalesPoint.
743         *
744         * <p>Unless you specify differently through an explicit call to {@link #setStatusMenuSheet}, the Framework
745         * will use this MenuSheet for the SalesPoint's status display.</p>
746         *
747         * @override Sometimes The default implementation returns <code>null</code> to indicate no MenuSheet.
748         *
749         * @see #attachStatusDisplay
750         */
751        protected MenuSheet getDefaultStatusMenuSheet() {
752            return null;
753        }
754    
755        /**
756         * Get the default status FormSheet for this SalesPoint.
757         *
758         * <p>Unless you specify differently through an explicit call to {@link #setStatusFormSheet}, the Framework
759         * will use this FormSheet for the SalesPoint's status display.</p>
760         *
761         * @override Sometimes The default implementation returns an empty FormSheet.
762         *
763         * @see #attachStatusDisplay
764         */
765        protected FormSheet getDefaultStatusFormSheet() {
766    
767            FormSheetContentCreator fscc = new FormSheetContentCreator() {
768                            private static final long serialVersionUID = 2456872618340060101L;
769                            protected void createFormSheetContent(final FormSheet fs) {
770    
771                    fs.setComponent(new JPanel());
772    
773                    fs.removeAllButtons();
774                }
775            };
776    
777            return new FormSheet(getName(), fscc, false);
778        }
779    
780        /**
781         * Internal communication method called by Shop to attach a display during restoration of the Shop
782         * from a stream.
783         *
784         * @override Never
785         *
786         * @param d the display just loaded.
787         */
788        public void attachLoadedDisplay(Display d) {
789            synchronized (getDisplayLock()) {
790                if (hasUseableDisplay(null)) {
791                    detachDisplay();
792                }
793    
794                m_dDisplay = d;
795            }
796        }
797    
798        /**
799         * Attach a new display to the SalesPoint.
800         *
801         * <p>Any Form- or MenuSheets displayed on the current display will be removed, and the SalesPoint's
802         * default sheets will be set on the new display.</p>
803         *
804         * @override Never
805         *
806         * @param d the new display
807         *
808         * @return the previously attached display, if any.
809         *
810         * @see #getDefaultFormSheet
811         * @see #getDefaultMenuSheet
812         */
813        public Display attach(Display d, boolean fSetDefaultSheets) {
814            synchronized (getDisplayLock()) {
815                if (hasUseableDisplay(null)) {
816                    m_dDisplay.removeFormSheetListener(this);
817                    if (fSetDefaultSheets) {
818                        try {
819                            m_dDisplay.setFormSheet(null);
820                        }
821                        catch (InterruptedException e) {}
822                        m_dDisplay.setMenuSheet(null);
823                    }
824                }
825    
826                Display dOld = m_dDisplay;
827    
828                m_dDisplay = d;
829    
830                // We can't use the previous FormSheet on the new display, as it will have been cancelled by the
831                // setFormSheet call.
832                if (hasUseableDisplay(null)) {
833                    m_dDisplay.addFormSheetListener(this);
834                    if (fSetDefaultSheets) {
835                        setDefaultSheets();
836                    }
837                }
838    
839                return dOld;
840            }
841        }
842    
843        public Display attach(Display d) {
844            return attach(d, true);
845        }
846    
847    
848        /**
849         * Detach the current display.
850         *
851         * <p>Any Form- or MenuSheet on the current display will be removed before detaching the display.</p>
852         *
853         * @override Never
854         *
855         * @return the detached display, if any.
856         */
857        public Display detachDisplay() {
858            return attach((Display)null);
859        }
860    
861        /**
862         * Return the display of this SalesPoint.
863         *
864         * @return the display of this SalesPoint.
865         */
866        public Display getDisplay() {
867            return m_dDisplay;
868        }
869    
870        /**
871         * Set the default Form- and MenuSheet on the currently attached display.
872         *
873         * @override Never
874         *
875         * @see #getDefaultMenuSheet
876         * @see #getDefaultFormSheet
877         */
878        private void setDefaultSheets() {
879            synchronized (getDisplayLock()) {
880                if (hasUseableDisplay(null)) {
881                    FormSheet fs = getDefaultFormSheet();
882    
883                    if (fs != null) {
884                        fs.setWaitResponse(false);
885                        fs.attach(this);
886                    }
887    
888                    try {
889                        m_dDisplay.setFormSheet(fs);
890                    }
891                    catch (InterruptedException e) {}
892    
893                    MenuSheet ms = getDefaultMenuSheet();
894    
895                    if (ms != null) {
896                        ms.attach(this);
897                    }
898    
899                    m_dDisplay.setMenuSheet(ms);
900                }
901            }
902        }
903    
904        /**
905         * Get the default FormSheet for this SalesPoint.
906         *
907         * <p>The default FormSheet will be displayed whenever there is a current user (and, thus, a display),
908         * but no process is running and no other FormSheet is being displayed.</p>
909         *
910         * @override Always The default implementation returns a FormSheet that simply states the name of the
911         * SalesPoint.
912         *
913         * @return the default FormSheet, if any.
914         */
915        protected FormSheet getDefaultFormSheet() {
916            return new sale.stdforms.MsgForm(getName(), "This is the default FormSheet of SalesPoint " + getName());
917        }
918    
919        /**
920         * Get the default MenuSheet for this SalesPoint.
921         *
922         * <p>The default MenuSheet will be displayed whenever there is a current user (and, thus, a display),
923         * but no process is running.</p>
924         *
925         * @override Always The default implementation returns <code>null</code> indicating no MenuSheet.
926         *
927         * @return the default MenuSheet, if any.
928         */
929        protected MenuSheet getDefaultMenuSheet() {
930            return null;
931        }
932    
933        // ProcessContext methods
934        /**
935         * Allow a process to set a FormSheet on the SalesPoint's current display.
936         *
937         * <p>The process launching the FormSheet as well as this SalesPoint will be attached to the
938         * FormSheet prior to displaying it. Thus, Actions triggered by the FormSheet will run in the
939         * correct context and will be able to access the process and the SalesPoint.</p>
940         *
941         * <p>If the FormSheet requests that the Framework wait for it being closed,
942         * <code>setFormSheet()</code> will block until the FormSheet was closed or an interrupt occured.</p>
943         *
944         * @override Never
945         *
946         * @param p the process that wants to display the FormSheet.
947         * @param fs the FormSheet to be displayed.
948         *
949         * @exception InterruptedException if an interrupt occurred while waiting for the FormSheet to be
950         * closed.
951         *
952         * @see sale.Action
953         * @see FormSheet#waitResponse
954         */
955        public void setFormSheet(SaleProcess p, FormSheet fs) throws InterruptedException {
956    
957            if (fs != null) {
958                fs.attach(p);
959                fs.attach(this);
960            }
961    
962            Display d;
963            synchronized (getDisplayLock()) {
964                d = m_dDisplay;
965            }
966    
967            // not in synchronized, as this call might block, and we don't want dead locks.
968            d.setFormSheet(fs);
969        }
970    
971        /**
972         * Sets the Framebounds of the SalesPoints assoziated Display (JDisplayFrame).
973         *
974         * <p>Example:<p>
975         * <code>sp.setSalesPointFrameBounds (new Rectangle (10,10,200,200));<code>
976         *
977         * This moves the SalesPointFrame to Position (10,10) with a size of (200,200).
978         *
979         * @override Sometimes
980         */
981        public void setSalesPointFrameBounds(Rectangle r) {
982            if (getDisplay() == null) {
983                m_rSalesPointFrameBounds = r;
984            } else {
985                m_rSalesPointFrameBounds = r;
986                if (Shop.getTheShop() != null) {
987                    if (Shop.getTheShop().getShopState() == Shop.RUNNING) {
988                        getDisplay().setBounds(r);
989    
990                    }
991                }
992            }
993        }
994    
995        /**
996         * Returns the Framebounds of the SalesPoints assoziated Display(JDisplayFrame).
997         *
998         * @override Sometimes
999         */
1000        public Rectangle getSalesPointFrameBounds() {
1001            return m_rSalesPointFrameBounds;
1002        }
1003    
1004        /**
1005         * Allow a process to pop up a FormSheet on the SalesPoint's current display.
1006         *
1007         * <p>The process launching the FormSheet as well as this SalesPoint will be attached to the
1008         * FormSheet prior to displaying it. Thus, Actions triggered by the FormSheet will run in the
1009         * correct context and will be able to access the process and the SalesPoint.</p>
1010         *
1011         * <p>If the FormSheet requests that the Framework wait for it being closed,
1012         * <code>popUpFormSheet</code> will block until the FormSheet was closed or an interrupt occured.</p>
1013         *
1014         * @override Never
1015         *
1016         * @param p the process that wants to display the FormSheet.
1017         * @param fs the FormSheet to be displayed.
1018         *
1019         * @exception InterruptedException if an interrupt occurred while waiting for the FormSheet to be
1020         * closed.
1021         *
1022         * @see sale.Action
1023         * @see FormSheet#waitResponse
1024         */
1025        public void popUpFormSheet(SaleProcess p, FormSheet fs) throws InterruptedException {
1026    
1027            if (fs != null) {
1028                fs.attach(p);
1029                fs.attach(this);
1030            }
1031    
1032            Display d;
1033            synchronized (getDisplayLock()) {
1034                d = m_dDisplay;
1035            }
1036    
1037            d.popUpFormSheet(fs);
1038        }
1039    
1040        /**
1041         * Allow a process to set a MenuSheet on the SalesPoint's current display.
1042         *
1043         * <p>The process setting the MenuSheet as well as this SalesPoint will be attached to the
1044         * MenuSheet prior to displaying it. Thus, Actions triggered by the MenuSheet will run in the
1045         * correct context and will be able to access the process and the SalesPoint.</p>
1046         *
1047         * @override Never
1048         *
1049         * @param p the process that wants to display the MenuSheet.
1050         * @param ms the MenuSheet to be displayed.
1051         *
1052         * @see sale.Action
1053         */
1054        public void setMenuSheet(SaleProcess p, MenuSheet ms) {
1055    
1056            if (ms != null) {
1057                ms.attach(p);
1058                ms.attach(this);
1059            }
1060    
1061            synchronized (getDisplayLock()) {
1062                m_dDisplay.setMenuSheet(ms);
1063            }
1064        }
1065    
1066        /**
1067         * True, if the SalesPoint currently has a display and this display is useable.
1068         *
1069         * @override Never
1070         *
1071         * @param p the process querying, unused.
1072         *
1073         * @see Display#isUseableDisplay
1074         */
1075        public boolean hasUseableDisplay(SaleProcess p) {
1076            synchronized (getDisplayLock()) {
1077                return ((m_dDisplay != null) && (m_dDisplay.isUseableDisplay()));
1078            }
1079        }
1080    
1081        /**
1082         * Log the given Loggable.
1083         *
1084         * <p>The given loggable object will be logged into the global log file unless you override this method.</p>
1085         *
1086         * @override Sometimes
1087         *
1088         * @exception IOException if an error occurs while trying to write the log data.
1089         *
1090         * @param p the SalesProcess demanding logging, unused.
1091         * @param la the object to be logged.
1092         */
1093        public void log(SaleProcess p, Loggable la) throws IOException {
1094            Shop.getTheShop().log(la);
1095        }
1096    
1097        /**
1098         * Get the current user for the given process. This is the user, if any,
1099         * previously attached with the {@link #attach(User)} method.
1100         *
1101         * @override Never
1102         *
1103         * @param p the process querying, unused.
1104         *
1105         * @return the current user
1106         *
1107         * @see #getUser
1108         */
1109        public final User getCurrentUser(SaleProcess p) {
1110            return getUser();
1111        }
1112    
1113        /**
1114         * Return a Stock for a given name.
1115         *
1116         * @override Sometimes
1117         *
1118         * @param sName the name of the Stock.
1119         */
1120        public Stock getStock(String sName) {
1121            return Shop.getTheShop().getStock(sName);
1122        }
1123    
1124        /**
1125         * Return a Catalog for a given name.
1126         *
1127         * @override Sometimes
1128         *
1129         * @param sName the name of the Catalog.
1130         */
1131        public Catalog getCatalog(String sName) {
1132            return Shop.getTheShop().getCatalog(sName);
1133        }
1134    
1135        /**
1136         * Notification that a process started on a SalesPoint.
1137         *
1138         * <p>This will remove all SalesPoint owned Form- and MenuSheets from the display, as to
1139         * make room for the process' sheets.</p>
1140         *
1141         * @override Never
1142         *
1143         * @param p the process that was just launched.
1144         */
1145        public void processStarted(SaleProcess p) {
1146            synchronized (getDisplayLock()) {
1147                if (hasUseableDisplay(null)) {
1148                    m_dDisplay.removeFormSheetListener(this);
1149    
1150                    try {
1151                        m_dDisplay.setFormSheet(null);
1152                    }
1153                    catch (InterruptedException e) {}
1154                    m_dDisplay.setMenuSheet(null);
1155                }
1156            }
1157        }
1158    
1159        /**
1160         * Notification that a process finished running on this SalesPoint.
1161         *
1162         * <p>This will restore a previously interrupted SaleProcess or the SalesPoint's default sheets if
1163         * there is no more pending process.</p>
1164         *
1165         * @override Never
1166         */
1167        public void processFinished(SaleProcess p) {
1168            synchronized (getProcessLock()) {
1169                //m_pCurProcess will be null if the stack has 0 or 1 entries
1170                if (m_stkProcess.size() > 0) {//normally not executed, just a check in case method was called directly
1171                    m_pCurProcess = (SaleProcess)m_stkProcess.pop();
1172                } else {
1173                    m_pCurProcess = null;
1174                }
1175                if (m_pCurProcess != null) {
1176                    m_pCurProcess.resume();
1177                } else {
1178                    synchronized (getDisplayLock()) {
1179                        if (hasUseableDisplay(null)) {
1180                            m_dDisplay.addFormSheetListener(this);
1181                            setDefaultSheets();
1182                        }
1183                    }
1184                }
1185            }
1186        }
1187    
1188        // FormSheetListener interface methods
1189        /**
1190         * Dummy interface to conform by the FormSheetListener interface.
1191         *
1192         * @override Never
1193         */
1194        public void formSheetSet(FormSheetEvent e) {
1195        }
1196    
1197        /**
1198         * Implemented to make sure there always is a FormSheet.
1199         *
1200         * @override Never
1201         */
1202        public void formSheetRemoved(FormSheetEvent e) {
1203            if (e.isExplicit()) {
1204                synchronized (getDisplayLock()) {
1205                    if (hasUseableDisplay(null)) {
1206                        FormSheet fs = getDefaultFormSheet();
1207    
1208                        if (fs != null) {
1209                            fs.setWaitResponse(false);
1210                            fs.attach(this);
1211                        }
1212    
1213                        try {
1214                            m_dDisplay.setFormSheet(fs);
1215                        }
1216                        catch (InterruptedException ex) {}
1217                    }
1218                }
1219            }
1220        }
1221    
1222        /**
1223         * Return a String description of this SalesPoint: the name.
1224         *
1225         * @override Sometimes
1226         */
1227        public String toString() {
1228            return getName();
1229        }
1230    
1231        /**
1232         * Put an object into the ProcessContext.
1233         * 
1234         * @override Never
1235         * 
1236         * @param sKey object's identifier
1237         * @param oData the data object
1238         * 
1239         */
1240        public void setProcessData(String sKey, Object oData) {
1241            Shop.getTheShop().setProcessData(sKey, oData);
1242        }
1243    
1244        /**
1245         * Get an object from the ProcessContext.
1246         * 
1247         * @override Never
1248         * 
1249         * @param sKey object's key
1250         * 
1251         * @return the object from ProcessContext
1252         */
1253        public Object getProcessData(String sKey) {
1254            return Shop.getTheShop().getProcessData(sKey);
1255        }
1256    
1257    }