View Javadoc

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);// modal
290 		// Set default size
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 		// check initial screen is not null
308 		if (initial == null) {
309 			return null;
310 		}
311 		createUI();
312 		return data;
313 	}
314 
315 	/*
316 	 * (non-Javadoc)
317 	 * 
318 	 * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
319 	 */
320 	public void actionPerformed(ActionEvent ae) {
321 		dialog.setCursor(new Cursor(Cursor.WAIT_CURSOR));
322 		try {
323 			// Previous required. Note that the prev button is enabled only if
324 			// the user can go previous
325 			if (ae.getActionCommand().equals("Prev")) { //$NON-NLS-1$
326 				setScreen(getPreviousScreen(current.getClass()));
327 			} else if (ae.getActionCommand().equals("Next")) { //$NON-NLS-1$
328 
329 				// call the deprecated function if the derived class has
330 				// implemented it
331 				// or call the onCancel function if it is implemented.
332 				// if neither is implemented in the derived class then call the
333 				// new function.
334 				// this use of reflection looks like a hack!
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 				// always call onNextImp in preference to the deprecated
349 				// function
350 				// if neither implemented, call the onNext
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")) { //$NON-NLS-1$
362 				// call the deprecated function if the derived class has
363 				// implemented it
364 				// or call the onCancel function if it is implemented.
365 				// if neither is implemented in the derived class then call the
366 				// new function.
367 				// this use of reflection looks like a hack!
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 				// always call onCancel in preference to the deprecated function
382 				// if neither implemented, call the onCancel
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")) { //$NON-NLS-1$
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 			// If the class is an clear point, we clean up all previous screens
416 			if (Arrays.asList(screenClass.getInterfaces()).contains(ClearPoint.class)) {
417 				clearScreens();
418 				screen = (Screen) screenClass.newInstance();
419 				screen.setWizard(this);
420 			}
421 			// otherwise, try to get a screen from buffer or create it if needed
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 		// Add a listener to resize left side image if wizard window is resized
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 		// can go previous if screen allow it and if the screen is not the first
548 		// one
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 		// if cancel is disabled, then don't call the onCancel function and
604 		// don't dispose
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 	 * (non-Javadoc)
617 	 * 
618 	 * @see java.awt.event.WindowListener#windowOpened(java.awt.event.WindowEvent)
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 	 * (non-Javadoc)
632 	 * 
633 	 * @see java.awt.event.WindowListener#windowClosed(java.awt.event.WindowEvent)
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 	 * (non-Javadoc)
644 	 * 
645 	 * @see java.awt.event.WindowListener#windowIconified(java.awt.event.WindowEvent)
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 	 * (non-Javadoc)
657 	 * 
658 	 * @see java.awt.event.WindowListener#windowDeiconified(java.awt.event.WindowEvent)
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 	 * (non-Javadoc)
669 	 * 
670 	 * @see java.awt.event.WindowListener#windowActivated(java.awt.event.WindowEvent)
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 	 * (non-Javadoc)
681 	 * 
682 	 * @see java.awt.event.WindowListener#windowDeactivated(java.awt.event.WindowEvent)
683 	 */
684 	public void windowDeactivated(WindowEvent windowEvent) {
685 	}
686 
687 	public boolean wasCancelled() {
688 		return bCancelled;
689 	}
690 
691 }