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 }