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 }