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                        /*
989                        // nicht sehr sch�n - vielmehr sollte hier die Schnittstelle von Display
990                        // erweitert werden, um unabh�ngig von der Art des Displays zu sein.
991                        // NOCH ZU �NDERN !!! - NICHT VERGESSEN
992                        ((JDisplayFrame)getDisplay()).setBounds(getSalesPointFrameBounds()); ((JDisplayFrame)
993                                getDisplay()).hide(); ((JDisplayFrame)getDisplay()).show();*/
994                        getDisplay().setBounds(r);
995    
996                    }
997                }
998            }
999        }
1000    
1001        /**
1002         * Returns the Framebounds of the SalesPoints assoziated Display(JDisplayFrame).
1003         *
1004         * @override Sometimes
1005         */
1006        public Rectangle getSalesPointFrameBounds() {
1007            return m_rSalesPointFrameBounds;
1008        }
1009    
1010        /**
1011         * Allow a process to pop up a FormSheet on the SalesPoint's current display.
1012         *
1013         * <p>The process launching the FormSheet as well as this SalesPoint will be attached to the
1014         * FormSheet prior to displaying it. Thus, Actions triggered by the FormSheet will run in the
1015         * correct context and will be able to access the process and the SalesPoint.</p>
1016         *
1017         * <p>If the FormSheet requests that the Framework wait for it being closed,
1018         * <code>popUpFormSheet</code> will block until the FormSheet was closed or an interrupt occured.</p>
1019         *
1020         * @override Never
1021         *
1022         * @param p the process that wants to display the FormSheet.
1023         * @param fs the FormSheet to be displayed.
1024         *
1025         * @exception InterruptedException if an interrupt occurred while waiting for the FormSheet to be
1026         * closed.
1027         *
1028         * @see sale.Action
1029         * @see FormSheet#waitResponse
1030         */
1031        public void popUpFormSheet(SaleProcess p, FormSheet fs) throws InterruptedException {
1032    
1033            if (fs != null) {
1034                fs.attach(p);
1035                fs.attach(this);
1036            }
1037    
1038            Display d;
1039            synchronized (getDisplayLock()) {
1040                d = m_dDisplay;
1041            }
1042    
1043            d.popUpFormSheet(fs);
1044        }
1045    
1046        /**
1047         * Allow a process to set a MenuSheet on the SalesPoint's current display.
1048         *
1049         * <p>The process setting the MenuSheet as well as this SalesPoint will be attached to the
1050         * MenuSheet prior to displaying it. Thus, Actions triggered by the MenuSheet will run in the
1051         * correct context and will be able to access the process and the SalesPoint.</p>
1052         *
1053         * @override Never
1054         *
1055         * @param p the process that wants to display the MenuSheet.
1056         * @param ms the MenuSheet to be displayed.
1057         *
1058         * @see sale.Action
1059         */
1060        public void setMenuSheet(SaleProcess p, MenuSheet ms) {
1061    
1062            if (ms != null) {
1063                ms.attach(p);
1064                ms.attach(this);
1065            }
1066    
1067            synchronized (getDisplayLock()) {
1068                m_dDisplay.setMenuSheet(ms);
1069            }
1070        }
1071    
1072        /**
1073         * True, if the SalesPoint currently has a display and this display is useable.
1074         *
1075         * @override Never
1076         *
1077         * @param p the process querying, unused.
1078         *
1079         * @see Display#isUseableDisplay
1080         */
1081        public boolean hasUseableDisplay(SaleProcess p) {
1082            synchronized (getDisplayLock()) {
1083                return ((m_dDisplay != null) && (m_dDisplay.isUseableDisplay()));
1084            }
1085        }
1086    
1087        /**
1088         * Log the given Loggable.
1089         *
1090         * <p>The given loggable object will be logged into the global log file unless you override this method.</p>
1091         *
1092         * @override Sometimes
1093         *
1094         * @exception IOException if an error occurs while trying to write the log data.
1095         *
1096         * @param p the SalesProcess demanding logging, unused.
1097         * @param la the object to be logged.
1098         */
1099        public void log(SaleProcess p, Loggable la) throws IOException {
1100            Shop.getTheShop().log(la);
1101        }
1102    
1103        /**
1104         * Get the current user for the given process. This is the user, if any,
1105         * previously attached with the {@link #attach(User)} method.
1106         *
1107         * @override Never
1108         *
1109         * @param p the process querying, unused.
1110         *
1111         * @return the current user
1112         *
1113         * @see #getUser
1114         */
1115        public final User getCurrentUser(SaleProcess p) {
1116            return getUser();
1117        }
1118    
1119        /**
1120         * Return a Stock for a given name.
1121         *
1122         * @override Sometimes
1123         *
1124         * @param sName the name of the Stock.
1125         */
1126        public Stock getStock(String sName) {
1127            return Shop.getTheShop().getStock(sName);
1128        }
1129    
1130        /**
1131         * Return a Catalog for a given name.
1132         *
1133         * @override Sometimes
1134         *
1135         * @param sName the name of the Catalog.
1136         */
1137        public Catalog getCatalog(String sName) {
1138            return Shop.getTheShop().getCatalog(sName);
1139        }
1140    
1141        /**
1142         * Notification that a process started on a SalesPoint.
1143         *
1144         * <p>This will remove all SalesPoint owned Form- and MenuSheets from the display, as to
1145         * make room for the process' sheets.</p>
1146         *
1147         * @override Never
1148         *
1149         * @param p the process that was just launched.
1150         */
1151        public void processStarted(SaleProcess p) {
1152            synchronized (getDisplayLock()) {
1153                if (hasUseableDisplay(null)) {
1154                    m_dDisplay.removeFormSheetListener(this);
1155    
1156                    try {
1157                        m_dDisplay.setFormSheet(null);
1158                    }
1159                    catch (InterruptedException e) {}
1160                    m_dDisplay.setMenuSheet(null);
1161                }
1162            }
1163        }
1164    
1165        /**
1166         * Notification that a process finished running on this SalesPoint.
1167         *
1168         * <p>This will restore a previously interrupted SaleProcess or the SalesPoint's default sheets if
1169         * there is no more pending process.</p>
1170         *
1171         * @override Never
1172         */
1173        public void processFinished(SaleProcess p) {
1174            synchronized (getProcessLock()) {
1175                //m_pCurProcess will be null if the stack has 0 or 1 entries
1176                if (m_stkProcess.size() > 0) {//normally not executed, just a check in case method was called directly
1177                    m_pCurProcess = (SaleProcess)m_stkProcess.pop();
1178                } else {
1179                    m_pCurProcess = null;
1180                }
1181                if (m_pCurProcess != null) {
1182                    m_pCurProcess.resume();
1183                } else {
1184                    synchronized (getDisplayLock()) {
1185                        if (hasUseableDisplay(null)) {
1186                            m_dDisplay.addFormSheetListener(this);
1187                            setDefaultSheets();
1188                        }
1189                    }
1190                }
1191            }
1192        }
1193    
1194        // FormSheetListener interface methods
1195        /**
1196         * Dummy interface to conform by the FormSheetListener interface.
1197         *
1198         * @override Never
1199         */
1200        public void formSheetSet(FormSheetEvent e) {
1201        }
1202    
1203        /**
1204         * Implemented to make sure there always is a FormSheet.
1205         *
1206         * @override Never
1207         */
1208        public void formSheetRemoved(FormSheetEvent e) {
1209            if (e.isExplicit()) {
1210                synchronized (getDisplayLock()) {
1211                    if (hasUseableDisplay(null)) {
1212                        FormSheet fs = getDefaultFormSheet();
1213    
1214                        if (fs != null) {
1215                            fs.setWaitResponse(false);
1216                            fs.attach(this);
1217                        }
1218    
1219                        try {
1220                            m_dDisplay.setFormSheet(fs);
1221                        }
1222                        catch (InterruptedException ex) {}
1223                    }
1224                }
1225            }
1226        }
1227    
1228        /**
1229         * Return a String description of this SalesPoint: the name.
1230         *
1231         * @override Sometimes
1232         */
1233        public String toString() {
1234            return getName();
1235        }
1236    
1237        /**
1238         * Put an object into the ProcessContext.
1239         * 
1240         * @override Never
1241         * 
1242         * @param sKey object's identifier
1243         * @param oData the data object
1244         * 
1245         */
1246        public void setProcessData(String sKey, Object oData) {
1247            Shop.getTheShop().setProcessData(sKey, oData);
1248        }
1249    
1250        /**
1251         * Get an object from the ProcessContext.
1252         * 
1253         * @override Never
1254         * 
1255         * @param sKey object's key
1256         * 
1257         * @return the object from ProcessContext
1258         */
1259        public Object getProcessData(String sKey) {
1260            return Shop.getTheShop().getProcessData(sKey);
1261        }
1262    
1263    }