1 package org.qdwizard;
2
3 import java.awt.BorderLayout;
4 import java.awt.Color;
5 import java.awt.Cursor;
6 import java.awt.Frame;
7 import java.awt.Image;
8 import java.awt.event.ActionEvent;
9 import java.awt.event.ActionListener;
10 import java.awt.event.ComponentEvent;
11 import java.awt.event.ComponentListener;
12 import java.awt.event.WindowEvent;
13 import java.awt.event.WindowListener;
14 import java.lang.reflect.Method;
15 import java.util.Arrays;
16 import java.util.HashMap;
17 import java.util.Locale;
18 import java.util.Observable;
19 import java.util.Observer;
20
21 import javax.swing.BorderFactory;
22 import javax.swing.ImageIcon;
23 import javax.swing.JDialog;
24 import javax.swing.JLabel;
25 import javax.swing.JPanel;
26 import javax.swing.JScrollPane;
27 import javax.swing.WindowConstants;
28
29 /***
30 * A Wizard dialog displaying one to many screens
31 * <ul><li>Create a class that extends Wizard. You have to implement getPreviousScreen(), getNextScreen() and finish() abstract methods</li>
32 <li> Displaying the wizard:</li>
33 <pre>
34 MyWizard wizard = new MyWizard(String sName,Class initial,
35 ImageIcon icon,Frame parentWindow,
36 Locale locale,int iHSize,int iVSize);
37 wizard.show();</pre>
38 <li>finish() method implements actions to be done at the end of the wizard</li>
39 <li>getPreviousScreen() and getNextScreen() have to return previous or next screen class. Example:</li>
40 <pre>
41 public Class getNextScreen(Class screen) {
42 if (ActionSelectionPanel.class.equals(getCurrentScreen())){
43 String sAction = (String)data.get(KEY_ACTION);
44 if (ActionSelectionPanel.ACTION_CREATION.equals(sAction)){
45 return TypeSelectionPanel.class;
46 }
47 else if (ActionSelectionPanel.ACTION_DELETE.equals(sAction)){
48 return RemovePanel.class;
49 }
50 }
51 }
52 </pre>
53 </ul>
54 *
55 * @author Bertrand Florat
56 * @created 1 may 2006
57 */
58 public abstract class Wizard implements ActionListener, WindowListener, Observer {
59 /*** Wizard name */
60 String sName;
61
62 /*** Initial screen */
63 Class<Screen> initial;
64
65 /*** Current screen */
66 Screen current;
67
68 /*** Wizard left side icon */
69 ImageIcon icon;
70
71 /*** Wizard data */
72 public static HashMap<String, Object> data;
73
74 /*** Wizard header */
75 Header header;
76
77 /*** Wizard action Panel */
78 ActionsPanel actions;
79
80 /*** Wizard dialog */
81 JDialog dialog;
82
83 /*** Parent window */
84 Frame parentWindow;
85
86 /*** Locale */
87 Locale locale;
88
89 /*** Screens instance repository */
90 HashMap<Class, Screen> hmClassScreens = new HashMap<Class, Screen>(10);
91
92 /*** Default Wizard size */
93 protected static final int DEFAULT_H_SIZE = 700;
94
95 protected static final int DEFAULT_V_SIZE = 500;
96
97 protected static final int DEFAULT_H_LAYOUT_PADDING = 5;
98
99 protected static final int DEFAULT_V_LAYOUT_PADDING = 5;
100
101 /*** Was the Wizard Canceled? */
102 private boolean bCancelled;
103
104 /*** Layout Padding */
105 private int layoutHPadding = DEFAULT_H_LAYOUT_PADDING;
106
107 private int layoutVPadding = DEFAULT_V_LAYOUT_PADDING;
108
109 /***
110 * Wizard constructor
111 *
112 * @param sName
113 * Wizard name displayed in dialog title
114 * @param initial
115 * Initial screen class
116 * @param icon
117 * Wizard icon (null if no icon)
118 * @param backgroundImage
119 * background image
120 * @param parentWindow
121 * wizard parent window
122 * @param locale
123 * Wizard locale
124 * @param iHSize
125 * Horizontal size
126 * @param iVSize
127 * Vertical size
128 * @param iLayoutHPadding
129 * Horizontal layout padding
130 * @param iLayoutVPadding
131 * Vertical layout padding
132 */
133 @SuppressWarnings("unchecked")
134 public Wizard(String sName, Class initial, ImageIcon icon, Image backgroundImage,
135 Frame parentWindow, Locale locale, int iHSize, int iVSize, int iLayoutHPadding,
136 int iLayoutVPadding) {
137 bCancelled = false;
138 this.sName = sName;
139 this.initial = initial;
140 this.parentWindow = parentWindow;
141 if (locale != null) {
142 this.locale = locale;
143 } else {
144 this.locale = Locale.getDefault();
145 }
146 data = new HashMap<String, Object>(10);
147 this.icon = icon;
148 this.layoutHPadding = iLayoutHPadding;
149 this.layoutVPadding = iLayoutVPadding;
150 createUI();
151 header.setImage(backgroundImage);
152 setScreen(initial);
153 current.onEnter();
154 dialog.setSize(iHSize, iVSize);
155 }
156
157 /***
158 * Wizard constructor
159 *
160 * @param sName
161 * Wizard name displayed in dialog title
162 * @param initial
163 * Initial screen class
164 * @param icon
165 * Wizard icon (null if no icon)
166 * @param parentWindow
167 * wizard parent window
168 * @param locale
169 * Wizard locale
170 * @param iHSize
171 * Horizontal size
172 * @param iVSize
173 * Vertical size
174 */
175 @SuppressWarnings("unchecked")
176 public Wizard(String sName, Class initial, ImageIcon icon, Frame parentWindow, Locale locale,
177 int iHSize, int iVSize) {
178 bCancelled = false;
179 this.sName = sName;
180 this.initial = initial;
181 this.parentWindow = parentWindow;
182 if (locale != null) {
183 this.locale = locale;
184 } else {
185 this.locale = Locale.getDefault();
186 }
187 data = new HashMap<String, Object>(10);
188 this.icon = icon;
189 createUI();
190 setScreen(initial);
191 current.onEnter();
192 dialog.setSize(iHSize, iVSize);
193 }
194
195 /***
196 * Wizard constructor
197 *
198 * @param sName
199 * Wizard name displayed in dialog title
200 * @param initial
201 * Initial screen class
202 * @param icon
203 * Wizard icon (null if no icon)
204 * @param backgroundImage
205 * Wizard header background (null if no image)
206 * @param parentWindow
207 * wizard parent window
208 * @param locale
209 * Wizard locale
210 */
211 public Wizard(String sName, Class initial, ImageIcon icon, Image backgroundImage,
212 Frame parentWindow, Locale locale) {
213 this(sName, initial, icon, backgroundImage, parentWindow, locale, DEFAULT_H_SIZE,
214 DEFAULT_V_SIZE, DEFAULT_H_LAYOUT_PADDING, DEFAULT_V_LAYOUT_PADDING);
215 }
216
217 /***
218 * Wizard constructor
219 *
220 * @param sName
221 * Wizard name displayed in dialog title
222 * @param initial
223 * Initial screen class
224 * @param icon
225 * Wizard icon (null if no icon)
226 * @param parentWindow
227 * wizard parent window
228 * @param locale
229 * Wizard locale
230 */
231 public Wizard(String sName, Class initial, ImageIcon icon, Frame parentWindow, Locale locale) {
232 this(sName, initial, icon, null, parentWindow, locale, DEFAULT_H_SIZE, DEFAULT_V_SIZE,
233 DEFAULT_H_LAYOUT_PADDING, DEFAULT_V_LAYOUT_PADDING);
234 }
235
236 /***
237 * Wizard constructor (uses default locale)
238 *
239 * @param sName
240 * Wizard name displayed in dialog title
241 * @param initial
242 * Initial screen class
243 * @param icon
244 * Wizard icon (null if no icon)
245 * @param backgroundImage
246 * Wizard header background (null if no image)
247 * @param parentWindow
248 * wizard parent window
249 */
250 public Wizard(String sName, Class initial, ImageIcon icon, Image backgroundImage,
251 Frame parentWindow) {
252 this(sName, initial, icon, backgroundImage, parentWindow, Locale.getDefault());
253 }
254
255 /***
256 * Wizard constructor (uses default locale)
257 *
258 * @param sName
259 * Wizard name displayed in dialog title
260 * @param initial
261 * Initial screen class
262 * @param icon
263 * Wizard icon (null if no icon)
264 * @param parentWindow
265 * wizard parent window
266 */
267 public Wizard(String sName, Class initial, ImageIcon icon, Frame parentWindow) {
268 this(sName, initial, icon, null, parentWindow, Locale.getDefault());
269 }
270
271 public void show() {
272 dialog.setVisible(true);
273 }
274
275 /***
276 * access to the jdialog of the wizard, in case we need it (for instance to
277 * set a glasspane when waiting)
278 *
279 * @return the wizard dialog
280 */
281 public JDialog getDialog() {
282 return dialog;
283 }
284
285 /***
286 * UI manager
287 */
288 private void createUI() {
289 dialog = new JDialog(parentWindow, true);
290
291 dialog.setSize(DEFAULT_H_SIZE, DEFAULT_V_SIZE);
292 dialog.setTitle(sName);
293 header = new Header();
294 actions = new ActionsPanel(this, locale);
295 dialog.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
296 dialog.addWindowListener(this);
297 display();
298 dialog.setLocationRelativeTo(parentWindow);
299 }
300
301 /***
302 * Display the wizard
303 *
304 * @return wizard data
305 */
306 private HashMap showWizard() {
307
308 if (initial == null) {
309 return null;
310 }
311 createUI();
312 return data;
313 }
314
315
316
317
318
319
320 public void actionPerformed(ActionEvent ae) {
321 dialog.setCursor(new Cursor(Cursor.WAIT_CURSOR));
322 try {
323
324
325 if (ae.getActionCommand().equals("Prev")) {
326 setScreen(getPreviousScreen(current.getClass()));
327 } else if (ae.getActionCommand().equals("Next")) {
328
329
330
331
332
333
334
335 Method[] methods = getClass().getDeclaredMethods();
336 String strMethod;
337 boolean onLeaveImp = false;
338 boolean onNextImp = false;
339 for (int i = 0; i < methods.length; i++) {
340 strMethod = methods[i].toString();
341 if (!onLeaveImp && strMethod.startsWith("public void")
342 && strMethod.endsWith(".onLeave()"))
343 onLeaveImp = true;
344 if (!onNextImp && strMethod.startsWith("public boolean")
345 && strMethod.endsWith(".onNext()"))
346 onNextImp = true;
347 }
348
349
350
351 if (onNextImp || !(onNextImp || onLeaveImp)) {
352 if (current.onNext()) {
353 setScreen(getNextScreen(current.getClass()));
354 current.onEnter();
355 }
356 } else if (onLeaveImp) {
357 current.onLeave();
358 setScreen(getNextScreen(current.getClass()));
359 current.onEnter();
360 }
361 } else if (ae.getActionCommand().equals("Cancel")) {
362
363
364
365
366
367
368 Method[] methods = getClass().getDeclaredMethods();
369 String strMethod;
370 boolean cancelImp = false;
371 boolean onCancelImp = false;
372 for (int i = 0; i < methods.length; i++) {
373 strMethod = methods[i].toString();
374 if (!cancelImp && strMethod.startsWith("public void")
375 && strMethod.endsWith(".cancel()"))
376 cancelImp = true;
377 if (!onCancelImp && strMethod.startsWith("public boolean")
378 && strMethod.endsWith(".onCancel()"))
379 onCancelImp = true;
380 }
381
382
383 if (onCancelImp || !(onCancelImp || cancelImp)) {
384 if (onCancel()) {
385 current.onCancelled();
386 data.clear();
387 this.bCancelled = true;
388 dialog.dispose();
389 }
390 } else if (cancelImp) {
391 current.onCancelled();
392 data.clear();
393 bCancelled = true;
394 cancel();
395 dialog.dispose();
396 }
397 } else if (ae.getActionCommand().equals("Finish")) {
398 current.onFinished();
399 finish();
400 dialog.dispose();
401 }
402 } finally {
403 dialog.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
404 }
405 }
406
407 /***
408 * Set the screen to display a a class
409 *
410 * @param screenClass
411 */
412 private void setScreen(Class screenClass) {
413 Screen screen = null;
414 try {
415
416 if (Arrays.asList(screenClass.getInterfaces()).contains(ClearPoint.class)) {
417 clearScreens();
418 screen = (Screen) screenClass.newInstance();
419 screen.setWizard(this);
420 }
421
422 else {
423 if (!hmClassScreens.containsKey(screenClass)) {
424 screen = (Screen) screenClass.newInstance();
425 screen.setWizard(this);
426 hmClassScreens.put(screenClass, screen);
427 }
428 screen = hmClassScreens.get(screenClass);
429 }
430 screen.setStateObserver(this);
431 } catch (Exception e) {
432 e.printStackTrace();
433 throw new RuntimeException("setScreen " + screenClass + " caused " + e.toString(), e);
434 }
435 current = screen;
436 current.setCanGoPrevious((getPreviousScreen(screenClass) != null));
437 current.setCanGoNext((getNextScreen(screenClass) != null));
438 String sDesc = screen.getDescription();
439 if (sDesc != null) {
440 header.setTitleText(screen.getName());
441 header.setSubtitleText(sDesc);
442 } else {
443 header.setTitleText(screen.getName());
444 header.setSubtitleText("");
445 }
446 display();
447 }
448
449 /***
450 * Called at each screen refresh
451 */
452 private void display() {
453 ((JPanel) dialog.getContentPane()).removeAll();
454 dialog.setLayout(new BorderLayout(layoutHPadding, layoutVPadding));
455 final JLabel jl = new JLabel(icon);
456 dialog.add(jl, BorderLayout.WEST);
457
458 jl.addComponentListener(new ComponentListener() {
459
460 public void componentShown(ComponentEvent e) {
461 }
462
463 public void componentResized(ComponentEvent e) {
464 Wizard.this.icon = getResizedImage(icon, jl.getWidth(), jl.getHeight());
465 jl.setIcon(Wizard.this.icon);
466 }
467
468 public void componentMoved(ComponentEvent e) {
469 }
470
471 public void componentHidden(ComponentEvent e) {
472 }
473
474 });
475 dialog.add(actions, BorderLayout.SOUTH);
476 JScrollPane jsp = new JScrollPane(header);
477 jsp.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
478 jsp.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
479 jsp.setBorder(BorderFactory.createEmptyBorder());
480
481 dialog.add(jsp, BorderLayout.NORTH);
482 if (current != null) {
483 dialog.add(current, BorderLayout.CENTER);
484 }
485
486 dialog.getRootPane().setDefaultButton(actions.jbNext);
487 ((JPanel) dialog.getContentPane()).revalidate();
488 dialog.getContentPane().repaint();
489 }
490
491 /***
492 * Set the header image
493 *
494 * @param img
495 */
496 public void setHeaderImage(Image img) {
497 header.setImage(img);
498 }
499
500 /***
501 * Set the background color of the ActionPanel
502 *
503 * @param color
504 */
505 public void setActionsBackgroundColor(Color color) {
506 actions.setBackgroundColor(color);
507 }
508
509 /***
510 * Set the background color of the ActionPanel's Problem notification area
511 *
512 * @param color
513 */
514 public void setProblemBackgroundColor(Color color) {
515 actions.setProblemBackgroundColor(color);
516 }
517
518 /***
519 * @return previous screen class
520 */
521 abstract public Class getPreviousScreen(Class screen);
522
523 /***
524 * Clear screens history
525 */
526 public void clearScreens() {
527 hmClassScreens.clear();
528 }
529
530 /***
531 *
532 * @return next screen class
533 */
534 abstract public Class getNextScreen(Class screen);
535
536 /***
537 * Get curent screen
538 *
539 * @return current screen class
540 */
541 public Class getCurrentScreen() {
542 return this.current.getClass();
543 }
544
545 public void update(Observable o, Object arg) {
546 boolean isFirst = initial.getClass().equals(Screen.class);
547
548
549 boolean bPrevious = current.canGoPrevious();
550 boolean bNext = current.canGoNext();
551 boolean bFinish = current.canFinish();
552 boolean bCancel = current.canCancel();
553 actions.setState(bPrevious, bNext, bFinish, bCancel);
554 actions.setProblem(current.getProblem());
555 }
556
557 /***
558 * Finish action. Called when user clicks on "finish"
559 */
560 abstract public void finish();
561
562 /***
563 * Empty cancel action. Called when user clicks on "cancel". Override it if
564 * you want to do something in cancel.
565 *
566 * Deprecated: use onCancel()
567 */
568 @Deprecated
569 public void cancel() {
570 }
571
572 /***
573 * Called when user clicks on "cancel". Override it if you want to do
574 * something in cancel such as display a confirmation dialog.
575 * <p>
576 *
577 * @return return true if the Wizard should contine to close
578 * @return return false if the Wizard should abort the cancellation
579 */
580 public boolean onCancel() {
581 return true;
582 }
583
584 /***
585 * Icon resizing
586 *
587 * @param img
588 * @param iNewWidth
589 * @param iNewHeight
590 * @return resized icon
591 */
592 private static ImageIcon getResizedImage(ImageIcon img, int iNewWidth, int iNewHeight) {
593 if (img == null)
594 return null;
595 ImageIcon iiNew = new ImageIcon();
596 Image image = img.getImage();
597 Image scaleImg = image.getScaledInstance(iNewWidth, iNewHeight, Image.SCALE_AREA_AVERAGING);
598 iiNew.setImage(scaleImg);
599 return iiNew;
600 }
601
602 public void windowClosing(WindowEvent windowEvent) {
603
604
605 if (current.canCancel() && onCancel()) {
606 bCancelled = true;
607 dialog.dispose();
608 }
609 }
610
611 /***
612 * Called when the wizard dialog opens. Override it if you want notification
613 * of this event.
614 */
615
616
617
618
619
620 public void windowOpened(WindowEvent windowEvent) {
621 }
622
623 /***
624 * Called when the wizard dialog is closed. Override it if you want
625 * notification of this event.
626 * <p>
627 * <b>caution:</b> You must always call super.windowClosed(windowEvent)
628 * within the override function to ensure that the Wizard closes completely.
629 */
630
631
632
633
634
635 public void windowClosed(WindowEvent windowEvent) {
636 }
637
638 /***
639 * Called when the wizard dialog is iconified. Override it if you want
640 * notification of this event.
641 */
642
643
644
645
646
647 public void windowIconified(WindowEvent windowEvent) {
648 }
649
650 /***
651 * Called when the wizard dialog is deiconified. Override it if you want
652 * notification of this event.
653 *
654 */
655
656
657
658
659
660 public void windowDeiconified(WindowEvent windowEvent) {
661 }
662
663 /***
664 * Called when the wizard dialog is activated. Override it if you want
665 * notification of this event.
666 */
667
668
669
670
671
672 public void windowActivated(WindowEvent windowEvent) {
673 }
674
675 /***
676 * Called when the wizard dialog is deactivated. Override it if you want
677 * notification of this event.
678 */
679
680
681
682
683
684 public void windowDeactivated(WindowEvent windowEvent) {
685 }
686
687 public boolean wasCancelled() {
688 return bCancelled;
689 }
690
691 }