blob: 157d4ddc7b40627d7f366ad705f0dadfed1438ae [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.openide;
import java.awt.*;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection;
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.IOException;
import java.net.URL;
import java.text.MessageFormat;
import java.util.*;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.accessibility.Accessible;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.HyperlinkEvent;
import javax.swing.event.HyperlinkEvent.EventType;
import javax.swing.event.HyperlinkListener;
import javax.swing.text.html.HTMLEditorKit;
import javax.swing.text.html.StyleSheet;
import org.netbeans.api.progress.ProgressHandle;
import org.netbeans.api.progress.ProgressHandleFactory;
import org.openide.awt.HtmlBrowser;
import org.openide.awt.HtmlBrowser.URLDisplayer;
import org.openide.awt.Mnemonics;
import org.openide.util.*;
/**
* Implements a basic "wizard" GUI system.
* A list of <em>wizard panels</em> may be specified and these
* may be traversed at the proper times using the "Previous"
* and "Next" buttons (or "Finish" on the last one).
*
* <p><b>Related Tutorial</b>
*
* <ul>
* <li><a href="http://platform.netbeans.org/tutorials/nbm-wizard.html">NetBeans Wizard Module Tutorial</a>
* <li><a href="doc-files/wizard-guidebook.html">Wizard Guide</a>
* </ul>
*
* @see DialogDisplayer#createDialog
*/
public class WizardDescriptor extends DialogDescriptor {
/** "Next" button option.
* @see #setOptions */
public static final Object NEXT_OPTION = new String("NEXT_OPTION"); // NOI18N
/** "Finish" button option.
* @see #setOptions */
public static final Object FINISH_OPTION = OK_OPTION;
/** "Previous" button option.
* @see #setOptions */
public static final Object PREVIOUS_OPTION = new String("PREVIOUS_OPTION"); // NOI18N
private static final ActionListener CLOSE_PREVENTER = new ActionListener() {
@Override
public void actionPerformed(ActionEvent evt) {
}
@Override
public String toString() {
return "CLOSE_PREVENTER"; // NOI18N
}
};
/** Set to <CODE>true</CODE> for enabling other properties. It is relevant only on
* initialization (client property in first panel). Recommended to be set to <code>true</code> in most cases,
* then wizard can display wizard steps on the left side, create a subtitle on active panel,
* display of error messages and others. When false or not present in JComponent.getClientProperty(),
* then supplied panel is used directly without content, help or panel name auto layout.
*
* The value is taken from <CODE>WizardDescriptor.getProperty()</CODE> or
* <CODE>((JComponent)Panel.getComponent()).getClientProperty()</CODE> in this order.
* <CODE>Boolean</CODE> type property.
* @since 7.8
*/
public static final String PROP_AUTO_WIZARD_STYLE = "WizardPanel_autoWizardStyle"; // NOI18N
/** Set to <CODE>true</CODE> for showing help pane (HTML browser) in the left pane. It is relevant only on
* initialization (client property in first panel). Help content will be taken from property <CODE>PROP_HELP_URL</CODE>.
*
* The value is taken from <CODE>WizardDescriptor.getProperty()</CODE> or
* <CODE>((JComponent)Panel.getComponent()).getClientProperty()</CODE> in this order.
* <CODE>Boolean</CODE> type property.
* @since 7.8
*/
public static final String PROP_HELP_DISPLAYED = "WizardPanel_helpDisplayed"; // NOI18N
/** Set to <CODE>true</CODE> for showing content pane (steps) in the left pane. It is relevant only on
* initialization (client property in first panel). Content will be constructed from property <CODE>PROP_CONTENT_DATA</code>.
*
* The value is taken from <CODE>WizardDescriptor.getProperty()</CODE> or
* <CODE>((JComponent)Panel.getComponent()).getClientProperty()</CODE> in this order.
* <CODE>Boolean</CODE> type property.
* @since 7.8
*/
public static final String PROP_CONTENT_DISPLAYED = "WizardPanel_contentDisplayed"; // NOI18N
/** Set to <CODE>true</CODE> for displaying numbers in the content. It is relevant only on
* initialization (client property in first panel).
*
* The value is taken from <CODE>WizardDescriptor.getProperty()</CODE> or
* <CODE>((JComponent)Panel.getComponent()).getClientProperty()</CODE> in this order.
* <CODE>Boolean</CODE> type property.
* @since 7.8
*/
public static final String PROP_CONTENT_NUMBERED = "WizardPanel_contentNumbered"; // NOI18N
/** Represents index of content item which will be highlighted.
*
* The value is taken from <CODE>WizardDescriptor.getProperty()</CODE> or
* <CODE>((JComponent)Panel.getComponent()).getClientProperty()</CODE> in this order.
* <CODE>Integer</CODE> type property.
* @since 7.8
*/
public static final String PROP_CONTENT_SELECTED_INDEX = "WizardPanel_contentSelectedIndex"; // NOI18N
/** Represents array of content items.
*
* The value is taken from <CODE>WizardDescriptor.getProperty()</CODE> or
* <CODE>((JComponent)Panel.getComponent()).getClientProperty()</CODE> in this order.
* <CODE>String[]</CODE> type property.
* @since 7.8
*/
public static final String PROP_CONTENT_DATA = "WizardPanel_contentData"; // NOI18N
/** Set background color of content pane.
*
* The value is taken from <CODE>WizardDescriptor.getProperty()</CODE> or
* <CODE>((JComponent)Panel.getComponent()).getClientProperty()</CODE> in this order.
* <CODE>Color</CODE> type property.
* @since 7.8
*/
public static final String PROP_CONTENT_BACK_COLOR = "WizardPanel_contentBackColor"; // NOI18N
/** Set foreground color of content pane.
*
* The value is taken from <CODE>WizardDescriptor.getProperty()</CODE> or
* <CODE>((JComponent)Panel.getComponent()).getClientProperty()</CODE> in this order.
* <CODE>Color</CODE> type property.
* @since 7.8
*/
public static final String PROP_CONTENT_FOREGROUND_COLOR = "WizardPanel_contentForegroundColor"; // NOI18N
/** Set the image which will be displayed in the left pane (behind the content).
*
* The value is taken from <CODE>WizardDescriptor.getProperty()</CODE> or
* <CODE>((JComponent) Panel.getComponent()).getClientProperty()</CODE> in this order.
* <CODE>java.awt.Image</CODE> type property.
* @since 7.8
*/
public static final String PROP_IMAGE = "WizardPanel_image"; // NOI18N
/** Set the side where the image should be drawn.
*
* The value is taken from <CODE>WizardDescriptor.getProperty()</CODE> or
* <CODE>((JComponent) Panel.getComponent()).getClientProperty()</CODE> in this order.
* <CODE>String</CODE> type property.
* @since 7.8
*/
public static final String PROP_IMAGE_ALIGNMENT = "WizardPanel_imageAlignment"; // NOI18N
/** Dimension of left pane, should be same as dimension of <CODE>PROP_IMAGE</CODE>.
* It is relevant only on initialization (client property in first panel).
*
* The value is taken from <CODE>WizardDescriptor.getProperty()</CODE> or
* <CODE>((JComponent) Panel.getComponent()).getClientProperty()</CODE> in this order.
* <CODE>Dimension</CODE> type property.
* @since 7.8
*/
public static final String PROP_LEFT_DIMENSION = "WizardPanel_leftDimension"; // NOI18N
/** Represents URL of help displayed in left pane.
*
* The value is taken from <CODE>WizardDescriptor.getProperty()</CODE> or
* <CODE>((JComponent) Panel.getComponent()).getClientProperty()</CODE> in this order.
* <CODE>URL</CODE> type property.
* @since 7.8
*/
public static final String PROP_HELP_URL = "WizardPanel_helpURL"; // NOI18N
/** Error message that is displayed at the bottom of the wizard.
* Message informs user why the panel is invalid and possibly why the Next/Finish buttons were disabled.
* The property must be set to null value to clear the message.
*
* The value is taken from <CODE>WizardDescriptor.getProperty()</CODE>.
* <CODE>String</CODE> type property.
* @since 7.8
*/
public static final String PROP_ERROR_MESSAGE = "WizardPanel_errorMessage"; // NOI18N
/** Warning message that is displayed at the bottom of the wizard.
* Message informs user about possible non fatal problems with current enterd values in the wizard panel.
* Next/Finish buttons are usually enabled. The property must be set to null value to clear the message.
*
* The value is taken from <CODE>WizardDescriptor.getProperty()</CODE>.
* <CODE>String</CODE> type property.
* @since 7.8
*/
public static final String PROP_WARNING_MESSAGE = "WizardPanel_warningMessage"; // NOI18N
/** Informational message that is displayed at the bottom of the wizard.
* Message informs user usually about need to fill some field or similar requirements or other non fatal problems.
* Next/Finish button are usually enabled. The property must be set to null value to clear the message.
*
* The value is taken from <CODE>WizardDescriptor.getProperty()</CODE>.
* <CODE>String</CODE> type property.
* @since 7.8
*/
public static final String PROP_INFO_MESSAGE = "WizardPanel_infoMessage"; // NOI18N
private static Logger err = Logger.getLogger(WizardDescriptor.class.getName ());
/** real buttons to be placed instead of the options */
private final JButton nextButton = new JButton();
private final JButton finishButton = new JButton();
private final JButton cancelButton = new JButton();
private final JButton previousButton = new JButton();
private FinishAction finishOption;
private Set<Object> newObjects = Collections.emptySet();
/** a component with wait cursor */
private Component waitingComponent;
private boolean changeStateInProgress = false;
private boolean addedWindowListener;
private boolean currentPanelWasChangedWhileStoreSettings = false;
private final AtomicBoolean initialized = new AtomicBoolean( true );
/** Whether wizard panel will be constructed from <CODE>WizardDescriptor.getProperty()</CODE>/
* <CODE>(JComponent)Panel.getComponent()</CODE> client properties or returned
* <CODE>Component</CODE> will be inserted to wizard dialog directly.
*/
private boolean autoWizardStyle = false;
/** Whether properties from first <CODE>(JComponent)Panel.getComponent()</CODE>
* have been initialized.
*/
private boolean init = false;
/** Panel which is used when in <CODE>AUTO_WIZARD_STYLE</CODE> mode.*/
private WizardPanel wizardPanel;
/** Image */
private Image image;
/** Content data */
private String[] contentData = new String[] { };
/** Selected content index */
private int contentSelectedIndex = -1;
/** Background color*/
private Color contentBackColor;
/** Foreground color*/
private Color contentForegroundColor;
/** Help URL displayed in the left pane */
private URL helpURL;
/** Listener on a user component client property changes*/
private PropL propListener;
/** 'North' or 'South' */
private String imageAlignment = "North"; // NOI18N
/** Iterator between panels in the wizard and its settings */
private SettingsAndIterator<?> data;
/** Change listener that invokes method update state */
private ChangeListener weakChangeListener;
private PropertyChangeListener weakPropertyChangeListener;
private ActionListener weakNextButtonListener;
private ActionListener weakPreviousButtonListener;
private ActionListener weakFinishButtonListener;
private ActionListener weakCancelButtonListener;
// base listener which won't be directly attached, will only wrapped by WeakListener
private Listener baseListener;
/** message format to create title of the document */
private MessageFormat titleFormat;
/** hashtable with additional settings that is usually used
* by Panels to store their data
*/
private Map<String,Object> properties;
ResourceBundle bundle = NbBundle.getBundle(WizardDescriptor.class);
/** Request processor that is used for asynchronous jobs (background validation,
* asynchronous instantiation i.e.) and supports Thread.interrupted().
* It's package-private to accessible for unit tests.
*/
static final RequestProcessor ASYNCHRONOUS_JOBS_RP =
new RequestProcessor("wizard-descriptor-asynchronous-jobs", 1, true); // NOI18N
private RequestProcessor.Task backgroundValidationTask;
private boolean validationRuns;
private ProgressHandle handle;
private static final String PROGRESS_BAR_DISPLAY_NAME = NbBundle.getMessage (WizardDescriptor.class, "CTL_InstantiateProgress_Title"); // NOI18N
private ActionListener escapeActionListener;
/**
* If non-null and non-default HelpCtx is set on the WizardDescriptor instance (true)
* then help context provided by individual wizard panels is ignored.
*/
private boolean isWizardWideHelpSet = false;
{
// button init
ResourceBundle b = NbBundle.getBundle("org.openide.Bundle"); // NOI18N
Mnemonics.setLocalizedText(nextButton, b.getString("CTL_NEXT"));
Mnemonics.setLocalizedText(previousButton, b.getString("CTL_PREVIOUS"));
Mnemonics.setLocalizedText(finishButton, b.getString("CTL_FINISH"));
finishButton.getAccessibleContext().setAccessibleDescription(b.getString("ACSD_FINISH"));
Mnemonics.setLocalizedText(cancelButton, b.getString("CTL_CANCEL"));
cancelButton.getAccessibleContext().setAccessibleDescription(b.getString("ACSD_CANCEL"));
finishButton.setDefaultCapable(true);
nextButton.setDefaultCapable(true);
previousButton.setDefaultCapable(true);
previousButton.putClientProperty( "defaultButton", Boolean.FALSE ); //NOI18N
cancelButton.setDefaultCapable(true);
cancelButton.putClientProperty( "defaultButton", Boolean.FALSE ); //NOI18N
}
/** Create a new wizard from a fixed list of panels, passing some settings to the panels.
* @param wizardPanels the panels to use
* @param settings the settings to pass to panels, or <code>null</code>
* @see #WizardDescriptor(WizardDescriptor.Iterator, Object)
*/
public <Data> WizardDescriptor(Panel<Data>[] wizardPanels, Data settings) {
this(new SettingsAndIterator<Data>(new ArrayIterator<Data>(wizardPanels), settings));
}
/** Create a new wizard from a fixed list of panels with settings
* defaulted to <CODE>this</CODE>.
*
* @param wizardPanels the panels to use
* @see #WizardDescriptor(WizardDescriptor.Iterator, Object)
*/
public WizardDescriptor(Panel<WizardDescriptor>[] wizardPanels) {
this(SettingsAndIterator.create(new ArrayIterator<WizardDescriptor>(wizardPanels)));
}
/** Create wizard for a sequence of panels, passing some settings to the panels.
* @param panels iterator over all {@link WizardDescriptor.Panel}s that can appear in the wizard
* @param settings the settings to provide to the panels (may be any data understood by them)
* @see WizardDescriptor.Panel#readSettings
* @see WizardDescriptor.Panel#storeSettings
*/
public <Data>WizardDescriptor(Iterator<Data> panels, Data settings) {
this(new SettingsAndIterator<Data>(panels, settings));
}
/** Constructor for subclasses. The expected use is to call this
* constructor and then call {@link #setPanelsAndSettings} to provide
* the right iterator, panels and data the wizard should use. This
* allows to eliminate unchecked warnings as described in bug #102261.
* @since 7.4
*/
protected WizardDescriptor() {
this(SettingsAndIterator.empty());
}
private <Data> WizardDescriptor(SettingsAndIterator<Data> data) {
super("", "", true, DEFAULT_OPTION, null, CLOSE_PREVENTER); // NOI18N
this.data = data;
baseListener = new Listener();
weakNextButtonListener = WeakListeners.create(
ActionListener.class, baseListener, nextButton
); // NOI18N
weakPreviousButtonListener = WeakListeners.create(
ActionListener.class, baseListener, previousButton
); // NOI18N
weakFinishButtonListener = WeakListeners.create(
ActionListener.class, baseListener, finishButton
); // NOI18N
weakCancelButtonListener = WeakListeners.create(
ActionListener.class, baseListener, cancelButton
); // NOI18N
nextButton.addActionListener(weakNextButtonListener);
previousButton.addActionListener(weakPreviousButtonListener);
finishButton.addActionListener(weakFinishButtonListener);
cancelButton.addActionListener(weakCancelButtonListener);
finishOption = new WizardDescriptor.FinishAction();
super.setOptions(new Object[] { previousButton, nextButton, finishButton, cancelButton });
super.setClosingOptions(new Object[] { finishOption, cancelButton });
createNotificationLineSupport ();
// attach the change listener to iterator
weakChangeListener = WeakListeners.change(baseListener, data.getIterator(this));
data.getIterator(this).addChangeListener(weakChangeListener);
callInitialize();
}
/** Create wizard for a sequence of panels, with settings
* defaulted to <CODE>this</CODE>.
*
* @param panels iterator over all {@link WizardDescriptor.Panel}s that can appear in the wizard
*/
public WizardDescriptor(Iterator<WizardDescriptor> panels) {
this(SettingsAndIterator.create(panels));
}
/** Initializes settings.
*/
@Override
protected void initialize() {
super.initialize();
_updateState();
// update buttons when setValid(...) called
addPropertyChangeListener(new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (PROP_VALID.equals(evt.getPropertyName())) {
if (!isValid()) {
nextButton.setEnabled(false);
finishButton.setEnabled(false);
} else {
_updateState();
}
}
}
});
}
/** Set a different list of panels.
* Correctly updates the buttons.
* @param panels the new list of {@link WizardDescriptor.Panel}s
* @deprecated use setPanelsAndSettings if needed.
*/
@Deprecated
@SuppressWarnings("unchecked")
public final synchronized void setPanels(Iterator panels) {
if (data.getIterator(this) != null) {
data.getIterator(this).removeChangeListener(weakChangeListener);
}
//callUninitialize ();
data = data.clone(panels);
weakChangeListener = WeakListeners.change(baseListener, data.getIterator(this));
data.getIterator(this).addChangeListener(weakChangeListener);
init = false;
//callInitialize ();
initialized.set( true );
_updateState();
}
/** Set a different list of panels.
* Correctly updates the buttons.
* @param panels the new list of {@link WizardDescriptor.Panel}s
* @param settings the new settings that will be passed to the panels
* @since 7.2
*/
public final synchronized <Data> void setPanelsAndSettings(Iterator<Data> panels, Data settings) {
if (data.getIterator(this) != null) {
data.getIterator(this).removeChangeListener(weakChangeListener);
}
//callUninitialize ();
data = new SettingsAndIterator<Data>(panels, settings);
weakChangeListener = WeakListeners.change(baseListener, data.getIterator(this));
data.getIterator(this).addChangeListener(weakChangeListener);
init = false;
//callInitialize ();
initialized.set( true );
_updateState();
}
/** Set options permitted by the wizard considered as a <code>DialogDescriptor</code>.
* Substitutes tokens such as {@link #NEXT_OPTION} with the actual button.
*
* @param options the options to set
*/
@Override
public void setOptions(Object[] options) {
super.setOptions(convertOptions(options));
}
/**
* @param options the options to set
*/
@Override
public void setAdditionalOptions(Object[] options) {
super.setAdditionalOptions(convertOptions(options));
}
/**
* @param options the options to set
*/
@Override
public void setClosingOptions(Object[] options) {
super.setClosingOptions(convertOptions(options));
}
/** Converts some options.
*/
private Object[] convertOptions(Object[] options) {
Object[] clonedOptions = options.clone();
for (int i = clonedOptions.length - 1; i >= 0; i--) {
if (clonedOptions[i] == NEXT_OPTION) {
clonedOptions[i] = nextButton;
}
if (clonedOptions[i] == PREVIOUS_OPTION) {
clonedOptions[i] = previousButton;
}
if (clonedOptions[i] == FINISH_OPTION) {
clonedOptions[i] = finishButton;
}
if (clonedOptions[i] == CANCEL_OPTION) {
clonedOptions[i] = cancelButton;
}
}
return clonedOptions;
}
/** Overriden to ensure that returned value is one of
* the XXX_OPTION constants.
*/
@Override
public Object getValue() {
return backConvertOption(super.getValue());
}
/** Converts the option back to one of the constants.
* It is called from getValue().
*/
private Object backConvertOption(Object op) {
if (op == nextButton) {
return NEXT_OPTION;
}
if (op == previousButton) {
return PREVIOUS_OPTION;
}
if (op == finishButton) {
return FINISH_OPTION;
}
if (op == cancelButton) {
return CANCEL_OPTION;
}
// if we don't know just return the original value
return op;
}
/** Sets the message format to create title of the wizard.
* The format can take two parameters. The name of the
* current component and the name returned by the iterator that
* defines the order of panels. The default value is something
* like
* <PRE>
* {0} wizard {1}
* </PRE>
* That can be expanded to something like this
* <PRE>
* EJB wizard (1 of 8)
* </PRE>
* This method allows anybody to provide own title format.
*
* @param format message format to the title
*/
public void setTitleFormat(MessageFormat format) {
titleFormat = format;
if (init) {
_updateState();
}
}
/** Getter for current format to be used to format title.
* @return the format
* @see #setTitleFormat
*/
public synchronized MessageFormat getTitleFormat() {
if (titleFormat == null) {
// ok, initialize the default one
titleFormat = new MessageFormat(NbBundle.getMessage(WizardDescriptor.class, "CTL_WizardName"));
}
return titleFormat;
}
/** Allows Panels that use WizardDescriptor as settings object to
* store additional settings into it.
*
* @param name name of the property
* @param value value of property
*/
public void putProperty(final String name, final Object value) {
Object oldValue;
synchronized (this) {
if (properties == null) {
properties = new HashMap<String,Object>(7);
}
oldValue = properties.get(name);
properties.put(name, value);
}
// bugfix #27738, firing changes in a value of the property
firePropertyChange(name, oldValue, value);
if (propListener != null) {
Mutex.EVENT.readAccess(
new Runnable() {
@Override
public void run() {
propListener.propertyChange(new PropertyChangeEvent(this, name, null, null));
}
}
);
}
if (PROP_ERROR_MESSAGE.equals(name)) {
// #76318: New Entity wizard shows unreadable red error
if (init && OK_OPTION.equals (getValue ())) return ; // call getValue() only on initialized WD
if (wizardPanel != null) {
SwingUtilities.invokeLater (new Runnable () {
@Override
public void run () {
if (nextButton.isEnabled () || finishButton.isEnabled ()) {
wizardPanel.setMessage(WizardPanel.MSG_TYPE_WARNING, (String) ((value == null) ? "" : value));
} else {
wizardPanel.setMessage(WizardPanel.MSG_TYPE_ERROR, (String) ((value == null) ? "" : value));
}
}
});
}
}
if (PROP_WARNING_MESSAGE.equals(name) || PROP_INFO_MESSAGE.equals(name)) {
if (wizardPanel != null) {
SwingUtilities.invokeLater (new Runnable () {
@Override
public void run () {
if (PROP_WARNING_MESSAGE.equals(name)) {
wizardPanel.setMessage(WizardPanel.MSG_TYPE_WARNING, (String) ((value == null) ? "" : value)); //NOI18N
} else {
wizardPanel.setMessage(WizardPanel.MSG_TYPE_INFO, (String) ((value == null) ? "" : value)); //NOI18N
}
}
});
}
}
}
/** Getter for stored property.
* @param name name of the property
* @return the value
*/
public synchronized Object getProperty(String name) {
return (properties == null) ? null : properties.get(name);
}
/** Read only map with stored properties.
* @return read only map of properties stored using {@link #putProperty} method
* @since 7.2
*/
public synchronized Map<String,Object> getProperties() {
return properties == null ? Collections.<String,Object>emptyMap() : new HashMap<String,Object>(properties);
}
@Override
public void setHelpCtx(final HelpCtx helpCtx) {
isWizardWideHelpSet = null != helpCtx && !HelpCtx.DEFAULT_HELP.equals( helpCtx );
doSetHelpCtx( helpCtx );
}
private void doSetHelpCtx(final HelpCtx helpCtx ) {
if ((wizardPanel != null) && (helpCtx != null)) {
HelpCtx.setHelpIDString(wizardPanel, helpCtx.getHelpID());
}
// we call the inherited method after setting the ID
// on the panel becuase super.setHelpCtx fires the change
super.setHelpCtx(helpCtx);
}
/** Returns set of newly instantiated objects if the wizard has been correctly finished.
* Returns the empty set as default. If the wizard uses the InstantiatingIterator
* then WizardDescriptor returns a set of Object as same as InstantiatingIterator.instantiate().
*
* @exception IllegalStateException if this method is called on the unfinished wizard
* @return a set of Objects created
* @since 4.41
*/
public Set /*<Object>*/ getInstantiatedObjects() {
//
if (!(FINISH_OPTION.equals(getValue()))) {
throw new IllegalStateException();
}
return newObjects;
}
@Override
void clearMessages () {
putProperty (PROP_ERROR_MESSAGE, null);
}
@Override
void setErrorMessage (String msg) {
putProperty (PROP_ERROR_MESSAGE, msg);
}
@Override
void setInformationMessage (String msg) {
putProperty (PROP_INFO_MESSAGE, msg);
}
@Override
void setWarningMessage (String msg) {
putProperty (PROP_WARNING_MESSAGE, msg);
}
/**
* Subclasses may override updateState() so make sure we always call it
* from EDT.
*/
private synchronized void _updateState() {
if (SwingUtilities.isEventDispatchThread ()) {
updateState();
} else {
SwingUtilities.invokeLater( new Runnable() {
@Override
public void run() {
updateState();
}
});
}
}
/** Updates buttons to reflect the current state of the panels.
* Can be overridden by subclasses
* to change the options to special values. In such a case use:
* <p><code><PRE>
* super.updateState ();
* setOptions (...);
* </PRE></code>
*/
protected synchronized void updateState() {
assert SwingUtilities.isEventDispatchThread();
updateStateOpen (data);
}
private static final Set<String> logged = new HashSet<String>();
@SuppressWarnings("unchecked")
private static void checkComponent(Panel<?> pnl) {
String name = pnl.getClass().getName();
if (pnl instanceof Component && logged.add(name)) {
Logger.getLogger(WizardDescriptor.class.getName()).warning(
name + " is both a " + //NOI18N
"WizardDescriptor.Panel and a Component. This is illegal " + //NOI18N
"because Component.isValid() conflicts with " + //NOI18N
"Panel.isValid(). See umbrella issue 154624 and " + //NOI18N
"issues 150223, 134601, 99680 and " + //NOI18N
"many others for why this is a Bad Thing."); //NOI18N
}
}
private <A> void updateStateOpen(SettingsAndIterator<A> data) {
if (!initialized.get()) { //#220286
return;
}
Panel<A> p = data.getIterator(this).current();
checkComponent(p);
// listeners on the panel
if (data.current != p) {
if (data.current != null) {
// remove
data.current.removeChangeListener(weakChangeListener);
data.current.storeSettings(data.getSettings(this));
}
// Hack - obtain current panel again
// It's here to allow dynamic change of panels in wizard
// (which can be done in storeSettings method)
p = data.getIterator(this).current();
checkComponent(p);
// add to new, detach old change listener and attach new one
data.getIterator(this).removeChangeListener(weakChangeListener);
weakChangeListener = WeakListeners.change(baseListener, p);
data.getIterator(this).addChangeListener(weakChangeListener);
p.addChangeListener(weakChangeListener);
data.current = p;
p.readSettings(data.getSettings(this));
}
// AWT sensitive code
boolean next = data.getIterator(this).hasNext ();
boolean prev = data.getIterator(this).hasPrevious ();
boolean valid = p.isValid () && !validationRuns;
nextButton.setEnabled (next && valid);
previousButton.setEnabled (prev);
cancelButton.setEnabled (true);
if (data.current instanceof FinishablePanel) {
// check if isFinishPanel
if (((FinishablePanel)data.current).isFinishPanel ()) {
finishButton.setEnabled (valid);
} else {
// XXX What if the last panel is not FinishPanel ??? enable ?
finishButton.setEnabled (valid && !next);
}
} else {
// original way
finishButton.setEnabled (
valid &&
(!next || (data.current instanceof FinishPanel))
);
}
if( !isWizardWideHelpSet )
doSetHelpCtx(p.getHelp());
assert SwingUtilities.isEventDispatchThread () : "getComponent() must be called in EQ only.";
java.awt.Component c = p.getComponent();
// end of AWT sensitive code
if ((c == null) || c instanceof java.awt.Window) {
throw new IllegalStateException("Wizard panel " + p + " gave a strange component " + c); // NOI18N
}
if (!init) {
if (c instanceof JComponent) {
autoWizardStyle = getBooleanProperty((JComponent) c, PROP_AUTO_WIZARD_STYLE);
if (autoWizardStyle) {
wizardPanel = new WizardPanel(
getBooleanProperty((JComponent) c, PROP_CONTENT_DISPLAYED),
getBooleanProperty((JComponent) c, PROP_HELP_DISPLAYED),
getBooleanProperty((JComponent) c, PROP_CONTENT_NUMBERED), getLeftDimension((JComponent) c)
);
initBundleProperties();
}
}
if (propListener == null) {
propListener = new PropL();
}
init = true;
}
//update wizardPanel
if (wizardPanel != null) {
Component oldComp = wizardPanel.getRightComponent();
if (oldComp != null) {
oldComp.removePropertyChangeListener(weakPropertyChangeListener);
}
if (c instanceof JComponent) {
setPanelProperties((JComponent) c);
wizardPanel.setContent(contentData);
wizardPanel.setSelectedIndex(contentSelectedIndex);
wizardPanel.setContentBackColor(contentBackColor);
wizardPanel.setContentForegroundColor(contentForegroundColor);
wizardPanel.setImage(image);
wizardPanel.setImageAlignment(imageAlignment);
wizardPanel.setHelpURL(helpURL);
updateButtonAccessibleDescription();
weakPropertyChangeListener = WeakListeners.propertyChange(propListener, c);
c.addPropertyChangeListener(weakPropertyChangeListener);
}
if (wizardPanel.getRightComponent() != c) {
wizardPanel.setRightComponent(c);
if (wizardPanel != getMessage()) {
setMessage(wizardPanel);
} else {
// force revalidate and repaint because the contents of
// wizardPanel has changed. See NbPresenter code
firePropertyChange(DialogDescriptor.PROP_MESSAGE, null, wizardPanel);
}
}
} else if (c != getMessage()) {
setMessage(c);
}
if (!addedWindowListener && getMessage() instanceof Component) {
final Component comp = (Component) getMessage();
// #81938: special handling WizardDescriptor to avoid close wizard during instantiate
comp.addHierarchyListener(new HierarchyListener() {
{
check();
}
@Override public void hierarchyChanged(HierarchyEvent e) {
if ((e.getChangeFlags() & HierarchyEvent.DISPLAYABILITY_CHANGED) != 0) {
check();
}
}
private void check() {
final Window w = SwingUtilities.getWindowAncestor(comp);
if (!addedWindowListener && w != null) {
addedWindowListener = true;
w.addWindowListener(new WindowAdapter() {
@Override public void windowClosing(WindowEvent e) {
if (!changeStateInProgress) {
if (getValue() == null || WizardDescriptor.NEXT_OPTION.equals(getValue())) {
setValue(NotifyDescriptor.CLOSED_OPTION);
}
w.setVisible(false);
w.dispose();
}
}
});
}
}
});
}
String panelName = c.getName();
if (panelName == null) {
panelName = ""; // NOI18N
}
Object[] args = { panelName, data.getIterator(this).name() };
MessageFormat mf = getTitleFormat();
if (autoWizardStyle) {
wizardPanel.setPanelName(mf.format(args));
} else {
setTitle(mf.format(args));
}
Component fo = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
if ((fo != null) && (!fo.isEnabled()) && (wizardPanel != null)) {
wizardPanel.requestFocus();
}
}
// for xtesting usage only
boolean isForwardEnabled () {
return data.getIterator(this).current ().isValid () && !validationRuns;
}
/** Shows blocking wait cursor during updateState run */
private void updateStateWithFeedback() {
if( !SwingUtilities.isEventDispatchThread() ) {
SwingUtilities.invokeLater( new Runnable() {
@Override
public void run() {
updateStateWithFeedback();
}
});
return;
}
try {
showWaitCursor();
updateState();
} finally {
showNormalCursor();
}
}
/** Shows next step in UI of wizards, displays wait cursot during the change.
*/
private void goToNextStep(Dimension previousSize) {
try {
showWaitCursor();
boolean alreadyUpdated = false;
Font controlFont = (Font) UIManager.getDefaults().get("controlFont"); //NOI18N
Integer defaultSize = (Integer) UIManager.get("nbDefaultFontSize");
if (defaultSize == null) {
//Plastic look and feel...
defaultSize = new Integer(11);
}
// enable auto-resizing policy only for fonts bigger thne default
if ((controlFont != null) && (controlFont.getSize() > defaultSize.intValue())) { //NOI18N
Window parentWindow = SwingUtilities.getWindowAncestor((Component) getMessage());
if (parentWindow != null) {
_updateState();
alreadyUpdated = true;
resizeWizard(parentWindow, previousSize);
}
}
if (!alreadyUpdated) {
_updateState();
}
if (wizardPanel != null) {
wizardPanel.requestFocus();
}
} finally {
showNormalCursor();
}
}
/** Tries to resize wizard wisely if needed. Keeps "display inertia" so that
* wizard is only enlarged, not shrinked, and location is changed only when
* wizard window exceeds screen bounds after resize.
*/
private void resizeWizard(Window parentWindow, Dimension prevSize) {
assert SwingUtilities.isEventDispatchThread () : "getComponent() must be called in EQ only.";
Dimension curSize = data.getIterator(this).current().getComponent().getPreferredSize();
// only enlarge if needed, don't shrink
if ((curSize.width > prevSize.width) || (curSize.height > prevSize.height)) {
Rectangle origBounds = parentWindow.getBounds();
int newWidth = origBounds.width;
int newHeight = origBounds.height;
Rectangle screenBounds = Utilities.getUsableScreenBounds();
Rectangle newBounds;
// don't allow to exceed screen size, center if needed
if (((origBounds.x + newWidth) > screenBounds.width) || ((origBounds.y + newHeight) > screenBounds.height)) {
newWidth = Math.min(screenBounds.width, newWidth);
newHeight = Math.min(screenBounds.height, newHeight);
newBounds = Utilities.findCenterBounds(new Dimension(newWidth, newHeight));
} else {
newBounds = new Rectangle(origBounds.x, origBounds.y, newWidth, newHeight);
}
parentWindow.setBounds(newBounds);
parentWindow.invalidate();
parentWindow.validate();
parentWindow.repaint();
}
}
private void showWaitCursor() {
if( !SwingUtilities.isEventDispatchThread() ) {
SwingUtilities.invokeLater( new Runnable() {
@Override
public void run() {
showWaitCursor();
}
});
return;
}
if ((wizardPanel == null) || (wizardPanel.getRootPane() == null)) {
// if none root pane --> don't set wait cursor
return;
}
// bugfix #92539: JR: I don't see the reason this code, I have tried comment out
// Window parentWindow = SwingUtilities.getWindowAncestor((Component) getMessage());
// if (parentWindow != null) {
// parentWindow.setEnabled (false);
// }
//
if (wizardPanel != null) {
// save escapeActionListener for normal state
KeyStroke ks = KeyStroke.getKeyStroke (KeyEvent.VK_ESCAPE, 0);
escapeActionListener = wizardPanel.getRootPane ().getActionForKeyStroke (ks);
wizardPanel.getRootPane ().unregisterKeyboardAction (ks);
}
waitingComponent = wizardPanel.getRootPane().getContentPane();
waitingComponent.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
changeStateInProgress = true;
}
private void showNormalCursor() {
if( !SwingUtilities.isEventDispatchThread() ) {
SwingUtilities.invokeLater( new Runnable() {
@Override
public void run() {
showNormalCursor();
}
} );
return;
}
if (waitingComponent == null) {
// none waitingComponent --> don't change cursor to normal
return;
}
Window parentWindow = SwingUtilities.getWindowAncestor((Component) getMessage());
if (parentWindow != null) {
parentWindow.setEnabled (true);
}
if (wizardPanel != null) {
// set back escapeActionListener as same as NbPresenter does
if (escapeActionListener != null) {
if (wizardPanel.getRootPane () != null) {
wizardPanel.getRootPane ().registerKeyboardAction (escapeActionListener, "Escape",
KeyStroke.getKeyStroke (KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
}
}
wizardPanel.setProgressComponent (null, null);
}
waitingComponent.setCursor(null);
waitingComponent = null;
changeStateInProgress = false;
}
/* commented out - issue #32927. Replaced by javadoc info in WizardDescriptor.Panel
private static final Set warnedPanelIsComponent = new WeakSet(); // Set<Class>
private static synchronized void warnPanelIsComponent(Class c) {
if (warnedPanelIsComponent.add(c)) {
StringBuffer buffer = new StringBuffer(150);
buffer.append("WARNING - the WizardDescriptor.Panel implementation "); // NOI18N
buffer.append(c.getName());
buffer.append(" provides itself as the result of getComponent().\n"); // NOI18N
buffer.append("This hurts performance and can cause a clash when Component.isValid() is overridden.\n"); // NOI18N
buffer.append("Please use a separate component class, see details at http://performance.netbeans.org/howto/dialogs/wizard-panels.html."); // NOI18N
err.log(ErrorManager.WARNING, buffer.toString());
}
}
*/
/** Tryes to get property from getProperty() if doesn't succeed then tryes at
* supplied <CODE>JComponent</CODE>s client property.
* @param c origin of property
* @param s name of property
* @return boolean property
*/
private boolean getBooleanProperty(JComponent c, String s) {
Object property = getProperty(s);
if (property instanceof Boolean) {
return ((Boolean) property).booleanValue();
}
property = c.getClientProperty(s);
if (property instanceof Boolean) {
return ((Boolean) property).booleanValue();
}
return false;
}
/** Tryes to get dimension of wizard panel's left pane from getProperty()
* if doesn't succeed then tryes at
* supplied <CODE>JComponent</CODE>s client property.
* @return <CODE>Dimension</CODE> dimension of wizard panel's left pane
*/
private Dimension getLeftDimension(JComponent c) {
Dimension leftDimension;
Object property = c.getClientProperty(PROP_LEFT_DIMENSION);
if (property instanceof Dimension) {
leftDimension = (Dimension) property;
} else {
leftDimension = new Dimension(198, 233);
}
return leftDimension;
}
/** Tryes to get properties from getProperty() if doesn't succeed then tryes at
* supplied <CODE>JComponent</CODE>s client properties and store them
* to appropriate fields.
* @param c origin of property
* @param s name of property
* @return boolean property
*/
private void setPanelProperties(JComponent c) {
// TODO: Method should be devided into individual setter/getter methods !?
Object property = getProperty(PROP_CONTENT_SELECTED_INDEX);
if (property instanceof Integer) {
contentSelectedIndex = ((Integer) property).intValue();
} else {
property = c.getClientProperty(PROP_CONTENT_SELECTED_INDEX);
if (property instanceof Integer) {
contentSelectedIndex = ((Integer) property).intValue();
}
}
property = getProperty(PROP_CONTENT_DATA);
if (property instanceof String[]) {
contentData = (String[]) property;
} else {
property = c.getClientProperty(PROP_CONTENT_DATA);
if (property instanceof String[]) {
contentData = (String[]) property;
}
}
property = getProperty(PROP_IMAGE);
if (property instanceof Image) {
image = (Image) property;
} else if ((properties == null) || (!properties.containsKey(PROP_IMAGE))) {
property = c.getClientProperty(PROP_IMAGE);
if (property instanceof Image) {
image = (Image) property;
}
}
property = getProperty(PROP_IMAGE_ALIGNMENT);
if (property instanceof String) {
imageAlignment = (String) property;
} else {
property = c.getClientProperty(PROP_IMAGE_ALIGNMENT);
if (property instanceof String) {
imageAlignment = (String) property;
}
}
property = getProperty(PROP_CONTENT_BACK_COLOR);
if (property instanceof Color) {
contentBackColor = (Color) property;
} else {
property = c.getClientProperty(PROP_CONTENT_BACK_COLOR);
if (property instanceof Color) {
contentBackColor = (Color) property;
}
}
property = getProperty(PROP_CONTENT_FOREGROUND_COLOR);
if (property instanceof Color) {
contentForegroundColor = (Color) property;
} else {
property = c.getClientProperty(PROP_CONTENT_FOREGROUND_COLOR);
if (property instanceof Color) {
contentForegroundColor = (Color) property;
}
}
property = c.getClientProperty(PROP_HELP_URL);
if (property instanceof URL) {
helpURL = (URL) property;
} else if (property == null) {
helpURL = null;
}
}
private void initBundleProperties() {
contentBackColor = new Color(
getIntFromBundle("INT_WizardBackRed"), // NOI18N
getIntFromBundle("INT_WizardBackGreen"), // NOI18N
getIntFromBundle("INT_WizardBackBlue") // NOI18N
); // NOI18N
if( Color.white.equals( contentBackColor ) ) {
Color c = UIManager.getColor( "Tree.background" ); //NOI18N
if( null != c )
contentBackColor = new Color( c.getRGB() );
}
contentForegroundColor = new Color(
getIntFromBundle("INT_WizardForegroundRed"), // NOI18N
getIntFromBundle("INT_WizardForegroundGreen"), // NOI18N
getIntFromBundle("INT_WizardForegroundBlue") // NOI18N
); // NOI18N
if( Color.black.equals( contentForegroundColor ) ) {
Color c = UIManager.getColor( "Tree.foreground" ); //NOI18N
if( null != c )
contentForegroundColor = new Color( c.getRGB() );
}
imageAlignment = bundle.getString("STRING_WizardImageAlignment"); //NOI18N
}
/** Overrides superclass method. Adds reseting of wizard
* for <code>CLOSED_OPTION</code>. */
@Override
public void setValue(Object value) {
setValueOpen(value, data);
}
private <A> void setValueOpen(Object value, SettingsAndIterator<A> data) {
Object convertedValue = backConvertOption(value);
// set new value w/o fire PROP_VALUE change
Object oldValue = getValue();
setValueWithoutPCH(convertedValue);
// #17360: Reset wizard on CLOSED_OPTION too.
if (CLOSED_OPTION.equals(convertedValue)) {
try {
resetWizard();
} catch (RuntimeException x) {
// notify to log
err.log(Level.INFO, null, x);
}
} else if (FINISH_OPTION.equals(convertedValue) || NEXT_OPTION.equals(convertedValue)) {
//do not fire prop change event yet, panel data must be validate and stored first
return;
}
// notify listeners about PROP_VALUE change
firePropertyChange(PROP_VALUE, oldValue, convertedValue);
}
/** Resets wizard when after closed/cancelled/finished the wizard dialog. */
private void resetWizard() {
resetWizardOpen(data);
}
private <A> void storeSettingsAndNotify(SettingsAndIterator<A> data) {
if (data.current != null) {
data.current.storeSettings(data.getSettings(this));
}
firePropertyChange(PROP_VALUE, null, NEXT_OPTION);
}
private <A> void resetWizardOpen(SettingsAndIterator<A> data) {
if (data.current != null) {
data.current.storeSettings(data.getSettings(this));
data.current.removeChangeListener(weakChangeListener);
data.current = null;
if (wizardPanel != null) {
wizardPanel.resetPreferredSize();
}
}
callUninitialize();
// detach the change listener at the end of wizard
data.getIterator(this).removeChangeListener(weakChangeListener);
}
private int getIntFromBundle(String key) {
return Integer.parseInt(bundle.getString(key));
}
private static Image getDefaultImage() {
return ImageUtilities.loadImage("org/netbeans/modules/dialogs/defaultWizard.gif", true);
}
private void updateButtonAccessibleDescription() {
assert SwingUtilities.isEventDispatchThread() : "Call only in AWT queue.";
String stepName = ((contentData != null) && (contentSelectedIndex > 0) &&
((contentSelectedIndex - 1) < contentData.length)) ? contentData[contentSelectedIndex - 1] : ""; // NOI18N
try {
previousButton.getAccessibleContext().setAccessibleDescription(
NbBundle.getMessage(WizardDescriptor.class, "ACSD_PREVIOUS", new Integer(contentSelectedIndex), stepName)
);
} catch (IllegalArgumentException iae) {
err.log (Level.INFO, iae.getLocalizedMessage() + " while setting ACSD_PREVIOUS with params " + stepName + ", " + contentSelectedIndex, iae); // NOI18N
}
stepName = ((contentData != null) && (contentSelectedIndex < (contentData.length - 1)) &&
((contentSelectedIndex + 1) >= 0)) ? contentData[contentSelectedIndex + 1] : ""; // NOI18N
try {
nextButton.getAccessibleContext().setAccessibleDescription(
NbBundle.getMessage(WizardDescriptor.class, "ACSD_NEXT", new Integer(contentSelectedIndex + 2), stepName)
);
} catch (IllegalArgumentException iae) {
err.log (Level.INFO, iae.getLocalizedMessage() + " while setting ACSD_NEXT with params " + stepName + ", " + (contentSelectedIndex + 2), iae); // NOI18N
}
}
private void lazyValidate(final WizardDescriptor.Panel panel, final Runnable onValidPerformer) {
Runnable validationPeformer = new Runnable() {
@Override
public void run() {
err.log (Level.FINE, "validationPeformer entry."); // NOI18N
ValidatingPanel v = (ValidatingPanel) panel;
try {
// try validation current panel
if (currentPanelWasChangedWhileStoreSettings) {
err.log (Level.FINE, "validationPeformer interupt because currentPanelWasChangedWhileStoreSettings"); // NOI18N
currentPanelWasChangedWhileStoreSettings = false;
} else {
v.validate();
err.log (Level.FINE, "validation passed successfully."); // NOI18N
}
validationRuns = false;
// validation was successful
if (SwingUtilities.isEventDispatchThread ()) {
err.log (Level.FINE, "Runs onValidPerformer directly in EDT."); // NOI18N
onValidPerformer.run();
} else {
err.log (Level.FINE, "invokeLater onValidPerformer."); // NOI18N
SwingUtilities.invokeLater (new Runnable () {
@Override
public void run () {
if( initialized.get() ) { //#220286
err.log (Level.FINE, "Runs onValidPerformer from invokeLater."); // NOI18N
if( panel instanceof ExtendedAsynchronousValidatingPanel ) {
((ExtendedAsynchronousValidatingPanel)panel).finishValidation();
}
onValidPerformer.run();
}
}
});
}
} catch (final WizardValidationException wve) {
validationRuns = false;
err.log (Level.FINE, "validation failed", wve); // NOI18N
if( FINISH_OPTION.equals( getValue() ) )
setValue( getDefaultValue() );
SwingUtilities.invokeLater( new Runnable() {
@Override
public void run() {
if( panel instanceof ExtendedAsynchronousValidatingPanel ) {
((ExtendedAsynchronousValidatingPanel)panel).finishValidation();
}
onValidationFailed( wve );
}
});
}
}
};
if (panel instanceof AsynchronousValidatingPanel) {
AsynchronousValidatingPanel p = (AsynchronousValidatingPanel) panel;
validationRuns = true; // disable Next> Finish buttons
p.prepareValidation();
err.log (Level.FINE, "Do ASYNCHRONOUS_JOBS_RP.post(validationPeformer)."); // NOI18N
updateStateWithFeedback ();
backgroundValidationTask = ASYNCHRONOUS_JOBS_RP.post(validationPeformer);
} else if (panel instanceof ValidatingPanel) {
validationRuns = true;
err.log (Level.FINE, "Runs validationPeformer."); // NOI18N
validationPeformer.run();
} else {
err.log (Level.FINE, "Runs onValidPerformer."); // NOI18N
onValidPerformer.run();
}
}
private void onValidationFailed( final WizardValidationException wve ) {
assert SwingUtilities.isEventDispatchThread();
_updateState ();
//delay the display of error message as isValid() is called in _updateState() above
//and the clients may set their own error/warning messages which are actually invokedLater()
//(otherwise the validation message just flashes briefly and is replaced with
//whatever is provided by the current panel's validation)
SwingUtilities.invokeLater( new Runnable() {
@Override
public void run() {
// cannot continue, notify user
if (wizardPanel != null) {
wizardPanel.setMessage(WizardPanel.MSG_TYPE_ERROR, wve.getLocalizedMessage());
}
}
});
// focus source of this problem
JComponent srcComp = wve.getSource();
if (srcComp != null && srcComp.isFocusable()) {
srcComp.requestFocus();
}
}
// helper methods which call to InstantiatingIterator
private void callInitialize() {
assert data.getIterator(this) != null;
initialized.set( true );
if (data.getIterator(this) instanceof InstantiatingIterator) {
((InstantiatingIterator) data.getIterator(this)).initialize(this);
}
newObjects = Collections.emptySet();
}
private void callUninitialize() {
assert data.getIterator(this) != null;
initialized.set( false );
if (data.getIterator(this) instanceof InstantiatingIterator) {
((InstantiatingIterator) data.getIterator(this)).uninitialize(this);
}
}
private void callInstantiate() throws IOException {
callInstantiateOpen(data);
}
private <A> void callInstantiateOpen(SettingsAndIterator<A> data) throws IOException {
Iterator<A> panels = data.getIterator(this);
assert panels != null;
if (panels instanceof BackgroundInstantiatingIterator) {
err.fine("is BackgroundInstantiatingIterator");
} else if (panels instanceof ProgressInstantiatingIterator) {
err.fine("is ProgressInstantiatingIterator");
handle = ProgressHandleFactory.createHandle (PROGRESS_BAR_DISPLAY_NAME);
final JComponent progressComp = ProgressHandleFactory.createProgressComponent (handle);
final JLabel detailComp = ProgressHandleFactory.createDetailLabelComponent (handle);
Mutex.EVENT.readAccess( new Runnable() {
@Override
public void run() {
if (wizardPanel != null) {
wizardPanel.setProgressComponent (progressComp, detailComp);
}
}
});
err.log (Level.FINE, "Show progressPanel controlled by iterator later.");
} else if (panels instanceof AsynchronousInstantiatingIterator) {
err.fine("is AsynchronousInstantiatingIterator");
handle = ProgressHandleFactory.createHandle (PROGRESS_BAR_DISPLAY_NAME);
final JComponent progressComp = ProgressHandleFactory.createProgressComponent (handle);
final JLabel mainLabelComp = ProgressHandleFactory.createMainLabelComponent (handle);
Mutex.EVENT.readAccess( new Runnable() {
@Override
public void run() {
if (wizardPanel != null) {
wizardPanel.setProgressComponent (progressComp, mainLabelComp);
}
}
});
handle.start ();
err.log (Level.FINE, "Show progressPanel later.");
}
// bugfix #44444, force store settings before do instantiate new objects
panels.current().storeSettings(data.getSettings(this));
if (panels instanceof InstantiatingIterator) {
showWaitCursor();
try {
assert ! (panels instanceof AsynchronousInstantiatingIterator) || ! SwingUtilities.isEventDispatchThread () : "Cannot invoked within EDT if AsynchronousInstantiatingIterator!";
if (panels instanceof ProgressInstantiatingIterator) {
assert handle != null : "ProgressHandle must be not null.";
err.log (Level.FINE, "Calls instantiate(ProgressHandle) on iterator: " + panels.getClass ().getName ());
newObjects = ((ProgressInstantiatingIterator) panels).instantiate (handle);
} else {
err.log (Level.FINE, "Calls instantiate() on iterator: " + panels.getClass ().getName ());
newObjects = ((InstantiatingIterator) panels).instantiate ();
}
} finally {
// set cursor back to normal
showNormalCursor();
}
}
}
private static Font doDeriveFont(Font original, int style) {
if (Utilities.isMac()) {
// don't use deriveFont() - see #49973 for details
return new Font(original.getName(), style, original.getSize());
}
return original.deriveFont(style);
}
/**
* Moves the wizard to its next panel - if Next button is enabled.
* Always call this method from EDT thread.
* @since 7.19
*/
public final void doNextClick() {
assert SwingUtilities.isEventDispatchThread();
if (nextButton.isEnabled()) {
nextButton.doClick();
}
}
/**
* Moves the wizard to its previous panel - if Previous button is enabled.
* Always call this method from EDT thread.
* @since 7.19
*/
public final void doPreviousClick() {
assert SwingUtilities.isEventDispatchThread();
if (previousButton.isEnabled()) {
previousButton.doClick();
}
}
/**
* Finishes the wizard - if Finish button is enabled.
* Always call this method from EDT thread.
* @since 7.19
*/
public final void doFinishClick() {
assert SwingUtilities.isEventDispatchThread();
if (finishButton.isEnabled()) {
finishButton.doClick();
}
}
/**
* Cancels the wizard - if Cancel button is enabled.
* Always call this method from EDT thread.
* @since 7.19
*/
public final void doCancelClick() {
assert SwingUtilities.isEventDispatchThread();
if (cancelButton.isEnabled()) {
cancelButton.doClick();
}
}
// helper method, might be removed from code
// returns false if Next button is disabled
final boolean isNextEnabled() {
return nextButton.isEnabled();
}
// helper method, might be removed from code
// returns false if Finish button is disabled
final boolean isFinishEnabled() {
return finishButton.isEnabled();
}
/** Iterator on the sequence of panels.
* @see WizardDescriptor.Panel
*/
public interface Iterator<Data> {
/** Get the current panel.
* @return the panel
*/
public Panel<Data> current();
/** Get the name of the current panel.
* @return the name
*/
public String name();
/** Test whether there is a next panel.
* @return <code>true</code> if so
*/
public boolean hasNext();
/** Test whether there is a previous panel.
* @return <code>true</code> if so
*/
public boolean hasPrevious();
/** Move to the next panel.
* I.e. increment its index, need not actually change any GUI itself.
* @exception NoSuchElementException if the panel does not exist
*/
public void nextPanel();
/** Move to the previous panel.
* I.e. decrement its index, need not actually change any GUI itself.
* @exception NoSuchElementException if the panel does not exist
*/
public void previousPanel();
/** Add a listener to changes of the current panel.
* The listener is notified when the possibility to move forward/backward changes.
* @param l the listener to add
*/
public void addChangeListener(ChangeListener l);
/** Remove a listener to changes of the current panel.
* @param l the listener to remove
*/
public void removeChangeListener(ChangeListener l);
}
/** One wizard panel with a component on it.
*
* For good performance, implementation of this interface should be as
* lightweight as possible. Defer creation and initialization of
* UI component of wizard panel into {@link #getComponent} method.
*
* Please see complete guide at http://performance.netbeans.org/howto/dialogs/wizard-panels.html
*/
public interface Panel<Data> {
/** Get the component displayed in this panel.
*
* Note; method can be called from any thread, but not concurrently
* with other methods of this interface. Please see complete guide at
* http://performance.netbeans.org/howto/dialogs/wizard-panels.html for
* correct implementation.
*
* @return the UI component of this wizard panel
*/
public java.awt.Component getComponent();
/** Help for this panel.
* When the panel is active, this is used as the help for the wizard dialog.
* @return the help or <code>null</code> if no help is supplied
*/
public HelpCtx getHelp();
/** Provides the wizard panel with the current data--either
* the default data or already-modified settings, if the user used the previous and/or next buttons.
* This method can be called multiple times on one instance of <code>WizardDescriptor.Panel</code>.
* <p>The settings object is originally supplied to {@link WizardDescriptor#WizardDescriptor(WizardDescriptor.Iterator,Object)}.
* In the case of a <code>TemplateWizard.Iterator</code> panel, the object is
* in fact the <code>TemplateWizard</code>.
* @param settings the object representing wizard panel state
* @exception IllegalStateException if the the data provided
* by the wizard are not valid.
*/
public void readSettings(Data settings);
/** Provides the wizard panel with the opportunity to update the
* settings with its current customized state.
* Rather than updating its settings with every change in the GUI, it should collect them,
* and then only save them when requested to by this method.
* Also, the original settings passed to {@link #readSettings} should not be modified (mutated);
* rather, the object passed in here should be mutated according to the collected changes,
* in case it is a copy.
* This method can be called multiple times on one instance of <code>WizardDescriptor.Panel</code>.
* <p>The settings object is originally supplied to {@link WizardDescriptor#WizardDescriptor(WizardDescriptor.Iterator,Object)}.
* In the case of a <code>TemplateWizard.Iterator</code> panel, the object is
* in fact the <code>TemplateWizard</code>.
* @param settings the object representing wizard panel state
*/
public void storeSettings(Data settings);
/** Test whether the panel is finished and it is safe to proceed to the next one.
* If the panel is valid, the "Next" (or "Finish") button will be enabled.
* <p><strong>Tip:</strong> if your panel is actually the component itself
* (so {@link #getComponent} returns <code>this</code>), be sure to specifically
* override this method, as the unrelated implementation in {@link java.awt.Component#isValid}
* if not overridden could cause your wizard to behave erratically.
* @return <code>true</code> if the user has entered satisfactory information
*/
public boolean isValid();
/** Add a listener to changes of the panel's validity.
* @param l the listener to add
* @see #isValid
*/
public void addChangeListener(ChangeListener l);
/** Remove a listener to changes of the panel's validity.
* @param l the listener to remove
*/
public void removeChangeListener(ChangeListener l);
}
/** A special interface for panels in middle of the
* iterators path that would like to have the finish button
* enabled. So both Next and Finish are enabled on panel
* implementing this interface.
* @deprecated 4.28 Use FinishablePanel instead.
*/
@Deprecated
public interface FinishPanel<Data> extends Panel<Data> {
}
/** A special interface for panels that need to do additional
* validation when Next or Finish button is clicked.
* @since 4.28
*/
public interface ValidatingPanel<Data> extends Panel<Data> {
/**
* Is called when Next of Finish buttons are clicked and
* allows deeper check to find out that panel is in valid
* state and it is ok to leave it.
*
* @throws WizardValidationException when validation fails
* @since 4.28
*/
public void validate() throws WizardValidationException;
}
/**
* A special interface for panels that need to do additional
* asynchronous validation when Next or Finish button is clicked.
*
* <p>During background validation is Next or Finish button
* disabled. On validation success wizard automatically
* progress to next panel or finishes.
*
* <p>During background validation Cancel button is hooked
* to signal the validation thread using interrupt().
*
* <p>Note: It is recommended to use ExtendedAsynchronousValidatingPanel instead
* as it adds a method to conveniently unlock wizard's user interface when
* the validation is finished.
*
* @since 6.2 (16 May 2005)
*/
public interface AsynchronousValidatingPanel<Data> extends ValidatingPanel<Data> {
/**
* Called synchronously from UI thread when Next
* of Finish buttons clicked. It allows to lock user
* input to assure official data for background validation.
*/
public void prepareValidation();
/**
* Is called in separate thread when Next of Finish buttons
* are clicked and allows deeper check to find out that panel
* is in valid state and it is ok to leave it.
*
* @throws WizardValidationException when validation fails
*/
@Override
public void validate() throws WizardValidationException;
}
/**
* A special interface for panels that need to do additional
* asynchronous validation when Next or Finish button is clicked.
*
* <p>During background validation is Next or Finish button
* disabled. On validation success wizard automatically
* progress to next panel or finishes.
*
* <p>During background validation Cancel button is hooked
* to signal the validation thread using interrupt().
*
* @param <Data>
*
* @since 7.31
*/
public interface ExtendedAsynchronousValidatingPanel<Data> extends AsynchronousValidatingPanel<Data> {
/**
* Called synchronously from UI thread when Next
* of Finish buttons clicked. It allows to lock user
* input to assure official data for background validation.
*/
@Override
public void prepareValidation();
/**
* Is called in separate thread when Next of Finish buttons
* are clicked and allows deeper check to find out that panel
* is in valid state and it is ok to leave it.
*
* @throws WizardValidationException when validation fails
*/
@Override
public void validate() throws WizardValidationException;
/**
* Called synchronously from UI thread when the background validation
* is finished (even when throwing validation exception).
* It allows to enable user input locked in prepareValidation() method.
*/
public void finishValidation();
}
/** A special interface for panel that needs to dynamically enabled
* Finish button.
* @since 4.28
*/
public interface FinishablePanel<Data> extends Panel<Data> {
/** Specify if this panel would enable Finish button. Finish button is
* enabled if and only if isValid() returns true and isFinishPanel()
* returns true.
*
* @return Finish button could be enabled
* @since 4.28
*/
boolean isFinishPanel();
}
/**
* Iterator for a wizard that needs to somehow instantiate new objects.
* (This interface can replace
* <a href="@org-openide-loaders@/org/openide/loaders/TemplateWizard.Iterator.html"><code>TemplateWizard.Iterator</code></a>
* in a template's declaration.)
* @param <Data> in practice this should be {@link WizardDescriptor}
* @since org.openide/1 4.33
*/
public interface InstantiatingIterator<Data> extends Iterator<Data> {
/** Returns set of instantiated objects. If instantiation fails then wizard remains open to enable correct values.
*
* @throws IOException
* @return a set of objects created (the exact type is at the discretion of the caller)
*/
public Set/*<?>*/ instantiate() throws IOException;
/** Initializes this iterator, called from WizardDescriptor's constructor.
*
* @param wizard wizard's descriptor
*/
public void initialize(WizardDescriptor wizard);
/** Uninitializes this iterator, called when the wizard is being
* closed, no matter what closing option invoked.
*
* @param wizard wizard's descriptor
*/
public void uninitialize(WizardDescriptor wizard);
}
/**
* Iterator for a wizard that needs to somehow instantiate new objects outside ATW queue.
* (This interface can replace
* <a href="@org-openide-loaders@/org/openide/loaders/TemplateWizard.Iterator.html"><code>TemplateWizard.Iterator</code></a>
* in a template's declaration.)
* @param <Data> in practice this should be {@link WizardDescriptor}
* @since org.openide/1 6.5
*/
public interface AsynchronousInstantiatingIterator<Data> extends InstantiatingIterator<Data> {
/**
* Is called in separate thread when the Finish button
* are clicked and allows implement asynchronous
* instantating of newly created objects.
*
* @throws IOException when instantiate fails
* @return a set of objects created (the exact type is at the discretion of the caller)
*/
@Override public Set/*<?>*/ instantiate () throws IOException;
}
/**
* Iterator for a wizard that will create new objects after the wizard has been closed.
* Suitable for cases where the instantiation might be quite time consuming, has its own progress/cancellation UI,
* or otherwise would be undesirable to run with the wizard dialog open.
* @param <Data> in practice this should be {@link WizardDescriptor}
* @since org.openide.dialogs 7.22
*/
public interface BackgroundInstantiatingIterator<Data> extends AsynchronousInstantiatingIterator<Data> {
/**
* Called in a separate thread when the Finish button is clicked and the wizard is closed.
* @return a set of objects created (the exact type is at the discretion of the caller)
* @throws IOException when instantiate fails
*/
@Override Set/*<?>*/ instantiate() throws IOException;
}
/**
* Iterator for a wizard that wants to notify users while instantiate is running by a progress bar.
* The method <code>instantiate</code> is called outside ATW queue.
* (This interface can replace
* <a href="@org-openide-loaders@/org/openide/loaders/TemplateWizard.Iterator.html"><code>TemplateWizard.Iterator</code></a>
* in a template's declaration.)
* @param <Data> in practice this should be {@link WizardDescriptor}
* @since org.openide.dialogs 7.1
*/
public interface ProgressInstantiatingIterator<Data> extends AsynchronousInstantiatingIterator<Data> {
/**
* Is called in separate thread when the Finish button
* are clicked and allows implement asynchronous instantating of newly created objects.
* While instantiating users are notified by progress bar in wizard's panel. Notfication will
* be visualized by a progress bar.
* Note: The <code>ProgressHandle</code> is not started, need to start it and report progress by
* messages in the <code>progress()</code> method.
*
* @param handle progress bar handle
* @throws IOException when instantiate fails
* @return a set of objects created (the exact type is at the discretion of the caller)
*/
public Set/*<?>*/ instantiate (ProgressHandle handle) throws IOException;
}
/** Special iterator that works on an array of <code>Panel</code>s.
*/
public static class ArrayIterator<Data> extends Object implements Iterator<Data> {
/** Array of items.
*/
private Panel<Data>[] panels;
/** Index into the array
*/
private int index;
/* Default constructor. It's here to allow subclasses to
* be serializable easily. Panel initialization is done
* through initializePanels() protected method. */
public ArrayIterator() {
panels = initializePanels();
index = 0;
}
/** Construct an iterator.
* @param array the list of panels to use
*/
public ArrayIterator(Panel<Data>[] array) {
panels = array;
index = 0;
}
/**
* Construct an iterator.
* @param panels the list of panels to use
* @since org.openide.dialogs 7.5
*/
@SuppressWarnings("unchecked") // exists so that other code does not have to do it
public ArrayIterator(List<Panel<Data>> panels) {
this.panels = panels.toArray(new Panel[panels.size()]);
index = 0;
}
/** Allows subclasses to initialize their arrays of panels when
* constructed using default constructor.
* (for example during deserialization.
* Default implementation returns empty array. */
@SuppressWarnings("unchecked")
protected Panel<Data>[] initializePanels() {
return new Panel[0];
}
/* The current panel.
*/
@Override
public Panel<Data> current() {
return panels[index];
}
/* Current name of the panel */
@Override
public String name() {
return NbBundle.getMessage(WizardDescriptor.class, "CTL_ArrayIteratorName", index + 1, panels.length);
}
/* Is there a next panel?
* @return true if so
*/
@Override
public boolean hasNext() {
return index < (panels.length - 1);
}
/* Is there a previous panel?
* @return true if so
*/
@Override
public boolean hasPrevious() {
return index > 0;
}
/* Moves to the next panel.
* @exception NoSuchElementException if the panel does not exist
*/
@Override
public synchronized void nextPanel() {
if ((index + 1) == panels.length) {
throw new java.util.NoSuchElementException();
}
index++;
}
/* Moves to previous panel.
* @exception NoSuchElementException if the panel does not exist
*/
@Override
public synchronized void previousPanel() {
if (index == 0) {
throw new java.util.NoSuchElementException();
}
index--;
}
/* Ignores the listener, there are no changes in order of panels.
*/
@Override
public void addChangeListener(ChangeListener l) {
}
/* Ignored.
*/
@Override
public void removeChangeListener(ChangeListener l) {
}
/** Resets this iterator to initial state.
* Called by subclasses when they need re-initialization of the iterator.
*/
protected void reset() {
index = 0;
}
}
/** Listener to changes in the iterator and panels.
*/
private final class Listener implements ChangeListener, ActionListener {
Listener() {
}
/** Change in the observed objects */
@Override
public void stateChanged(ChangeEvent ev) {
_updateState();
}
/** Action listener */
@Override
public void actionPerformed(ActionEvent ev) {
final Iterator<?> panels = data.getIterator(WizardDescriptor.this);
if (wizardPanel != null) {
wizardPanel.setMessage(WizardPanel.MSG_TYPE_ERROR, ""); //NOI18N
}
Object src = ev.getSource();
err.log (Level.FINE, "actionPerformed entry. Source: " + src); // NOI18N
if (src == nextButton) {
assert SwingUtilities.isEventDispatchThread () : "getComponent() must be called in EQ only.";
final Dimension previousSize = panels.current().getComponent().getSize();
Runnable onValidPerformer = new Runnable() {
@Override
public void run() {
err.log(Level.FINE,
"onValidPerformer on next button entry.");
//#163078 - validate first then store
storeSettingsAndNotify(data);
panels.nextPanel();
try {
// change UI to show next step, show wait cursor during
// the change
goToNextStep(previousSize);
}
catch (IllegalStateException ise) {
panels.previousPanel();
String msg = ise.getMessage();
if (msg != null) {
// this is only for backward compatitility
DialogDisplayer.getDefault().notify(new NotifyDescriptor.Message(msg));
} else {
// this should be used (it checks for exception
// annotations and severity)
Exceptions.printStackTrace(ise);
}
_updateState();
}
err.log(Level.FINE,
"onValidPerformer on next button exit.");
}
};
lazyValidate(panels.current(), onValidPerformer);
}
if (ev.getSource() == previousButton) {
panels.previousPanel();
// show wait cursor when updating previous button
updateStateWithFeedback();
}
if (ev.getSource() == finishButton) {
Runnable onValidPerformer = new Runnable() {
@Override
public void run() {
err.log (Level.FINE, "onValidPerformer on finish button entry."); // NOI18N
// disable all buttons to indicate that instantiate runs
previousButton.setEnabled (false);
nextButton.setEnabled (false);
finishButton.setEnabled (false);
cancelButton.setEnabled (false);
Runnable performFinish = new Runnable () {
@Override
public void run () {
err.log (Level.FINE, "performFinish entry."); // NOI18N
Object oldValue = getValue();
// do instantiate
try {
callInstantiate();
setValueWithoutPCH(OK_OPTION);
resetWizard();
} catch (IOException ioe) {
// notify to log
err.log(Level.INFO, null, ioe);
setValueWithoutPCH(NEXT_OPTION);
updateStateWithFeedback();
// notify user by the wizard's status line
putProperty(PROP_ERROR_MESSAGE, ioe.getLocalizedMessage());
// if validation failed => cannot move to next panel
return;
} catch (RuntimeException x) {
// notify to log
err.log(Level.WARNING, null, x);
setValueWithoutPCH(NEXT_OPTION);
updateStateWithFeedback();
// notify user by the wizard's status line
putProperty(PROP_ERROR_MESSAGE, x.getLocalizedMessage());
// if validation failed => cannot move to next panel
return;
}
firePropertyChange(PROP_VALUE, oldValue, OK_OPTION);
SwingUtilities.invokeLater (new Runnable () {
@Override
public void run () {
// all is OK
// close wizrad
err.log (Level.FINE, "WD.finishOption.fireActionPerformed()");
finishOption.fireActionPerformed();
err.log (Level.FINE, "Set value to OK_OPTION.");
setValue (OK_OPTION);
}
});
err.log (Level.FINE, "performFinish exit."); // NOI18N
}
};
if (panels instanceof AsynchronousInstantiatingIterator) {
err.log (Level.FINE, "Do ASYNCHRONOUS_JOBS_RP.post(performFinish)."); // NOI18N
ASYNCHRONOUS_JOBS_RP.post (performFinish);
if (panels instanceof BackgroundInstantiatingIterator) {
Window parentWindow = SwingUtilities.getWindowAncestor((Component) getMessage());
if (parentWindow != null) {
parentWindow.setVisible(false);
} else {
err.log(Level.WARNING, "could not find parent window of {0}", getMessage());
}
}
} else {
err.log (Level.FINE, "Run performFinish."); // NOI18N
performFinish.run ();
}
err.log(Level.FINE, "onValidPerformer on finish button exit on {0}", panels);
}
};
lazyValidate(panels.current(), onValidPerformer);
}
if (ev.getSource() == cancelButton) {
if (backgroundValidationTask != null) {
backgroundValidationTask.cancel();
}
Object oldValue = getValue();
setValueWithoutPCH(CANCEL_OPTION);
if (Arrays.asList(getClosingOptions()).contains(cancelButton)) {
try {
resetWizard();
} catch (RuntimeException x) {
// notify to log
err.log(Level.INFO, null, x);
}
}
firePropertyChange(PROP_VALUE, oldValue, CANCEL_OPTION);
}
}
}
/** Listenes on a users client property changes
*/
private class PropL implements PropertyChangeListener {
PropL() {
}
/** Accepts client property changes of user component */
@Override
public void propertyChange(final PropertyChangeEvent e) {
if (wizardPanel == null) {
return;
}
if (! SwingUtilities.isEventDispatchThread()) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
propertyChange(e);
}
});
return ;
}
String propName = e.getPropertyName();
setPanelProperties((JComponent) wizardPanel.getRightComponent());
if (PROP_CONTENT_DATA.equals(propName)) {
wizardPanel.setContent(contentData);
updateButtonAccessibleDescription();
} else if (PROP_CONTENT_SELECTED_INDEX.equals(propName)) {
wizardPanel.setSelectedIndex(contentSelectedIndex);
updateButtonAccessibleDescription();
} else if (PROP_CONTENT_BACK_COLOR.equals(propName)) {
wizardPanel.setContentBackColor(contentBackColor);
} else if (PROP_CONTENT_FOREGROUND_COLOR.equals(propName)) {
wizardPanel.setContentForegroundColor(contentForegroundColor);
} else if (PROP_IMAGE.equals(propName)) {
wizardPanel.setImage(image);
} else if (PROP_IMAGE_ALIGNMENT.equals(propName)) {
wizardPanel.setImageAlignment(imageAlignment);
} else if (PROP_HELP_URL.equals(propName)) {
wizardPanel.setHelpURL(helpURL);
}
}
}
// end of calling to InstantiatingIterator
/** Panel which paints image as its background.
*/
private static class ImagedPanel extends JComponent implements Accessible, Runnable {
/** background image */
Image image;
/** helper variables for passing image between threads and painting
* methods */
Image tempImage;
/** helper variables for passing image between threads and painting
* methods */
Image image2Load;
/** true if default image is used */
boolean isDefault = false;
/** true if loading of image is in progress, false otherwise */
boolean loadPending = false;
boolean north = true;
/** sync lock for image variables access */
private final Object IMAGE_LOCK = new Object();
/** Constrcuts panel with given image on background.
* @param im background image, null means default image
*/
public ImagedPanel(Image im) {
setImage(im);
setLayout(new BorderLayout());
setOpaque(true);
}
/** Overriden to paint backround image */
@Override
protected void paintComponent(Graphics graphics) {
graphics.setColor(getBackground());
graphics.fillRect(0, 0, getWidth(), getHeight());
if (image != null) {
graphics.drawImage(image, 0, north ? 0 : (getHeight() - image.getHeight(null)), this);
} else if (image2Load != null) {
loadImageInBackground(image2Load);
image2Load = null;
}
}
public void setImageAlignment(String align) {
north = "North".equals(align); // NOI18N
}
/** Sets background image for this component. Image will be loaded
* asynchronously if not loaded yet. Null means default image.
*/
public void setImage(Image im) {
if (im != null) {
loadImage(im);
isDefault = false;
return;
}
if (!isDefault) {
if( !UIManager.getBoolean( "nb.wizard.hideimage" ) ) //NOI18N
loadImage(getDefaultImage());
isDefault = true;
}
}
private void loadImage(Image im) {
// check image and just set variable if fully loaded already
MediaTracker mt = new MediaTracker(this);
mt.addImage(im, 0);
if (mt.checkID(0)) {
image = im;
if (isShowing()) {
repaint();
}
return;
}
// start loading in background or just mark that loading should
// start when paint is invoked
if (isShowing()) {
loadImageInBackground(im);
} else {
synchronized (IMAGE_LOCK) {
image = null;
}
image2Load = im;
}
}
private void loadImageInBackground(Image image) {
synchronized (IMAGE_LOCK) {
tempImage = image;
// coalesce with previous task if hasn't really started yet
if (loadPending) {
return;
}
loadPending = true;
}
// 30ms is safety time to ensure code will run asynchronously
RequestProcessor.getDefault().post(this, 30);
}
/** Loads image stored in image2Load variable.
* Then invokes repaint when image is fully loaded.
*/
@Override
public void run() {
Image localImage;
// grab value
synchronized (IMAGE_LOCK) {
localImage = tempImage;
tempImage = null;
loadPending = false;
}
// actually loads image
ImageIcon localImageIcon = new ImageIcon(localImage);
boolean shouldRepaint = false;
synchronized (IMAGE_LOCK) {
// don't commit results if another loading was started after us
if (!loadPending) {
image = localImageIcon.getImage();
// keep repaint call out of sync section
shouldRepaint = true;
}
}
if (shouldRepaint) {
repaint();
}
}
}
/** Text list cell renderer. Wraps text of items at specified width. Allows numbering
* of items.
*/
private static class WrappedCellRenderer extends JPanel implements ListCellRenderer {
JTextArea ta = new JTextArea();
JLabel numberLabel;
int selected = -1;
boolean contentNumbered;
int taWidth;
/**
* @param contentNumbered Whether content will be numbered
* @param wrappingWidth Width of list item.
*/
private WrappedCellRenderer(boolean contentNumbered, int wrappingWidth) {
super(new BorderLayout());
this.contentNumbered = contentNumbered;
ta.setOpaque(false);
ta.setEditable(false);
ta.setLineWrap(true);
ta.setWrapStyleWord(true);
ta.setFont(UIManager.getFont("Label.font")); // NOI18N
ta.getAccessibleContext().setAccessibleDescription(""); // NOI18N
ta.setBorder(BorderFactory.createEmptyBorder());
taWidth = wrappingWidth - 12 - 12;
numberLabel = new JLabel() {
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
// #9804. Draw bullet if the content is not numbered.
if (!WrappedCellRenderer.this.contentNumbered) {
java.awt.Rectangle rect = g.getClipBounds();
g.fillOval(rect.x, rect.y, 7, 7);
}
}
};
numberLabel.setLabelFor(ta); // a11y
numberLabel.setHorizontalAlignment(SwingConstants.LEFT);
numberLabel.setVerticalAlignment(SwingConstants.TOP);
numberLabel.setFont(ta.getFont());
numberLabel.setOpaque(false);
numberLabel.setPreferredSize(new Dimension(25, 0));
add(numberLabel, BorderLayout.WEST);
taWidth -= 25;
Insets taInsets = ta.getInsets();
ta.setSize(taWidth, taInsets.top + taInsets.bottom + 1);
add(ta, BorderLayout.CENTER);
setBorder(BorderFactory.createEmptyBorder(0, 12, 0, 12));
setOpaque(false);
}
@Override
public Component getListCellRendererComponent(
JList list, Object value, int index, boolean isSelected, boolean cellHasFocus
) {
if (index == selected) {
numberLabel.setFont(doDeriveFont(numberLabel.getFont(), Font.BOLD));
ta.setFont(doDeriveFont(ta.getFont(), Font.BOLD));
} else {
numberLabel.setFont(doDeriveFont(numberLabel.getFont(), Font.PLAIN));
ta.setFont(doDeriveFont(ta.getFont(), Font.PLAIN));
}
if (contentNumbered) {
numberLabel.setText(Integer.toString(index + 1) + "."); // NOI18N
}
// #21322: on JDK1.4 wrapping width is cleared between two rendering runs
Insets taInsets = ta.getInsets();
ta.setSize(taWidth, taInsets.top + taInsets.bottom + 1);
ta.setText((String) value);
return this;
}
private void setSelectedIndex(int i) {
selected = i;
}
private void setForegroundColor(Color color) {
if (numberLabel != null) {
numberLabel.setForeground(color);
numberLabel.setBackground(color);
}
ta.setForeground(color);
}
}
/** Wizard panel. Allows auto layout of content, wizard panel name and input panel.
*/
private static class WizardPanel extends JPanel {
/** Users panel is inserted into this panel. */
private JPanel rightPanel = new JPanel(new BorderLayout());
/** Name of the users panel. */
private JLabel panelName = new JLabel("Step"); //NOI18N
/** List of content. */
private JList contentList;
/** Users component. Should be held for removing from rightPanel */
private Component rightComponent;
/** Panel which paints image */
private ImagedPanel contentPanel;
/** Name of content. Can be switched off. */
private JPanel contentLabelPanel;
/** Wrapped list cell renderer */
private WrappedCellRenderer cellRenderer;
/** Tabbed pane is used only when both content and help are displayed */
private JTabbedPane tabbedPane;
/** HTML Browser is used only when help is displayed in the left pane */
private HtmlBrowser htmlBrowser;
/** Each wizard panel have to be larger or same as this */
private Dimension cachedDimension;
/** Label of steps pane */
private JLabel label;
private JPanel progressBarPanel;
/** Selected index of content */
private int selectedIndex;
private JTextPane messagePane;
private JLabel iconLabel;
private Color nbErrorForeground;
private Color nbWarningForeground;
private Color nbInfoForeground;
private static final int MSG_TYPE_ERROR = 1;
private static final int MSG_TYPE_WARNING = 2;
private static final int MSG_TYPE_INFO = 3;
/** Creates new <CODE>WizardPanel<CODE>.
* @param contentDisplayed whether content will be displayed in the left pane
* @param helpDisplayed whether help will be displayed in the left pane
* @param contentNumbered whether content will be numbered
* @param leftDimension dimension of content or help pane
*/
public WizardPanel(
boolean contentDisplayed, boolean helpDispalyed, boolean contentNumbered, Dimension leftDimension
) {
super(new BorderLayout());
initComponents(contentDisplayed, helpDispalyed, contentNumbered, leftDimension);
setOpaque(false);
resetPreferredSize();
}
private void initComponents(
boolean contentDisplayed, boolean helpDisplayed, boolean contentNumbered, Dimension leftDimension
) {
if (contentDisplayed) {
createContentPanel(contentNumbered, leftDimension);
if (!helpDisplayed) {
add(contentPanel, BorderLayout.WEST);
}
}
if (helpDisplayed) {
htmlBrowser = new BoundedHtmlBrowser(leftDimension);
htmlBrowser.setPreferredSize(leftDimension);
if (!contentDisplayed) {
add(htmlBrowser, BorderLayout.WEST);
}
}
if (helpDisplayed && contentDisplayed) {
tabbedPane = new JTabbedPane(JTabbedPane.BOTTOM);
tabbedPane.addTab(NbBundle.getMessage(WizardDescriptor.class, "CTL_ContentName"), contentPanel);
tabbedPane.addTab(NbBundle.getMessage(WizardDescriptor.class, "CTL_HelpName"), htmlBrowser);
tabbedPane.setEnabledAt(1, false);
tabbedPane.setOpaque(false);
// tabbedPane.setPreferredSize(leftDimension);
add(tabbedPane, BorderLayout.WEST);
}
panelName.setBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, panelName.getForeground()));
panelName.setFont(doDeriveFont(panelName.getFont(), Font.BOLD));
JPanel labelPanel = new JPanel(new BorderLayout());
labelPanel.add(panelName, BorderLayout.NORTH);
labelPanel.setBorder(BorderFactory.createEmptyBorder(12, 12, 12, 11));
rightPanel.setBorder(BorderFactory.createEmptyBorder(0, 12, 11, 11));
panelName.setLabelFor(labelPanel);
nbErrorForeground = UIManager.getColor("nb.errorForeground"); //NOI18N
if (nbErrorForeground == null) {
//nbErrorForeground = new Color(89, 79, 191); // RGB suggested by Bruce in #28466
nbErrorForeground = new Color(255, 0, 0); // RGB suggested by jdinga in #65358
}
nbWarningForeground = UIManager.getColor("nb.warningForeground"); //NOI18N
if (nbWarningForeground == null) {
nbWarningForeground = new Color(51, 51, 51); // Label.foreground
}
nbInfoForeground = UIManager.getColor("nb.warningForeground"); //NOI18N
if (nbInfoForeground == null) {
nbInfoForeground = UIManager.getColor("Label.foreground"); //NOI18N
}
JPanel errorPanel = new JPanel(new BorderLayout());
errorPanel.setBorder(BorderFactory.createEmptyBorder(0, 12, 12, 11));
messagePane = new FixedHeightPane ();
messagePane.setForeground (nbErrorForeground);
iconLabel = new FixedHeightLabel ();
errorPanel.add(iconLabel, BorderLayout.LINE_START);
errorPanel.add(messagePane, BorderLayout.CENTER);
progressBarPanel = new JPanel (new BorderLayout ());
progressBarPanel.setVisible (false);
if (contentDisplayed) {
// place for visualize progress bar in content panel (if contentDisplayed)
progressBarPanel.setOpaque (false);
progressBarPanel.setBorder (BorderFactory.createEmptyBorder (0, 4, 7, 4));
contentPanel.add (progressBarPanel, BorderLayout.SOUTH);
} else {
// placeholder for progress bar components in WizardPanel (if no contentDisplayed set)
progressBarPanel.add (new JLabel (), BorderLayout.NORTH);
JProgressBar pb = new JProgressBar ();
pb.setOrientation (JProgressBar.HORIZONTAL);
pb.setAlignmentX(0.5f);
pb.setAlignmentY(0.5f);
pb.setString ("0"); // NOI18N
progressBarPanel.add (pb, BorderLayout.CENTER);
progressBarPanel.setBorder (BorderFactory.createEmptyBorder (4, 0, 0, 0));
errorPanel.add (progressBarPanel, BorderLayout.SOUTH);
}
JPanel fullRightPanel = new JPanel(new BorderLayout());
fullRightPanel.add(labelPanel, BorderLayout.NORTH);
fullRightPanel.add(rightPanel, BorderLayout.CENTER);
fullRightPanel.add(errorPanel, BorderLayout.SOUTH);
// #65506: the wizard panel should fit into window w/o scrollbar
add(fullRightPanel, BorderLayout.CENTER);
if ((getBorder() == null) || "GTK".equals(UIManager.getLookAndFeel().getID())) {
// Look & Feel has not set the border already
JSeparator sep = new JSeparator();
sep.setForeground(Color.darkGray);
add(sep, BorderLayout.SOUTH);
}
}
public void setMessage(int msgType, String msg) {
if (msg != null && msg.trim().length() > 0) {
switch (msgType) {
case MSG_TYPE_ERROR:
prepareMessage(msg, ImageUtilities.loadImageIcon("org/netbeans/modules/dialogs/error.gif", false),
nbErrorForeground);
break;
case MSG_TYPE_WARNING:
prepareMessage(msg, ImageUtilities.loadImageIcon("org/netbeans/modules/dialogs/warning.gif", false),
nbWarningForeground);
break;
case MSG_TYPE_INFO:
prepareMessage(msg, ImageUtilities.loadImageIcon("org/netbeans/modules/dialogs/info.png", false),
nbInfoForeground);
break;
default:
}
} else {
prepareMessage(null, null, null);
}
}
private void prepareMessage(final String msg, final ImageIcon icon, final Color fgColor) {
if( !SwingUtilities.isEventDispatchThread() ) {
SwingUtilities.invokeLater( new Runnable() {
@Override
public void run() {
prepareMessage( msg, icon, fgColor );
}
} );
return;
}
String message = msg;
messagePane.setToolTipText (message);
if (message != null) {
message = message.replaceAll("\\s", "&nbsp;"); // NOI18N
if (! message.toUpperCase().startsWith("<HTML>")) { // NOI18N
message = "<HTML>" + message; // NOI18N
}
}
iconLabel.setIcon(icon);
iconLabel.setForeground(fgColor);
messagePane.setForeground(fgColor);
messagePane.setText(message);
messagePane.setFocusable(message != null && !message.isEmpty());
}
private void setProgressComponent (JComponent progressComp, final JLabel progressLabel) {
assert SwingUtilities.isEventDispatchThread();
if (progressComp == null) {
progressBarPanel.removeAll ();
progressBarPanel.setVisible (false);
} else {
if (progressLabel != null) {
progressLabel.setText (PROGRESS_BAR_DISPLAY_NAME);
progressLabel.addPropertyChangeListener ("text", new PropertyChangeListener () { // NOI18N
@Override
public void propertyChange (PropertyChangeEvent evt) {
progressLabel.putClientProperty (JComponent.TOOL_TIP_TEXT_KEY, evt.getNewValue ().toString ());
}
});
progressLabel.setToolTipText (PROGRESS_BAR_DISPLAY_NAME);
progressBarPanel.add (progressLabel, BorderLayout.NORTH);
}
progressBarPanel.add (progressComp, BorderLayout.CENTER);
progressBarPanel.setVisible (true);
}
}
/** Creates content panel.
* @param contentNumbered <CODE>boolean</CODE> whether content will be numbered
* @param leftDimension <CODE>Dimension</CODE> dimension of content pane
*/
private void createContentPanel(boolean contentNumbered, Dimension leftDimension) {
contentList = new JList();
cellRenderer = new WrappedCellRenderer(contentNumbered, leftDimension.width);
cellRenderer.setOpaque(false);
contentList.setCellRenderer(cellRenderer);
contentList.setOpaque(false);
contentList.setEnabled(false);
contentList.getAccessibleContext().setAccessibleDescription(""); // NOI18N
JScrollPane scroll = new JScrollPane(contentList);
scroll.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
scroll.getViewport().setOpaque(false);
scroll.setBorder(BorderFactory.createEmptyBorder());
scroll.setOpaque(false);
// #89392: remove GTK's viewport border
scroll.setViewportBorder(BorderFactory.createEmptyBorder());
label = new JLabel(NbBundle.getMessage(WizardDescriptor.class, "CTL_ContentName"));
label.setForeground(Color.white);
label.setBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, label.getForeground()));
label.setFont(doDeriveFont(label.getFont(), Font.BOLD));
contentLabelPanel = new JPanel(new BorderLayout());
contentLabelPanel.setBorder(BorderFactory.createEmptyBorder(12, 12, 11, 11));
contentLabelPanel.setOpaque(false);
contentLabelPanel.add(label, BorderLayout.NORTH);
contentPanel = new ImagedPanel(null);
contentPanel.add(contentLabelPanel, BorderLayout.NORTH);
contentPanel.add(scroll, BorderLayout.CENTER);
contentPanel.setPreferredSize(leftDimension);
label.setLabelFor(contentList);
}
/** Setter for lists items.
* @param content Array of list items.
*/
public void setContent(final String[] content) {
final JList list = contentList;
if (list == null) {
return;
}
// #18055: Ensure it runs in AWT thread.
// Remove this when component handling will be assured
// by other means that runs always in AWT.
Mutex.EVENT.writeAccess(
new Runnable() {
@Override
public void run() {
list.setListData(content);
list.revalidate();
list.repaint();
contentLabelPanel.setVisible(content.length > 0);
}
}
);
}
/** Setter for selected list item.
* @param index Index of selected item in the list.
*/
public void setSelectedIndex(final int index) {
selectedIndex = index;
if (cellRenderer != null) {
cellRenderer.setSelectedIndex(index);
final JList list = contentList;
if (list == null) {
return;
}
// #18055. See previous #18055 comment.
Mutex.EVENT.readAccess(
new Runnable() {
@Override
public void run() {
list.ensureIndexIsVisible(index);
// Fix of #10787.
// This is workaround for swing bug - BasicListUI doesn't ask for preferred
// size of rendered list cell as a result of property selectedIndex change.
// It does only on certain JList property changes (e.g. fixedCellWidth).
// Maybe subclassing BasicListUI could be better fix.
list.setFixedCellWidth(0);
list.setFixedCellWidth(-1);
}
}
);
}
}
/** Setter for content background color.
* @param color content background color.
*/
public void setContentBackColor(Color color) {
if (contentPanel != null) {
contentPanel.setBackground(color);
}
}
/** Setter for content foreground color.
* @param color content foreground color.
*/
public void setContentForegroundColor(Color color) {
if (cellRenderer == null) {
return;
}
cellRenderer.setForegroundColor(color);
label.setForeground(color);
label.setBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, label.getForeground()));
}
/** Setter for content background image.
* @param image content background image
*/
public void setImage(Image image) {
if (contentPanel != null) {
contentPanel.setImage(image);
}
}
/** Setter for image alignment.
* @param align image alignment - 'North', 'South'
*/
public void setImageAlignment(String align) {
if (contentPanel != null) {
contentPanel.setImageAlignment(align);
}
}
/** Setter for user's component.
* @param c user's component
*/
public void setRightComponent(Component c) {
if (rightComponent != null) {
rightPanel.remove(rightComponent);
}
rightComponent = c;
rightPanel.add(rightComponent, BorderLayout.CENTER);
// validate();
}
/** Getter for user's component.
* @return <CODE>Component</CODE> user's component
*/
public Component getRightComponent() {
return rightComponent;
}
/** Setter for wizard panel name.
* @param name panel name
*/
public void setPanelName(String name) {
panelName.setText(name);
}
/** Setter for help URL.
* @param helpURL help URL
*/
public void setHelpURL(URL helpURL) {
if (htmlBrowser == null) {
return;
}
if (helpURL != null) {
if (!helpURL.equals(htmlBrowser.getDocumentURL())) {
htmlBrowser.setURL(helpURL);
}
if (tabbedPane != null) {
tabbedPane.setEnabledAt(tabbedPane.indexOfComponent(htmlBrowser), true);
}
} else if (tabbedPane != null) {
tabbedPane.setSelectedComponent(contentPanel);
tabbedPane.setEnabledAt(tabbedPane.indexOfComponent(htmlBrowser), false);
}
}
public void resetPreferredSize() {
cachedDimension = new Dimension(600, 365);
}
@Override
public Dimension getPreferredSize() {
Dimension dim = super.getPreferredSize();
if (dim.height > cachedDimension.height) {
cachedDimension.height = dim.height;
}
if (dim.width > cachedDimension.width) {
cachedDimension.width = dim.width;
}
return cachedDimension;
}
/** Overriden to delegate call to user component.
*/
@Override
public void requestFocus() {
if ((rightComponent != null) && rightComponent.isDisplayable()) {
JComponent comp = (JComponent) rightComponent;
Container rootAnc = comp.getFocusCycleRootAncestor();
FocusTraversalPolicy policy = rootAnc.getFocusTraversalPolicy();
Component focus = policy.getComponentAfter(rootAnc, comp);
if (focus != null) {
focus.requestFocus();
} else {
comp.requestFocus();
}
} else {
super.requestFocus();
}
}
/** Overriden to delegate call to user component.
*/
@Deprecated
@Override
public boolean requestDefaultFocus() {
if (rightComponent instanceof JComponent) {
return ((JComponent) rightComponent).requestDefaultFocus();
}
return super.requestDefaultFocus();
}
@Override
public javax.accessibility.AccessibleContext getAccessibleContext() {
if (accessibleContext == null) {
accessibleContext = new AccessibleWizardPanel();
}
return accessibleContext;
}
private class AccessibleWizardPanel extends AccessibleJPanel {
AccessibleWizardPanel() {
}
@Override
public String getAccessibleDescription() {
if (accessibleDescription != null) {
return accessibleDescription;
}
if (rightComponent instanceof Accessible) {
if (rightComponent.getAccessibleContext().getAccessibleDescription() == null) {
return null;
}
return NbBundle.getMessage(
WizardDescriptor.class, "ACSD_WizardPanel", new Integer(selectedIndex + 1), panelName.getText(),
rightComponent.getAccessibleContext().getAccessibleDescription()
);
}
return super.getAccessibleDescription();
}
}
}
/** Overriden to return wished preferred size */
private static class BoundedHtmlBrowser extends HtmlBrowser {
Dimension dim;
public BoundedHtmlBrowser(Dimension d) {
super(false, false);
dim = d;
}
@Override
public Dimension getPreferredSize() {
return dim;
}
}
// helper, make possible close wizard as finish
static class FinishAction extends Object {
ActionListener listner;
public void addActionListener(ActionListener ac) {
listner = ac;
}
public void removeActionListener(ActionListener ac) {
listner = null;
}
public void fireActionPerformed() {
if (listner != null) {
listner.actionPerformed(new ActionEvent(this, 0, ""));
}
}
}
private static final class FixedHeightLabel extends JLabel {
private static final int ESTIMATED_HEIGHT = 16;
public FixedHeightLabel () {
super ();
}
@Override
public Dimension getPreferredSize() {
Dimension preferredSize = super.getPreferredSize();
assert ESTIMATED_HEIGHT == ImageUtilities.loadImage ("org/netbeans/modules/dialogs/warning.gif").getHeight (null) : "Use only 16px icon.";
preferredSize.height = Math.max (ESTIMATED_HEIGHT, preferredSize.height);
return preferredSize;
}
}
private static final class FixedHeightPane extends JTextPane {
private static final int ESTIMATED_HEIGHT = 16;
public FixedHeightPane () {
super ();
setEditable(false);
putClientProperty( JEditorPane.HONOR_DISPLAY_PROPERTIES, Boolean.TRUE);
HTMLEditorKit htmlkit = new HTMLEditorKit();
// override the Swing default CSS to make the HTMLEditorKit use the
// same font as the rest of the UI.
// XXX the style sheet is shared by all HTMLEditorKits. We must
// detect if it has been tweaked by ourselves or someone else
// (code completion javadoc popup for example) and avoid doing the
// same thing again
StyleSheet css = htmlkit.getStyleSheet();
if (css.getStyleSheets() == null) {
StyleSheet css2 = new StyleSheet();
Font f = new JList().getFont();
int size = f.getSize();
try {
css2.addRule(new StringBuffer("body { font-size: ").append(size) // NOI18N
.append("; font-family: ").append(f.getName()).append("; }").toString()); // NOI18N
css2.addStyleSheet(css);
htmlkit.setStyleSheet(css2);
} catch( RuntimeException ex ) {
//#213031
Logger.getLogger( WizardDescriptor.class.getName()).log( Level.INFO, "Error while setting up text pane.", ex );
}
} else {
setFont( new JLabel().getFont() );
}
setEditorKit(htmlkit);
setOpaque(false);
if( "Nimbus".equals( UIManager.getLookAndFeel().getID() ) ) //NOI18N
setBackground(new Color( 0, 0, 0, 0) );
addHyperlinkListener(new HyperlinkListener() {
@Override
public void hyperlinkUpdate(HyperlinkEvent hlevt) {
if (EventType.ACTIVATED == hlevt.getEventType()) {
if (hlevt.getURL () != null) {
URLDisplayer.getDefault().showURLExternal(hlevt.getURL());
}
}
}
});
addMouseListener( new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
showCopyToClipboardPopupMenu( e );
}
@Override
public void mouseReleased(MouseEvent e) {
showCopyToClipboardPopupMenu( e );
}
private void showCopyToClipboardPopupMenu(MouseEvent e) {
if( e.isPopupTrigger() && null != getToolTipText() && !getToolTipText().isEmpty() ) {
JPopupMenu pm = new JPopupMenu();
pm.add(new AbstractAction(NbBundle.getMessage(WizardDescriptor.class, "Lbl_CopyToClipboard")) { //NOI18N
@Override
public void actionPerformed(ActionEvent e) {
Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
c.setContents(new StringSelection(getToolTipText()), null);
}
});
pm.show(e.getComponent(), e.getX(), e.getY());
}
}
} );
}
@Override
public Dimension getPreferredSize() {
Dimension preferredSize = super.getPreferredSize();
assert ESTIMATED_HEIGHT == ImageUtilities.loadImage ("org/netbeans/modules/dialogs/warning.gif").getHeight (null) : "Use only 16px icon.";
preferredSize.height = Math.max (ESTIMATED_HEIGHT, preferredSize.height);
return preferredSize;
}
}
private static final class SettingsAndIterator<Data> {
private final Iterator<Data> panels;
private final Data settings;
private final boolean useThis;
/** current panel */
private Panel<Data> current;
public SettingsAndIterator(Iterator<Data> iterator, Data settings) {
this(iterator, settings, false);
}
public SettingsAndIterator(Iterator<Data> iterator, Data settings, boolean useThis) {
this.panels = iterator;
this.settings = settings;
this.useThis = useThis;
}
public static SettingsAndIterator<WizardDescriptor> create(Iterator<WizardDescriptor> iterator) {
return new SettingsAndIterator<WizardDescriptor>(iterator, null, true);
}
public static SettingsAndIterator<Void> empty() {
return new SettingsAndIterator<Void>(new EmptyPanel(), (Void)null);
}
public Iterator<Data> getIterator(WizardDescriptor caller) {
return panels;
}
@SuppressWarnings("unchecked")
public Data getSettings(WizardDescriptor caller) {
return useThis ? (Data)caller : settings;
}
public SettingsAndIterator<Data> clone(Iterator<Data> it) {
SettingsAndIterator<Data> s = new SettingsAndIterator<Data>(it, settings, useThis);
return s;
}
}
private static final class EmptyPanel implements Panel<Void>, Iterator<Void> {
@Override
public Component getComponent() {
return new JPanel();
}
@Override
public HelpCtx getHelp() {
return HelpCtx.DEFAULT_HELP;
}
@Override
public void readSettings(Void settings) {
}
@Override
public void storeSettings(Void settings) {
}
@Override
public boolean isValid() {
return true;
}
@Override
public void addChangeListener(ChangeListener l) {
}
@Override
public void removeChangeListener(ChangeListener l) {
}
@Override
public Panel<Void> current() {
return this;
}
@Override
public String name() {
return ""; // NORTH
}
@Override
public boolean hasNext() {
return false;
}
@Override
public boolean hasPrevious() {
return false;
}
@Override
public void nextPanel() {
}
@Override
public void previousPanel() {
}
} // end of EmptyPanel
}