components/visual-panels/core/src/java/vpanels/panel/com/oracle/solaris/vp/panel/swing/control/SwingControl.java
author Dan Labrecque <dan.labrecque@oracle.com>
Thu, 24 May 2012 04:16:47 -0400
changeset 827 0944d8c0158b
permissions -rw-r--r--
7169052 Integrate Visual Panels into Userland

/*
 * 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) 2010, 2012, Oracle and/or its affiliates. All rights reserved.
 */

package com.oracle.solaris.vp.panel.swing.control;

import java.awt.*;
import java.beans.*;
import java.util.Map;
import javax.swing.*;
import com.oracle.solaris.vp.panel.common.ClientContext;
import com.oracle.solaris.vp.panel.common.action.*;
import com.oracle.solaris.vp.panel.common.control.*;
import com.oracle.solaris.vp.panel.common.model.*;
import com.oracle.solaris.vp.panel.swing.action.SwingStructuredAction;
import com.oracle.solaris.vp.util.misc.*;
import com.oracle.solaris.vp.util.misc.finder.Finder;
import com.oracle.solaris.vp.util.swing.*;

public class SwingControl<P extends PanelDescriptor, C extends Component>
    extends DefaultControl<P> implements HasComponent<C>,
    PropertyChangeListener {

    //
    // Instance data
    //

    private ManagedObject propSource;
    private String[] propNames;
    private boolean ignorePropChange;

    private C comp;
    private Stackable<Component> stack;

    private SwingStructuredAction<Object, Object, Object> resetAction =
	new SwingStructuredAction<Object, Object, Object>(null, null, null,
	    this) {

	    @Override
	    public Object workBusy(Object pInput, Object rtInput)
		throws ActionAbortedException, ActionFailedException,
		ActionUnauthorizedException {

		resetAll();
		return null;
	    }
	};

    private SwingStructuredAction<Object, Object, Object> saveAction =
	new SwingStructuredAction<Object, Object, Object>(null, null, null,
	    this) {

	    @Override
	    public Object workBusy(Object pInput, Object rtInput)
		throws ActionAbortedException, ActionFailedException,
		ActionUnauthorizedException {

		saveAll();
		return null;
	    }
	};

    //
    // Constructors
    //

    public SwingControl(String id, String name, ClientContext context) {
	super(id, name, context);
    }

    public SwingControl(String id, String name, P descriptor) {
	super(id, name, descriptor);
    }

    //
    // HasComponent methods
    //

    /**
     * Gets the {@code Component} for this {@code SwingControl}.
     *
     * @return	    a {@code Component}, or {@code null} if this {@code
     *		    SwingControl} has no {@code Component}.
     *
     * @see	    #setComponent
     */
    @Override
    public C getComponent() {
	return comp;
    }

    //
    // PropertyChangeListener methods
    //

    /**
     * Convenience implementation that calls {@link #initComponentOnEventThread}
     * if property changes are not temporarilty being {@link
     * #getPropertyChangeIgnore ignored}.  If this {@link SwingControl}'s
     * component displayed the properties of a {@link ManagedObject}, for
     * example, it may make sense to add this {@code SwingControl} as a {@code
     * PropertyListener} to that object.
     */
    @Override
    public void propertyChange(PropertyChangeEvent event) {
	if (!ignorePropChange && getComponent() != null) {
	    initComponentOnEventThread();
	}
    }

    //
    // Control methods
    //

    /**
     * If a) this {@code SwingControl} defines a {@link #getComponentStack
     * Component stack}, b) the just-started {@link Control} implements {@link
     * HasComponent}, and c) its {@code Component} does not yet have a parent,
     * this method adds that {@code Component} to the stack.
     */
    @Override
    public void descendantStarted(Control[] path) {
	super.descendantStarted(path);

	final Stackable<Component> stack = getComponentStack();
	if (stack != null) {
	    Control control = path[path.length - 1];
	    if (control instanceof HasComponent) {
		final Component comp = ((HasComponent)control).getComponent();
		if (comp != null && !(comp instanceof Window) &&
		    comp.getParent() == null) {

		    GUIUtil.invokeAndWait(
			new Runnable() {
			    @Override
			    public void run() {
				stack.push(comp);
			    }
			});
		}
	    }
	}
    }

    /**
     * If a) this {@code SwingControl} defines a {@link #getComponentStack
     * Component stack}, b) the just-stopped {@link Control} implements {@link
     * HasComponent}, and c) its {@code Component} is at the top of the stack,
     * this method removes that {@code Component} from the stack.
     */
    @Override
    public void descendantStopped(Control[] path) {
	super.descendantStopped(path);

	final Stackable<Component> stack = getComponentStack();
	if (stack != null) {
	    Control control = path[path.length - 1];
	    if (control instanceof HasComponent) {
		final Component comp = ((HasComponent)control).getComponent();
		if (comp != null && !(comp instanceof Window)) {
		    GUIUtil.invokeAndWait(
			new Runnable() {
			    @Override
			    public void run() {
				StackUtil.popIfAtTop(stack, comp);
			    }
			});
		}
	    }
	}
    }

    /**
     * Gets a {@link SwingStructuredAction} that invokes {@link #resetAll},
     * displaying errors.
     */
    @Override
    public SwingStructuredAction<?, ?, ?> getResetAction() {
	return resetAction;
    }

    /**
     * Gets a {@link SwingStructuredAction} that invokes {@link #saveAll},
     * displaying errors and prompting for login on {@code
     * ActionUnauthorizedException}s.
     */
    @Override
    public SwingStructuredAction<?, ?, ?> getSaveAction() {
	return saveAction;
    }

    /**
     * Prompts the user to save changes, discard changes, or cancel navigation,
     * when navigating away from a {@code Control} with unsaved changes.
     *
     * @see	    #getUnsavedChangesMessage
     */
    @Override
    protected UnsavedChangesAction getUnsavedChangesAction() {
	String message = getUnsavedChangesMessage();
	if (message == null) {
	    message = Finder.getString("settings.unsavedchanges.message");
	}

	String title = Finder.getString("settings.unsavedchanges.title");
	int messageType = JOptionPane.WARNING_MESSAGE;
	int optionType = JOptionPane.DEFAULT_OPTION;
	Icon icon = GUIUtil.getIcon(JOptionPane.WARNING_MESSAGE);

	Object[] options = {
	    Finder.getString("settings.unsavedchanges.button.cancel"),
	    Finder.getString("settings.unsavedchanges.button.discard"),
	    Finder.getString("settings.unsavedchanges.button.save"),
	};

	Object defaultOption = options[0];

	int result = JOptionPane.showOptionDialog(getComponent(), message,
	    title, optionType, messageType, icon, options, defaultOption);

	switch (result) {
	    default:
	    case 0: return UnsavedChangesAction.CANCEL;
	    case 1: return UnsavedChangesAction.DISCARD;
	    case 2: return UnsavedChangesAction.SAVE;
	}
    }

    /**
     * If this {@code SwingControl} has a {@link #getChangeableAggregator
     * ChangeableAggregator}, returns the value from the aggregator's {@link
     * ChangeableAggregator#isChanged isChanged} method.  Otherwise, returns
     * {@code false}.
     */
    @Override
    protected boolean isChanged() {
	ChangeableAggregator aggregator = getChangeableAggregator();
	if (aggregator != null) {
	    return aggregator.isChanged();
	}
	return false;
    }

    /**
     * If this {@code SwingControl} has a {@link #getChangeableAggregator
     * ChangeableAggregator}, calls its {@link ChangeableAggregator#reset}
     * method on the AWT event thread and waits for its completion.
     */
    @Override
    protected void reset() throws ActionAbortedException, ActionFailedException
    {
	final ChangeableAggregator aggregator = getChangeableAggregator();
	if (aggregator != null) {
	    GUIUtil.invokeAndWait(
		new Runnable() {
		    @Override
		    public void run() {
			aggregator.reset();
		    }
		});
	}
    }

    /**
     * If this {@code SwingControl} has a {@link #getChangeableAggregator
     * ChangeableAggregator}, calls its {@link ChangeableAggregator#save}
     * method on the AWT event thread and waits for its completion.
     */
    @Override
    protected void save() throws ActionAbortedException, ActionFailedException,
	ActionUnauthorizedException {

	final ChangeableAggregator aggregator = getChangeableAggregator();
	if (aggregator != null) {
	    GUIUtil.invokeAndWait(
		new Runnable() {
		    @Override
		    public void run() {
			aggregator.save();
		    }
		});
	}
    }

    /**
     * Calls the superclass implementation, {@link #getComponentCreate creates}
     * this {@code SwingControl}'s {@code Component} if necessary, then
     * {@link #initComponentOnEventThread initializes} it.
     */
    @Override
    public void start(Navigator navigator, Map<String, String> parameters)
	throws NavigationAbortedException, InvalidParameterException,
	NavigationFailedException {

	super.start(navigator, parameters);

	if (propSource != null) {
	    if (propNames == null) {
		propSource.addPropertyChangeListener(this);
	    } else {
		for (String propName : propNames) {
		    propSource.addPropertyChangeListener(propName, this);
		}
	    }
	}

	Component comp = getComponentCreate();
	if (comp != null) {
	    initComponentOnEventThread();
	}
    }

    /**
     * Calls the superclass implementation, then {@link
     * #deinitComponentOnEventThread deinitializes} this {@code SwingControl}'s
     * {@code Component}.
     *
     * @exception   NavigationAbortedException
     *		    if the navigation is cancelled, or a save fails
     */
    @Override
    public void stop(boolean isCancel) throws NavigationAbortedException {
	super.stop(isCancel);

	Component comp = getComponent();
	if (comp != null) {
	    deinitComponentOnEventThread();
	}

	if (propSource != null) {
	    propSource.removePropertyChangeListener(this);
	    propSource = null;
	    propNames = null;
	}
    }

    //
    // SwingControl methods
    //

    /**
     * Configures the just-{@code #createComponent created} {@code Component}
     * for this {@code SwingControl}.  This method is called automatically just
     * after {@code createComponent}.
     * <p/>
     * Most subclasses can do all configuration in {@link #createComponent}.
     * Only implementors that wish to provide a common configuration for all
     * {@code Component}s created by their subclasses need to implement this
     * method.
     * <p/>
     * Subclasses that override this method should call {@code
     * super.configComponent(comp)} to ensure that any present or future
     * functionality in any superclass implementation is executed.
     * <p/>
     * This default implementation does nothing.
     *
     * @param	    comp
     *		    the newly-created {@code Component}
     */
    protected void configComponent(C comp) {
    }

    /**
     * Creates the {@code Component} for this {@code SwingControl}.
     * <p/>
     * This default implementation does nothing and returns {@code null}.
     *
     * @return	    the newly-created {@code Component}
     */
    protected C createComponent() {
	return null;
    }

    /**
     * De-initializes the {@code Component} for this {@code SwingControl}.  This
     * method is called automatically when this {@code Control} is stopped.
     * <p/>
     * This default implementation does nothing.
     */
    protected void deinitComponent() {
    }

    /**
     * Calls {@link #deinitComponent} on the event queue thread and waits until
     * it has completed.  This method is called automatically when this {@code
     * Control} is stopped.
     */
    protected void deinitComponentOnEventThread() {
	GUIUtil.invokeAndWait(
	    new Runnable() {
		@Override
		public void run() {
		    deinitComponent();
		}
	    });
    }

    /**
     * Gets the {@link ChangeableAggregator}, if any, for this class.  This is
     * used in the default implementation of some {@link Control} methods to
     * track changes to this {@code Control}.
     * <p/>
     * This default implementation returns {@code null}.
     *
     * @return	    a {@link ChangeableAggregator}, or {@code null} if this
     *		    {@code SwingControl} has none
     */
    public ChangeableAggregator getChangeableAggregator() {
	return null;
    }

    /**
     * Gets the {@code Component} for this {@code SwingControl}, {@link
     * #createComponent creating} it, {@code #configComponent configuring} it,
     * and {@code #setComponent setting} it first if necessary.
     * <p/>
     * This method is not thread-safe; external locking may be necessary to
     * ensure that {@link #createComponent} is not called by multiple threads.
     *
     * @return	    a {@code Component}, or {@code null} if this {@code
     *		    SwingControl} has no {@code Component}.
     */
    public C getComponentCreate() {
	C comp = getComponent();
	if (comp == null) {
	    comp = createComponent();
	    if (comp != null) {
		setComponent(comp);
		configComponent(comp);
	    }
	}
	return comp;
    }

    /**
     * Gets the {@code {@link Stackable}<Component>} to which {@code Component}s
     * of descendant {@code SwingControl}s may be added.
     *
     * @return	    a {@code {@link Stackable}<Component>}, or {@code null} to
     *		    defer to {@code SwingControl}s higher in the navigation
     *		    stack
     */
    public Stackable<Component> getComponentStack() {
	return stack;
    }

    /**
     * Gets whether property changes to the {@link #setPropertyChangeSource
     * property change source} are temporarily ignored.
     *
     * @see	    #setPropertyChangeIgnore
     */
    public boolean getPropertyChangeIgnore() {
	return ignorePropChange;
    }

    /**
     * Gets a custom message to display in the dialog that prompts the user to
     * {@link #getUnsavedChangesAction handle unsaved changes}.
     * <p/>
     * This default implementation returns {@code null}.
     *
     * @return	    a {@code String}, or {@code null} to use the default message
     */
    protected String getUnsavedChangesMessage() {
	return null;
    }

    /**
     * Initializes the {@code Component} for this {@code SwingControl}.  This
     * method is called automatically when this {@code Control} is started.
     * <p/>
     * This default implementation does nothing.
     */
    protected void initComponent() {
    }

    /**
     * Calls {@link #initComponent} on the event queue thread and waits until it
     * has completed.  This method is called automatically when this {@code
     * Control} is started.
     */
    protected void initComponentOnEventThread() {
	GUIUtil.invokeAndWait(
	    new Runnable() {
		@Override
		public void run() {
		    initComponent();
		}
	    });
    }

    /**
     * Gets the {@code Component} for this {@code SwingControl}.
     *
     * @param	    comp
     *		    a {@code Component}, or {@code null} if this {@code
     *		    SwingControl} has no {@code Component}.
     *
     * @see	    #getComponent
     */
    protected void setComponent(C comp) {
	this.comp = comp;
    }

    /**
     * Sets the {@code {@link Stackable}<Component>} to which {@code Component}s
     * of descendant {@code SwingControl}s may be added.
     *
     * @param	    stack
     *		    a {@code {@link Stackable}<Component>} or {@code null} to
     *		    defer to {@code SwingControl}s higher in the navigation
     *		    stack
     */
    protected void setComponentStack(Stackable<Component> stack) {
	this.stack = stack;
    }

    /**
     * Sets whether property changes to the {@link #setPropertyChangeSource
     * property change source} are temporarily ignored.
     *
     * @see	    #getPropertyChangeIgnore
     */
    public void setPropertyChangeIgnore(boolean ignorePropChange) {
	this.ignorePropChange = ignorePropChange;
    }

    /**
     * Sets a {@link ManagedObject} to which this control should listen
     * for {@code PropertyChangeEvent}s once it is started.  Should not
     * be called while the control is started.
     *
     * @param	    propSource
     *		    the {@link ManagedObject} to listen to
     *
     * @param	    propNames
     *		    the names of the properties in which to listen for changes,
     *		    or an empty array to listen for changes in all properties on
     *		    {@code propSource}
     */
    protected void setPropertyChangeSource(ManagedObject propSource,
	String... propNames) {

	assert !isStarted();
	this.propSource = propSource;
	this.propNames = propNames == null || propNames.length == 0 ? null :
	    propNames;
    }
}