1 /* |
|
2 * CDDL HEADER START |
|
3 * |
|
4 * The contents of this file are subject to the terms of the |
|
5 * Common Development and Distribution License (the "License"). |
|
6 * You may not use this file except in compliance with the License. |
|
7 * |
|
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE |
|
9 * or http://www.opensolaris.org/os/licensing. |
|
10 * See the License for the specific language governing permissions |
|
11 * and limitations under the License. |
|
12 * |
|
13 * When distributing Covered Code, include this CDDL HEADER in each |
|
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. |
|
15 * If applicable, add the following below this CDDL HEADER, with the |
|
16 * fields enclosed by brackets "[]" replaced with your own identifying |
|
17 * information: Portions Copyright [yyyy] [name of copyright owner] |
|
18 * |
|
19 * CDDL HEADER END |
|
20 */ |
|
21 |
|
22 /* |
|
23 * Copyright (c) 2009, 2012, Oracle and/or its affiliates. All rights reserved. |
|
24 */ |
|
25 |
|
26 package com.oracle.solaris.vp.panel.swing.control; |
|
27 |
|
28 import java.awt.Component; |
|
29 import java.util.*; |
|
30 import javax.swing.*; |
|
31 import javax.swing.event.*; |
|
32 import com.oracle.solaris.vp.panel.common.action.*; |
|
33 import com.oracle.solaris.vp.panel.common.control.*; |
|
34 import com.oracle.solaris.vp.panel.common.model.PanelDescriptor; |
|
35 import com.oracle.solaris.vp.panel.swing.view.WizardPanel; |
|
36 import com.oracle.solaris.vp.util.misc.Stackable; |
|
37 import com.oracle.solaris.vp.util.misc.finder.Finder; |
|
38 import com.oracle.solaris.vp.util.swing.GUIUtil; |
|
39 import com.oracle.solaris.vp.util.swing.event.ChangeListeners; |
|
40 |
|
41 /** |
|
42 * Implements a simple linear wizard out of a list of other controls. |
|
43 */ |
|
44 public abstract class WizardControl<P extends PanelDescriptor> |
|
45 extends SwingControl<P, WizardPanel> implements WizardStepSequence { |
|
46 |
|
47 // |
|
48 // Inner classes |
|
49 // |
|
50 |
|
51 private class WizardRootControl extends SwingControl<P, Component> { |
|
52 // |
|
53 // Instance data |
|
54 // |
|
55 |
|
56 private boolean successful; |
|
57 |
|
58 // |
|
59 // Constructors |
|
60 // |
|
61 |
|
62 public WizardRootControl(Stackable<Component> stack) { |
|
63 super(WizardControl.this.getId(), WizardControl.this.getName(), |
|
64 WizardControl.this.getPanelDescriptor()); |
|
65 |
|
66 children = WizardControl.this.children; |
|
67 setComponentStack(stack); |
|
68 } |
|
69 |
|
70 // |
|
71 // Control methods |
|
72 // |
|
73 |
|
74 @Override |
|
75 public void start(Navigator navigator, Map<String, String> parameters) |
|
76 throws NavigationAbortedException, InvalidParameterException, |
|
77 NavigationFailedException { |
|
78 |
|
79 successful = false; |
|
80 super.start(navigator, parameters); |
|
81 } |
|
82 |
|
83 @Override |
|
84 public void stop(boolean isCancel) throws NavigationAbortedException { |
|
85 wizardStopped(successful); |
|
86 } |
|
87 }; |
|
88 |
|
89 // |
|
90 // Instance data |
|
91 // |
|
92 |
|
93 private Navigator wNavigator; |
|
94 private WizardRootControl rootControl; |
|
95 |
|
96 @SuppressWarnings({"unchecked"}) |
|
97 private List<Navigable> roChildren = |
|
98 (List<Navigable>)(List)Collections.unmodifiableList(children); |
|
99 |
|
100 private ChangeListeners listeners = new ChangeListeners(); |
|
101 private ChangeEvent changeEvent = new ChangeEvent(this); |
|
102 |
|
103 // |
|
104 // Constructors |
|
105 // |
|
106 |
|
107 public WizardControl(String id, String name, P descriptor) { |
|
108 super(id, name, descriptor); |
|
109 } |
|
110 |
|
111 // |
|
112 // Control methods |
|
113 // |
|
114 |
|
115 @Override |
|
116 public void start(Navigator navigator, Map<String, String> parameters) |
|
117 throws NavigationAbortedException, InvalidParameterException, |
|
118 NavigationFailedException { |
|
119 |
|
120 // Create WizardPanel |
|
121 super.start(navigator, parameters); |
|
122 |
|
123 if (rootControl == null) { |
|
124 rootControl = new WizardRootControl(getComponentStack()); |
|
125 } |
|
126 |
|
127 if (wNavigator == null) { |
|
128 wNavigator = new SwingNavigator(); |
|
129 |
|
130 // Resize windows on each navigation |
|
131 wNavigator.addNavigationListener(new NavigationWindowResizer()); |
|
132 |
|
133 wNavigator.addNavigationListener( |
|
134 new NavigationListener() { |
|
135 @Override |
|
136 public void navigationStarted(NavigationStartEvent e) { |
|
137 } |
|
138 |
|
139 @Override |
|
140 public void navigationStopped(NavigationStopEvent e) { |
|
141 GUIUtil.invokeAndWait( |
|
142 new Runnable() { |
|
143 @Override |
|
144 public void run() { |
|
145 fireChanged(); |
|
146 } |
|
147 }); |
|
148 } |
|
149 }); |
|
150 } |
|
151 |
|
152 try { |
|
153 Navigable first = getFirstStep(); |
|
154 if (first != null) { |
|
155 // Go to the beginning of the wizard |
|
156 wNavigator.goToAsyncAndWait(false, null, rootControl, first); |
|
157 } |
|
158 } catch (InvalidAddressException impossible) { |
|
159 } catch (EmptyNavigationStackException impossible) { |
|
160 } catch (RootNavigableNotControlException impossible) { |
|
161 } |
|
162 } |
|
163 |
|
164 @Override |
|
165 public void stop(boolean isCancel) throws NavigationAbortedException { |
|
166 if (rootControl.getRunningChild() != null && !confirmCancelWizard()) { |
|
167 throw new NavigationAbortedException(); |
|
168 } |
|
169 |
|
170 try { |
|
171 // Make sure the wizard is stopped before we are |
|
172 cancelWizardSync(); |
|
173 } catch (NavigationException e) { |
|
174 throw new NavigationAbortedException(e); |
|
175 } |
|
176 |
|
177 super.stop(isCancel); |
|
178 } |
|
179 |
|
180 // |
|
181 // SwingControl methods |
|
182 // |
|
183 |
|
184 @Override |
|
185 protected WizardPanel createComponent() { |
|
186 WizardPanel panel = createWizardPanel(); |
|
187 |
|
188 setComponentStack(panel.getComponentStack()); |
|
189 |
|
190 return panel; |
|
191 } |
|
192 |
|
193 // |
|
194 // WizardStepSequence methods |
|
195 // |
|
196 |
|
197 @Override |
|
198 public void addChangeListener(ChangeListener l) { |
|
199 listeners.add(l); |
|
200 } |
|
201 |
|
202 @Override |
|
203 public void cancelWizard() { |
|
204 wNavigator.asyncExec( |
|
205 new Runnable() { |
|
206 @Override |
|
207 public void run() { |
|
208 try { |
|
209 cancelWizardSync(); |
|
210 } catch (NavigationException ignore) { |
|
211 } |
|
212 } |
|
213 }); |
|
214 } |
|
215 |
|
216 /** |
|
217 * Default implementation that returns the running child {@link Control}'s |
|
218 * index in the {@link #getSteps step list}. |
|
219 */ |
|
220 @Override |
|
221 public int getSelectedIndex() { |
|
222 int index = -1; |
|
223 Control control = rootControl.getRunningChild(); |
|
224 if (control != null) { |
|
225 index = getSteps().indexOf(control); |
|
226 } |
|
227 return index; |
|
228 } |
|
229 |
|
230 /** |
|
231 * Default implementation that returns a live, unmodifiable copy of the |
|
232 * child {@link Control} list. Subclasses may wish to override this |
|
233 * behavior. |
|
234 */ |
|
235 @Override |
|
236 public List<Navigable> getSteps() { |
|
237 ensureChildrenCreated(); |
|
238 return roChildren; |
|
239 } |
|
240 |
|
241 @Override |
|
242 public void goToStep(final int index) { |
|
243 final int selected = getSelectedIndex(); |
|
244 |
|
245 if (selected == index) { |
|
246 return; |
|
247 } |
|
248 |
|
249 int max = selected + 1; |
|
250 if (index < 0 || index > max) { |
|
251 throw new IndexOutOfBoundsException( |
|
252 String.format("%d out of range (0 - %d)", index, max)); |
|
253 } |
|
254 |
|
255 final List<Navigable> steps = getSteps(); |
|
256 |
|
257 wNavigator.asyncExec( |
|
258 new Runnable() { |
|
259 @Override |
|
260 public void run() { |
|
261 try { |
|
262 // First, save current step |
|
263 if (selected >= 0) { |
|
264 Control cur = rootControl.getRunningChild(); |
|
265 if (index > selected) { |
|
266 cur.getSaveAction().invoke(); |
|
267 } else { |
|
268 cur.getResetAction().invoke(); |
|
269 } |
|
270 } |
|
271 |
|
272 // Last step? |
|
273 if (index == steps.size()) { |
|
274 // Stop wizard |
|
275 rootControl.successful = true; |
|
276 wNavigator.goTo(false, null); |
|
277 } else { |
|
278 Navigable next = steps.get(index); |
|
279 wNavigator.goTo(false, rootControl, next); |
|
280 } |
|
281 |
|
282 // Thrown by invoke |
|
283 } catch (ActionAbortedException ignore) { |
|
284 } catch (ActionFailedException ignore) { |
|
285 |
|
286 // Thrown by goTo |
|
287 } catch (NavigationException ignore) { |
|
288 } |
|
289 } |
|
290 }); |
|
291 } |
|
292 |
|
293 @Override |
|
294 public void removeChangeListener(ChangeListener l) { |
|
295 listeners.add(l); |
|
296 } |
|
297 |
|
298 // |
|
299 // WizardControl methods |
|
300 // |
|
301 |
|
302 protected WizardPanel createWizardPanel() { |
|
303 return new WizardPanel(this); |
|
304 } |
|
305 |
|
306 protected void fireChanged() { |
|
307 listeners.stateChanged(changeEvent); |
|
308 } |
|
309 |
|
310 /** |
|
311 * Gets a custom message to display in the dialog that prompts the user to |
|
312 * confirm cancellation of the wizard, when navigating away from this |
|
313 * {@link WizardControl}. |
|
314 * <p> |
|
315 * This default implementation returns {@code null}. |
|
316 * |
|
317 * @return a {@code String}, or {@code null} to use the default message |
|
318 */ |
|
319 protected String getCancelWizardMessage() { |
|
320 return null; |
|
321 } |
|
322 |
|
323 public Navigator getWizardNavigator() { |
|
324 return wNavigator; |
|
325 } |
|
326 |
|
327 /** |
|
328 * Prompts the user to onfirm cancellation of the wizard. This is done only |
|
329 * when navigating away from this {@link WizardControl}; explicit cancelling |
|
330 * (as when the user hits "Cancel") does NOT require confirmation. |
|
331 * |
|
332 * @return {@code true} if the operation was confirmed, {@code false} |
|
333 * otherwise |
|
334 */ |
|
335 protected boolean confirmCancelWizard() { |
|
336 String message = getCancelWizardMessage(); |
|
337 if (message == null) { |
|
338 message = Finder.getString("wizard.cancel.confirm.message"); |
|
339 } |
|
340 |
|
341 String title = Finder.getString("wizard.cancel.confirm.title"); |
|
342 int messageType = JOptionPane.WARNING_MESSAGE; |
|
343 int optionType = JOptionPane.YES_NO_OPTION; |
|
344 Icon icon = GUIUtil.getIcon(JOptionPane.WARNING_MESSAGE); |
|
345 |
|
346 return JOptionPane.showConfirmDialog(getComponent(), message, title, |
|
347 optionType, messageType, icon) == JOptionPane.YES_OPTION; |
|
348 } |
|
349 |
|
350 /** |
|
351 * Indicates that the wizard has completed. Subclasses should override this |
|
352 * method to navigate away (and thus close) this {@link Control}. |
|
353 * <p/> |
|
354 * This default implementation asynchronously attempts to navigate to the |
|
355 * parent {@link Control} of this {@link WizardControl} in the navigation |
|
356 * stack, ignoring any errors. |
|
357 * |
|
358 * @param successful |
|
359 * {@code true} if the wizard completed normally, {@code false} |
|
360 * if it was cancelled |
|
361 */ |
|
362 public void wizardStopped(boolean successful) { |
|
363 try { |
|
364 getNavigator().goToAsync(false, this, Navigator.PARENT_NAVIGABLE); |
|
365 } catch (Throwable ignore) { |
|
366 } |
|
367 } |
|
368 |
|
369 // |
|
370 // Private methods |
|
371 // |
|
372 |
|
373 private Navigable getFirstStep() { |
|
374 try { |
|
375 return getSteps().get(0); |
|
376 } catch (IndexOutOfBoundsException e) { |
|
377 } |
|
378 |
|
379 // No steps in this wizard? |
|
380 return null; |
|
381 } |
|
382 |
|
383 private void cancelWizardSync() throws NavigationAbortedException, |
|
384 InvalidAddressException, MissingParameterException, |
|
385 InvalidParameterException, NavigationFailedException, |
|
386 EmptyNavigationStackException, RootNavigableNotControlException { |
|
387 |
|
388 wNavigator.asyncExecAndWait( |
|
389 new NavRunnable() { |
|
390 @Override |
|
391 public void run() throws NavigationAbortedException, |
|
392 InvalidAddressException, MissingParameterException, |
|
393 InvalidParameterException, NavigationFailedException, |
|
394 EmptyNavigationStackException, |
|
395 RootNavigableNotControlException { |
|
396 |
|
397 try { |
|
398 // First, reset current step |
|
399 Control cur = rootControl.getRunningChild(); |
|
400 if (cur != null) { |
|
401 cur.getResetAction().invoke(); |
|
402 } |
|
403 |
|
404 // Stop wizard |
|
405 rootControl.successful = false; |
|
406 wNavigator.goTo(true, null); |
|
407 |
|
408 // Thrown by invoke |
|
409 } catch (ActionException e) { |
|
410 throw new NavigationAbortedException(e); |
|
411 } |
|
412 } |
|
413 }); |
|
414 } |
|
415 } |
|