Home | History | Annotate | Download | only in control
      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 2009 Sun Microsystems, Inc.  All rights reserved.
     24  * Use is subject to license terms.
     25  */
     26 
     27 package org.opensolaris.os.vp.panel.swing.control;
     28 
     29 import java.awt.*;
     30 import java.awt.event.*;
     31 import java.beans.*;
     32 import java.util.*;
     33 import java.util.List;
     34 import javax.swing.*;
     35 import org.opensolaris.os.vp.panel.common.ClientContext;
     36 import org.opensolaris.os.vp.panel.common.action.*;
     37 import org.opensolaris.os.vp.panel.common.control.*;
     38 import org.opensolaris.os.vp.panel.common.model.*;
     39 import org.opensolaris.os.vp.panel.swing.action.SwingStructuredAction;
     40 import org.opensolaris.os.vp.util.misc.*;
     41 import org.opensolaris.os.vp.util.swing.*;
     42 
     43 public class SwingControl<P extends PanelDescriptor, C extends Component>
     44     extends DefaultControl<P> implements HasComponent<C>,
     45     PropertyChangeListener {
     46 
     47     //
     48     // Instance data
     49     //
     50 
     51     private ManagedObject propSource;
     52     private String[] propNames;
     53     private boolean ignorePropChange;
     54 
     55     private C comp;
     56     private ComponentStack stack;
     57     private JDialog childDialog;
     58     private List<Control> dialogChildren = new ArrayList<Control>();
     59 
     60     private SwingStructuredAction<Object, Object, Object> resetAction =
     61 	new SwingStructuredAction<Object, Object, Object>(null, null, null,
     62 	    this) {
     63 
     64 	    @Override
     65 	    public Object workBusy(Object pInput, Object rtInput)
     66 		throws ActionAbortedException, ActionFailedException,
     67 		ActionUnauthorizedException {
     68 
     69 		resetAll();
     70 		return null;
     71 	    }
     72 	};
     73 
     74     private SwingStructuredAction<Object, Object, Object> saveAction =
     75 	new SwingStructuredAction<Object, Object, Object>(null, null, null,
     76 	    this) {
     77 
     78 	    @Override
     79 	    public Object workBusy(Object pInput, Object rtInput)
     80 		throws ActionAbortedException, ActionFailedException,
     81 		ActionUnauthorizedException {
     82 
     83 		saveAll();
     84 		return null;
     85 	    }
     86 	};
     87 
     88     //
     89     // Constructors
     90     //
     91 
     92     public SwingControl(String id, String name, ClientContext context) {
     93 	super(id, name, context);
     94     }
     95 
     96     public SwingControl(String id, String name, P descriptor) {
     97 	super(id, name, descriptor);
     98     }
     99 
    100     //
    101     // HasComponent methods
    102     //
    103 
    104     /**
    105      * Gets the {@code Component} for this {@code SwingControl}.
    106      *
    107      * @return	    a {@code Component}, or {@code null} if this {@code
    108      *		    SwingControl} has no {@code Component}.
    109      *
    110      * @see	    #setComponent
    111      */
    112     @Override
    113     public C getComponent() {
    114 	return comp;
    115     }
    116 
    117     //
    118     // PropertyChangeListener methods
    119     //
    120 
    121     /**
    122      * Convenience implementation that calls {@link #initComponentOnEventThread}
    123      * if property changes are not temporarilty being {@link
    124      * #getPropertyChangeIgnore ignored}.  If this {@link SwingControl}'s
    125      * component displayed the properties of a {@link ManagedObject}, for
    126      * example, it may make sense to add this {@code SwingControl} as a {@code
    127      * PropertyListener} to that object.
    128      */
    129     @Override
    130     public void propertyChange(PropertyChangeEvent event) {
    131 	if (!ignorePropChange && getComponent() != null) {
    132 	    initComponentOnEventThread();
    133 	}
    134     }
    135 
    136     //
    137     // Control methods
    138     //
    139 
    140     @Override
    141     public void childStarted(final Control child) {
    142 	super.childStarted(child);
    143 
    144 	assert childDialog == null;
    145 
    146 	if (dialogChildren.contains(child)) {
    147 	    Component comp = getComponent();
    148 	    final Window owner = SwingUtilities.getWindowAncestor(comp);
    149 
    150 	    childDialog = new JDialog(owner,
    151 		Dialog.ModalityType.DOCUMENT_MODAL);
    152 	    Container cont = childDialog.getContentPane();
    153 	    cont.setLayout(new BorderLayout());
    154 
    155 	    Component cComp = ((SwingControl)child).getComponent();
    156 	    cont.add(cComp, BorderLayout.CENTER);
    157 
    158 	    if (child instanceof HasImages) {
    159 		List<? extends Image> images = ((HasImages)child).getImages();
    160 		childDialog.setIconImages(images);
    161 	    }
    162 
    163 	    childDialog.setTitle(child.getName());
    164 	    childDialog.setDefaultCloseOperation(
    165 		WindowConstants.DISPOSE_ON_CLOSE);
    166 	    childDialog.pack();
    167 
    168 	    childDialog.addWindowListener(
    169 		new WindowAdapter() {
    170 		    @Override
    171 		    public void windowClosing(WindowEvent e) {
    172 			childDialog.removeWindowListener(this);
    173 			child.doCancel();
    174 		    }
    175 		});
    176 
    177 	    // Show the window after navigation has completed
    178 	    getNavigator().asyncExec(
    179 		new Runnable() {
    180 		    @Override
    181 		    public void run() {
    182 			EventQueue.invokeLater(
    183 			    new Runnable() {
    184 				@Override
    185 				public void run() {
    186 				    childDialog.setLocationRelativeTo(owner);
    187 				    childDialog.setVisible(true);
    188 				}
    189 			    });
    190 		    }
    191 		});
    192 	}
    193     }
    194 
    195     @Override
    196     public void childStopped(Control child) {
    197 	super.childStopped(child);
    198 
    199 	if (childDialog != null) {
    200 	    childDialog.dispose();
    201 	    childDialog = null;
    202 	}
    203     }
    204 
    205     /**
    206      * If a) this {@code SwingControl} defines a {@link #getComponentStack
    207      * ComponentStack}, b) the just-started {@link Control} implements {@link
    208      * HasComponent}, and c) its {@code Component} does not yet have a parent,
    209      * this method adds that {@code Component} to the stack.
    210      */
    211     @Override
    212     public void descendantStarted(Control[] path) {
    213 	super.descendantStarted(path);
    214 
    215 	final ComponentStack stack = getComponentStack();
    216 	if (stack != null) {
    217 	    Control control = path[path.length - 1];
    218 	    if (control instanceof HasComponent) {
    219 		final Component comp = ((HasComponent)control).getComponent();
    220 		if (comp != null && comp.getParent() == null) {
    221 		    GUIUtil.invokeAndWait(
    222 			new Runnable() {
    223 			    @Override
    224 			    public void run() {
    225 				stack.push(comp);
    226 			    }
    227 			});
    228 		}
    229 	    }
    230 	}
    231     }
    232 
    233     /**
    234      * If a) this {@code SwingControl} defines a {@link #getComponentStack
    235      * ComponentStack}, b) the just-stopped {@link Control} implements {@link
    236      * HasComponent}, and c) its {@code Component} is at the top of the stack,
    237      * this method removes that {@code Component} from the stack.
    238      */
    239     @Override
    240     public void descendantStopped(Control[] path) {
    241 	super.descendantStopped(path);
    242 
    243 	final ComponentStack stack = getComponentStack();
    244 	if (stack != null) {
    245 	    Control control = path[path.length - 1];
    246 	    if (control instanceof HasComponent) {
    247 		final Component comp = ((HasComponent)control).getComponent();
    248 		if (comp != null) {
    249 		    GUIUtil.invokeAndWait(
    250 			new Runnable() {
    251 			    @Override
    252 			    public void run() {
    253 				StackUtil.popIfAtTop(stack, comp);
    254 			    }
    255 			});
    256 		}
    257 	    }
    258 	}
    259     }
    260 
    261     /**
    262      * Gets a {@link SwingStructuredAction} that invokes {@link #resetAll},
    263      * displaying errors.
    264      */
    265     @Override
    266     public SwingStructuredAction<?, ?, ?> getResetAction() {
    267 	return resetAction;
    268     }
    269 
    270     /**
    271      * Gets a {@link SwingStructuredAction} that invokes {@link #saveAll},
    272      * displaying errors and prompting for login on {@code
    273      * ActionUnauthorizedException}s.
    274      */
    275     @Override
    276     public SwingStructuredAction<?, ?, ?> getSaveAction() {
    277 	return saveAction;
    278     }
    279 
    280     /**
    281      * Prompts the user to save changes, discard changes, or cancel navigation,
    282      * when navigating away from a {@code Control} with unsaved changes.
    283      *
    284      * @see	    #getUnsavedChangesMessage
    285      */
    286     @Override
    287     protected UnsavedChangesAction getUnsavedChangesAction() {
    288 	String message = getUnsavedChangesMessage();
    289 	if (message == null) {
    290 	    message = Finder.getString("settings.unsavedchanges.message");
    291 	}
    292 
    293 	String title = Finder.getString("settings.unsavedchanges.title");
    294 	int messageType = JOptionPane.WARNING_MESSAGE;
    295 	int optionType = JOptionPane.DEFAULT_OPTION;
    296 	Icon icon = GUIUtil.getIcon(JOptionPane.WARNING_MESSAGE);
    297 
    298 	Object[] options = {
    299 	    Finder.getString("settings.unsavedchanges.button.cancel"),
    300 	    Finder.getString("settings.unsavedchanges.button.discard"),
    301 	    Finder.getString("settings.unsavedchanges.button.save"),
    302 	};
    303 
    304 	Object defaultOption = options[0];
    305 
    306 	int result = JOptionPane.showOptionDialog(getComponent(), message,
    307 	    title, optionType, messageType, icon, options, defaultOption);
    308 
    309 	switch (result) {
    310 	    default:
    311 	    case 0: return UnsavedChangesAction.CANCEL;
    312 	    case 1: return UnsavedChangesAction.DISCARD;
    313 	    case 2: return UnsavedChangesAction.SAVE;
    314 	}
    315     }
    316 
    317     /**
    318      * If this {@code SwingControl} has a {@link #getChangeableAggregator
    319      * ChangeableAggregator}, returns the value from the aggregator's {@link
    320      * ChangeableAggregator#isChanged isChanged} method.  Otherwise, returns
    321      * {@code false}.
    322      */
    323     @Override
    324     protected boolean isChanged() {
    325 	ChangeableAggregator aggregator = getChangeableAggregator();
    326 	if (aggregator != null) {
    327 	    return aggregator.isChanged();
    328 	}
    329 	return false;
    330     }
    331 
    332     /**
    333      * If this {@code SwingControl} has a {@link #getChangeableAggregator
    334      * ChangeableAggregator}, calls its {@link ChangeableAggregator#reset}
    335      * method on the AWT event thread and waits for its completion.
    336      */
    337     @Override
    338     protected void reset() throws ActionAbortedException, ActionFailedException
    339     {
    340 	final ChangeableAggregator aggregator = getChangeableAggregator();
    341 	if (aggregator != null) {
    342 	    GUIUtil.invokeAndWait(
    343 		new Runnable() {
    344 		    @Override
    345 		    public void run() {
    346 			aggregator.reset();
    347 		    }
    348 		});
    349 	}
    350     }
    351 
    352     /**
    353      * If this {@code SwingControl} has a {@link #getChangeableAggregator
    354      * ChangeableAggregator}, calls its {@link ChangeableAggregator#save}
    355      * method on the AWT event thread and waits for its completion.
    356      */
    357     @Override
    358     protected void save() throws ActionAbortedException, ActionFailedException,
    359 	ActionUnauthorizedException {
    360 
    361 	final ChangeableAggregator aggregator = getChangeableAggregator();
    362 	if (aggregator != null) {
    363 	    GUIUtil.invokeAndWait(
    364 		new Runnable() {
    365 		    @Override
    366 		    public void run() {
    367 			aggregator.save();
    368 		    }
    369 		});
    370 	}
    371     }
    372 
    373     /**
    374      * Calls the superclass implementation, {@link #getComponentCreate creates}
    375      * this {@code SwingControl}'s {@code Component} if necessary, then
    376      * {@link #initComponentOnEventThread initializes} it.
    377      */
    378     @Override
    379     public void start(Navigator navigator, Map<String, String> parameters)
    380 	throws NavigationAbortedException, InvalidParameterException {
    381 
    382 	super.start(navigator, parameters);
    383 
    384 	if (propSource != null) {
    385 	    if (propNames == null) {
    386 		propSource.addPropertyChangeListener(this);
    387 	    } else {
    388 		for (String propName : propNames) {
    389 		    propSource.addPropertyChangeListener(propName, this);
    390 		}
    391 	    }
    392 	}
    393 
    394 	Component comp = getComponentCreate();
    395 	if (comp != null) {
    396 	    initComponentOnEventThread();
    397 	}
    398     }
    399 
    400     /**
    401      * Calls the superclass implementation, then {@link
    402      * #deinitComponentOnEventThread deinitializes} this {@code SwingControl}'s
    403      * {@code Component}.
    404      *
    405      * @exception   NavigationAbortedException
    406      *		    if the navigation is cancelled, or a save fails
    407      */
    408     @Override
    409     public void stop(boolean isCancel) throws NavigationAbortedException {
    410 	super.stop(isCancel);
    411 
    412 	Component comp = getComponent();
    413 	if (comp != null) {
    414 	    deinitComponentOnEventThread();
    415 	}
    416 
    417 	if (propSource != null) {
    418 	    propSource.removePropertyChangeListener(this);
    419 	    propSource = null;
    420 	    propNames = null;
    421 	}
    422     }
    423 
    424     //
    425     // DefaultControl methods
    426     //
    427 
    428     @Override
    429     protected void removeChild(int index) {
    430 	synchronized (children) {
    431 	    Control child = children.get(index);
    432 	    children.remove(index);
    433 	    dialogChildren.remove(child);
    434 	}
    435     }
    436 
    437     //
    438     // SwingControl methods
    439     //
    440 
    441     /**
    442      * Calls {@link DefaultControl#addChildren addChildren} with the given
    443      * controls.  When a child in the given list is started, it will
    444      * automatically be placed in a modal dialog that corresponds to the child's
    445      * lifecycle.
    446      */
    447     public void addDialogChildren(Control... controls) {
    448 	super.addChildren(controls);
    449 	for (Control child : controls) {
    450 	    dialogChildren.add(child);
    451 	}
    452     }
    453 
    454     /**
    455      * Configures the just-{@code #createComponent created} {@code Component}
    456      * for this {@code SwingControl}.  This method is called automatically just
    457      * after {@code createComponent}.
    458      * <p/>
    459      * This default implementation does nothing.  Most subclasses can do all
    460      * configuration in {@link #createComponent}.  Only implementors that wish
    461      * to provide a common configuration for all {@code Component}s created by
    462      * their subclasses need to implement this method.
    463      *
    464      * @param	    comp
    465      *		    the newly-created {@code Component}
    466      */
    467     protected void configComponent(C comp) {
    468     }
    469 
    470     /**
    471      * Creates the {@code Component} for this {@code SwingControl}.
    472      * <p/>
    473      * This default implementation does nothing and returns {@code null}.
    474      *
    475      * @return	    the newly-created {@code Component}
    476      */
    477     protected C createComponent() {
    478 	return null;
    479     }
    480 
    481     /**
    482      * De-initializes the {@code Component} for this {@code SwingControl}.  This
    483      * method is called automatically when this {@code Control} is stopped.
    484      * <p/>
    485      * This default implementation does nothing.
    486      */
    487     protected void deinitComponent() {
    488     }
    489 
    490     /**
    491      * Calls {@link #deinitComponent} on the event queue thread and waits until
    492      * it has completed.  This method is called automatically when this {@code
    493      * Control} is stopped.
    494      */
    495     protected void deinitComponentOnEventThread() {
    496 	GUIUtil.invokeAndWait(
    497 	    new Runnable() {
    498 		@Override
    499 		public void run() {
    500 		    deinitComponent();
    501 		}
    502 	    });
    503     }
    504 
    505     /**
    506      * Gets the {@link ChangeableAggregator}, if any, for this class.  This is
    507      * used in the default implementation of some {@link Control} methods to
    508      * track changes to this {@code Control}.
    509      * <p/>
    510      * This default implementation returns {@code null}.
    511      *
    512      * @return	    a {@link ChangeableAggregator}, or {@code null} if this
    513      *		    {@code SwingControl} has none
    514      */
    515     public ChangeableAggregator getChangeableAggregator() {
    516 	return null;
    517     }
    518 
    519     /**
    520      * Gets the {@code Component} for this {@code SwingControl}, {@link
    521      * #createComponent creating} it, {@code #configComponent configuring} it,
    522      * and {@code #setComponent setting} it first if necessary.
    523      * <p/>
    524      * This method is not thread-safe; external locking may be necessary to
    525      * ensure that {@link #createComponent} is not called by multiple threads.
    526      *
    527      * @return	    a {@code Component}, or {@code null} if this {@code
    528      *		    SwingControl} has no {@code Component}.
    529      */
    530     public C getComponentCreate() {
    531 	C comp = getComponent();
    532 	if (comp == null) {
    533 	    comp = createComponent();
    534 	    if (comp != null) {
    535 		setComponent(comp);
    536 		configComponent(comp);
    537 	    }
    538 	}
    539 	return comp;
    540     }
    541 
    542     /**
    543      * Gets the {@link ComponentStack} to which {@code Component}s of
    544      * descendant {@code SwingControl}s may be added.
    545      *
    546      * @return	    a {@link ComponentStack}, or {@code null} to defer to
    547      *		    {@code SwingControl}s higher in the navigation stack
    548      */
    549     public ComponentStack getComponentStack() {
    550 	return stack;
    551     }
    552 
    553     /**
    554      * Gets whether property changes to the {@link #setPropertyChangeSource
    555      * property change source} are temporarily ignored.
    556      *
    557      * @see	    #setPropertyChangeIgnore
    558      */
    559     public boolean getPropertyChangeIgnore() {
    560 	return ignorePropChange;
    561     }
    562 
    563     /**
    564      * Gets a custom message to display in the dialog that prompts the user to
    565      * {@link #getUnsavedChangesAction handle unsaved changes}.
    566      * <p/>
    567      * This default implementation returns {@code null}.
    568      *
    569      * @return	    a {@code String}, or {@code null} to use the default message
    570      */
    571     protected String getUnsavedChangesMessage() {
    572 	return null;
    573     }
    574 
    575     /**
    576      * Initializes the {@code Component} for this {@code SwingControl}.  This
    577      * method is called automatically when this {@code Control} is started.
    578      * <p/>
    579      * This default implementation does nothing.
    580      */
    581     protected void initComponent() {
    582     }
    583 
    584     /**
    585      * Calls {@link #initComponent} on the event queue thread and waits until it
    586      * has completed.  This method is called automatically when this {@code
    587      * Control} is started.
    588      */
    589     protected void initComponentOnEventThread() {
    590 	GUIUtil.invokeAndWait(
    591 	    new Runnable() {
    592 		@Override
    593 		public void run() {
    594 		    initComponent();
    595 		}
    596 	    });
    597     }
    598 
    599     /**
    600      * Gets the {@code Component} for this {@code SwingControl}.
    601      *
    602      * @param	    comp
    603      *		    a {@code Component}, or {@code null} if this {@code
    604      *		    SwingControl} has no {@code Component}.
    605      *
    606      * @see	    #getComponent
    607      */
    608     protected void setComponent(C comp) {
    609 	this.comp = comp;
    610     }
    611 
    612     /**
    613      * Sets the {@link ComponentStack} to which {@code Component}s of
    614      * descendant {@code SwingControl}s may be added.
    615      *
    616      * @param	    stack
    617      *		    a {@link ComponentStack}, or {@code null} to defer to
    618      *		    {@code SwingControl}s higher in the navigation stack
    619      */
    620     protected void setComponentStack(ComponentStack stack) {
    621 	this.stack = stack;
    622     }
    623 
    624     /**
    625      * Sets whether property changes to the {@link #setPropertyChangeSource
    626      * property change source} are temporarily ignored.
    627      *
    628      * @see	    #getPropertyChangeIgnore
    629      */
    630     public void setPropertyChangeIgnore(boolean ignorePropChange) {
    631 	this.ignorePropChange = ignorePropChange;
    632     }
    633 
    634     /**
    635      * Sets a {@link ManagedObject} to which this control should listen
    636      * for {@code PropertyChangeEvent}s once it is started.  Should not
    637      * be called while the control is started.
    638      *
    639      * @param	    propSource
    640      *		    the {@link ManagedObject} to listen to
    641      *
    642      * @param	    propNames
    643      *		    the names of the properties in which to listen for changes,
    644      *		    or an empty array to listen for changes in all properties on
    645      *		    {@code propSource}
    646      */
    647     protected void setPropertyChangeSource(ManagedObject propSource,
    648 	String... propNames) {
    649 
    650 	assert !isStarted();
    651 	this.propSource = propSource;
    652 	this.propNames = propNames == null || propNames.length == 0 ? null :
    653 	    propNames;
    654     }
    655 }
    656