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