components/visual-panels/core/src/java/vpanels/panel/com/oracle/solaris/vp/panel/swing/control/WizardControl.java
changeset 827 0944d8c0158b
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/components/visual-panels/core/src/java/vpanels/panel/com/oracle/solaris/vp/panel/swing/control/WizardControl.java	Thu May 24 04:16:47 2012 -0400
@@ -0,0 +1,415 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Copyright (c) 2009, 2012, Oracle and/or its affiliates. All rights reserved.
+ */
+
+package com.oracle.solaris.vp.panel.swing.control;
+
+import java.awt.Component;
+import java.util.*;
+import javax.swing.*;
+import javax.swing.event.*;
+import com.oracle.solaris.vp.panel.common.action.*;
+import com.oracle.solaris.vp.panel.common.control.*;
+import com.oracle.solaris.vp.panel.common.model.PanelDescriptor;
+import com.oracle.solaris.vp.panel.swing.view.WizardPanel;
+import com.oracle.solaris.vp.util.misc.Stackable;
+import com.oracle.solaris.vp.util.misc.finder.Finder;
+import com.oracle.solaris.vp.util.swing.GUIUtil;
+import com.oracle.solaris.vp.util.swing.event.ChangeListeners;
+
+/**
+ * Implements a simple linear wizard out of a list of other controls.
+ */
+public abstract class WizardControl<P extends PanelDescriptor>
+    extends SwingControl<P, WizardPanel> implements WizardStepSequence {
+
+    //
+    // Inner classes
+    //
+
+    private class WizardRootControl extends SwingControl<P, Component> {
+	//
+	// Instance data
+	//
+
+	private boolean successful;
+
+	//
+	// Constructors
+	//
+
+	public WizardRootControl(Stackable<Component> stack) {
+	    super(WizardControl.this.getId(), WizardControl.this.getName(),
+		WizardControl.this.getPanelDescriptor());
+
+	    children = WizardControl.this.children;
+	    setComponentStack(stack);
+	}
+
+	//
+	// Control methods
+	//
+
+	@Override
+	public void start(Navigator navigator, Map<String, String> parameters)
+	    throws NavigationAbortedException, InvalidParameterException,
+	    NavigationFailedException {
+
+	    successful = false;
+	    super.start(navigator, parameters);
+	}
+
+	@Override
+	public void stop(boolean isCancel) throws NavigationAbortedException {
+	    wizardStopped(successful);
+	}
+    };
+
+    //
+    // Instance data
+    //
+
+    private Navigator wNavigator;
+    private WizardRootControl rootControl;
+
+    @SuppressWarnings({"unchecked"})
+    private List<Navigable> roChildren =
+	(List<Navigable>)(List)Collections.unmodifiableList(children);
+
+    private ChangeListeners listeners = new ChangeListeners();
+    private ChangeEvent changeEvent = new ChangeEvent(this);
+
+    //
+    // Constructors
+    //
+
+    public WizardControl(String id, String name, P descriptor) {
+	super(id, name, descriptor);
+    }
+
+    //
+    // Control methods
+    //
+
+    @Override
+    public void start(Navigator navigator, Map<String, String> parameters)
+	throws NavigationAbortedException, InvalidParameterException,
+	NavigationFailedException {
+
+	// Create WizardPanel
+	super.start(navigator, parameters);
+
+	if (rootControl == null) {
+	    rootControl = new WizardRootControl(getComponentStack());
+	}
+
+	if (wNavigator == null) {
+	    wNavigator = new SwingNavigator();
+
+	    // Resize windows on each navigation
+	    wNavigator.addNavigationListener(new NavigationWindowResizer());
+
+	    wNavigator.addNavigationListener(
+		new NavigationListener() {
+		    @Override
+		    public void navigationStarted(NavigationStartEvent e) {
+		    }
+
+		    @Override
+		    public void navigationStopped(NavigationStopEvent e) {
+			GUIUtil.invokeAndWait(
+			    new Runnable() {
+				@Override
+				public void run() {
+				    fireChanged();
+				}
+			    });
+		    }
+		});
+	}
+
+	try {
+	    Navigable first = getFirstStep();
+	    if (first != null) {
+		// Go to the beginning of the wizard
+		wNavigator.goToAsyncAndWait(false, null, rootControl, first);
+	    }
+	} catch (InvalidAddressException impossible) {
+	} catch (EmptyNavigationStackException impossible) {
+	} catch (RootNavigableNotControlException impossible) {
+	}
+    }
+
+    @Override
+    public void stop(boolean isCancel) throws NavigationAbortedException {
+	if (rootControl.getRunningChild() != null && !confirmCancelWizard()) {
+	    throw new NavigationAbortedException();
+	}
+
+	try {
+	    // Make sure the wizard is stopped before we are
+	    cancelWizardSync();
+	} catch (NavigationException e) {
+	    throw new NavigationAbortedException(e);
+	}
+
+	super.stop(isCancel);
+    }
+
+    //
+    // SwingControl methods
+    //
+
+    @Override
+    protected WizardPanel createComponent() {
+	WizardPanel panel = createWizardPanel();
+
+	setComponentStack(panel.getComponentStack());
+
+	return panel;
+    }
+
+    //
+    // WizardStepSequence methods
+    //
+
+    @Override
+    public void addChangeListener(ChangeListener l) {
+	listeners.add(l);
+    }
+
+    @Override
+    public void cancelWizard() {
+	wNavigator.asyncExec(
+	    new Runnable() {
+		@Override
+		public void run() {
+		    try {
+			cancelWizardSync();
+		    } catch (NavigationException ignore) {
+		    }
+		}
+	    });
+    }
+
+    /**
+     * Default implementation that returns the running child {@link Control}'s
+     * index in the {@link #getSteps step list}.
+     */
+    @Override
+    public int getSelectedIndex() {
+	int index = -1;
+	Control control = rootControl.getRunningChild();
+	if (control != null) {
+	    index = getSteps().indexOf(control);
+	}
+	return index;
+    }
+
+    /**
+     * Default implementation that returns a live, unmodifiable copy of the
+     * child {@link Control} list.  Subclasses may wish to override this
+     * behavior.
+     */
+    @Override
+    public List<Navigable> getSteps() {
+	ensureChildrenCreated();
+	return roChildren;
+    }
+
+    @Override
+    public void goToStep(final int index) {
+	final int selected = getSelectedIndex();
+
+	if (selected == index) {
+	    return;
+	}
+
+	int max = selected + 1;
+	if (index < 0 || index > max) {
+	    throw new IndexOutOfBoundsException(
+		String.format("%d out of range (0 - %d)", index, max));
+	}
+
+	final List<Navigable> steps = getSteps();
+
+	wNavigator.asyncExec(
+	    new Runnable() {
+		@Override
+		public void run() {
+		    try {
+			// First, save current step
+			if (selected >= 0) {
+			    Control cur = rootControl.getRunningChild();
+			    if (index > selected) {
+				cur.getSaveAction().invoke();
+			    } else {
+				cur.getResetAction().invoke();
+			    }
+			}
+
+			// Last step?
+			if (index == steps.size()) {
+			    // Stop wizard
+			    rootControl.successful = true;
+			    wNavigator.goTo(false, null);
+			} else {
+			    Navigable next = steps.get(index);
+			    wNavigator.goTo(false, rootControl, next);
+			}
+
+		    // Thrown by invoke
+		    } catch (ActionAbortedException ignore) {
+		    } catch (ActionFailedException ignore) {
+
+		    // Thrown by goTo
+		    } catch (NavigationException ignore) {
+		    }
+		}
+	    });
+    }
+
+    @Override
+    public void removeChangeListener(ChangeListener l) {
+	listeners.add(l);
+    }
+
+    //
+    // WizardControl methods
+    //
+
+    protected WizardPanel createWizardPanel() {
+	return new WizardPanel(this);
+    }
+
+    protected void fireChanged() {
+	listeners.stateChanged(changeEvent);
+    }
+
+    /**
+     * Gets a custom message to display in the dialog that prompts the user to
+     * confirm cancellation of the wizard, when navigating away from this
+     * {@link WizardControl}.
+     * <p>
+     * This default implementation returns {@code null}.
+     *
+     * @return	    a {@code String}, or {@code null} to use the default message
+     */
+    protected String getCancelWizardMessage() {
+	return null;
+    }
+
+    public Navigator getWizardNavigator() {
+	return wNavigator;
+    }
+
+    /**
+     * Prompts the user to onfirm cancellation of the wizard.  This is done only
+     * when navigating away from this {@link WizardControl}; explicit cancelling
+     * (as when the user hits "Cancel") does NOT require confirmation.
+     *
+     * @return	    {@code true} if the operation was confirmed, {@code false}
+     *		    otherwise
+     */
+    protected boolean confirmCancelWizard() {
+	String message = getCancelWizardMessage();
+	if (message == null) {
+	    message = Finder.getString("wizard.cancel.confirm.message");
+	}
+
+	String title = Finder.getString("wizard.cancel.confirm.title");
+	int messageType = JOptionPane.WARNING_MESSAGE;
+	int optionType = JOptionPane.YES_NO_OPTION;
+	Icon icon = GUIUtil.getIcon(JOptionPane.WARNING_MESSAGE);
+
+	return JOptionPane.showConfirmDialog(getComponent(), message, title,
+	    optionType, messageType, icon) == JOptionPane.YES_OPTION;
+    }
+
+    /**
+     * Indicates that the wizard has completed.  Subclasses should override this
+     * method to navigate away (and thus close) this {@link Control}.
+     * <p/>
+     * This default implementation asynchronously attempts to navigate to the
+     * parent {@link Control} of this {@link WizardControl} in the navigation
+     * stack, ignoring any errors.
+     *
+     * @param	    successful
+     *		    {@code true} if the wizard completed normally, {@code false}
+     *		    if it was cancelled
+     */
+    public void wizardStopped(boolean successful) {
+	try {
+	    getNavigator().goToAsync(false, this, Navigator.PARENT_NAVIGABLE);
+	} catch (Throwable ignore) {
+	}
+    }
+
+    //
+    // Private methods
+    //
+
+    private Navigable getFirstStep() {
+	try {
+	    return getSteps().get(0);
+	} catch (IndexOutOfBoundsException e) {
+	}
+
+	// No steps in this wizard?
+	return null;
+    }
+
+    private void cancelWizardSync() throws NavigationAbortedException,
+	InvalidAddressException, MissingParameterException,
+        InvalidParameterException, NavigationFailedException,
+        EmptyNavigationStackException, RootNavigableNotControlException {
+
+	wNavigator.asyncExecAndWait(
+	    new NavRunnable() {
+		@Override
+		public void run() throws NavigationAbortedException,
+		    InvalidAddressException, MissingParameterException,
+		    InvalidParameterException, NavigationFailedException,
+                    EmptyNavigationStackException,
+                    RootNavigableNotControlException {
+
+		    try {
+			// First, reset current step
+			Control cur = rootControl.getRunningChild();
+			if (cur != null) {
+			    cur.getResetAction().invoke();
+			}
+
+			// Stop wizard
+			rootControl.successful = false;
+			wNavigator.goTo(true, null);
+
+		    // Thrown by invoke
+		    } catch (ActionException e) {
+			throw new NavigationAbortedException(e);
+		    }
+		}
+	    });
+    }
+}