All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.openide.WizardDescriptor Maven / Gradle / Ivy

There is a newer version: RELEASE240
Show newest version
/*
 * 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 wizard panels may be specified and these
 * may be traversed at the proper times using the "Previous"
 * and "Next" buttons (or "Finish" on the last one).
 *
 * 

Related Tutorial * *

* * @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 true for enabling other properties. It is relevant only on * initialization (client property in first panel). Recommended to be set to true 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 WizardDescriptor.getProperty() or * ((JComponent)Panel.getComponent()).getClientProperty() in this order. * Boolean type property. * @since 7.8 */ public static final String PROP_AUTO_WIZARD_STYLE = "WizardPanel_autoWizardStyle"; // NOI18N /** Set to true 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 PROP_HELP_URL. * * The value is taken from WizardDescriptor.getProperty() or * ((JComponent)Panel.getComponent()).getClientProperty() in this order. * Boolean type property. * @since 7.8 */ public static final String PROP_HELP_DISPLAYED = "WizardPanel_helpDisplayed"; // NOI18N /** Set to true 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 PROP_CONTENT_DATA. * * The value is taken from WizardDescriptor.getProperty() or * ((JComponent)Panel.getComponent()).getClientProperty() in this order. * Boolean type property. * @since 7.8 */ public static final String PROP_CONTENT_DISPLAYED = "WizardPanel_contentDisplayed"; // NOI18N /** Set to true for displaying numbers in the content. It is relevant only on * initialization (client property in first panel). * * The value is taken from WizardDescriptor.getProperty() or * ((JComponent)Panel.getComponent()).getClientProperty() in this order. * Boolean 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 WizardDescriptor.getProperty() or * ((JComponent)Panel.getComponent()).getClientProperty() in this order. * Integer 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 WizardDescriptor.getProperty() or * ((JComponent)Panel.getComponent()).getClientProperty() in this order. * String[] 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 WizardDescriptor.getProperty() or * ((JComponent)Panel.getComponent()).getClientProperty() in this order. * Color 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 WizardDescriptor.getProperty() or * ((JComponent)Panel.getComponent()).getClientProperty() in this order. * Color 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 WizardDescriptor.getProperty() or * ((JComponent) Panel.getComponent()).getClientProperty() in this order. * java.awt.Image 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 WizardDescriptor.getProperty() or * ((JComponent) Panel.getComponent()).getClientProperty() in this order. * String 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 PROP_IMAGE. * It is relevant only on initialization (client property in first panel). * * The value is taken from WizardDescriptor.getProperty() or * ((JComponent) Panel.getComponent()).getClientProperty() in this order. * Dimension 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 WizardDescriptor.getProperty() or * ((JComponent) Panel.getComponent()).getClientProperty() in this order. * URL 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 WizardDescriptor.getProperty(). * String 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 WizardDescriptor.getProperty(). * String 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 WizardDescriptor.getProperty(). * String 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 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 WizardDescriptor.getProperty()/ * (JComponent)Panel.getComponent() client properties or returned * Component will be inserted to wizard dialog directly. */ private boolean autoWizardStyle = false; /** Whether properties from first (JComponent)Panel.getComponent() * have been initialized. */ private boolean init = false; /** Panel which is used when in AUTO_WIZARD_STYLE 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 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 null * @see #WizardDescriptor(WizardDescriptor.Iterator, Object) */ public WizardDescriptor(Panel[] wizardPanels, Data settings) { this(new SettingsAndIterator(new ArrayIterator(wizardPanels), settings)); } /** Create a new wizard from a fixed list of panels with settings * defaulted to this. * * @param wizardPanels the panels to use * @see #WizardDescriptor(WizardDescriptor.Iterator, Object) */ public WizardDescriptor(Panel[] wizardPanels) { this(SettingsAndIterator.create(new ArrayIterator(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 WizardDescriptor(Iterator panels, Data settings) { this(new SettingsAndIterator(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 WizardDescriptor(SettingsAndIterator 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 this. * * @param panels iterator over all {@link WizardDescriptor.Panel}s that can appear in the wizard */ public WizardDescriptor(Iterator 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 void setPanelsAndSettings(Iterator panels, Data settings) { if (data.getIterator(this) != null) { data.getIterator(this).removeChangeListener(weakChangeListener); } //callUninitialize (); data = new SettingsAndIterator(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 DialogDescriptor. * 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 *
    *   {0} wizard {1}
    * 
* That can be expanded to something like this *
    *   EJB wizard (1 of 8)
    * 
* 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(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 getProperties() { return properties == null ? Collections.emptyMap() : new HashMap(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 /**/ 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: *
{@code
    *   super.updateState ();
    *   setOptions (...);
    * }
*/ protected synchronized void updateState() { assert SwingUtilities.isEventDispatchThread(); updateStateOpen (data); } private static final Set logged = new HashSet(); @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 void updateStateOpen(SettingsAndIterator data) { if (!initialized.get()) { //#220286 return; } Panel 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 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 JComponents 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 JComponents client property. * @return Dimension 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 JComponents 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 CLOSED_OPTION. */ @Override public void setValue(Object value) { setValueOpen(value, data); } private void setValueOpen(Object value, SettingsAndIterator 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 void storeSettingsAndNotify(SettingsAndIterator data) { if (data.current != null) { data.current.storeSettings(data.getSettings(this)); } firePropertyChange(PROP_VALUE, null, NEXT_OPTION); } private void resetWizardOpen(SettingsAndIterator 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 void callInstantiateOpen(SettingsAndIterator data) throws IOException { Iterator 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 { /** Get the current panel. * @return the panel */ public Panel current(); /** Get the name of the current panel. * @return the name */ public String name(); /** Test whether there is a next panel. * @return true if so */ public boolean hasNext(); /** Test whether there is a previous panel. * @return true 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 { /** 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 null 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 WizardDescriptor.Panel. *

The settings object is originally supplied to {@link WizardDescriptor#WizardDescriptor(WizardDescriptor.Iterator,Object)}. * In the case of a TemplateWizard.Iterator panel, the object is * in fact the TemplateWizard. * @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 WizardDescriptor.Panel. *

The settings object is originally supplied to {@link WizardDescriptor#WizardDescriptor(WizardDescriptor.Iterator,Object)}. * In the case of a TemplateWizard.Iterator panel, the object is * in fact the TemplateWizard. * @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. *

Tip: if your panel is actually the component itself * (so {@link #getComponent} returns this), 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 true 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 extends Panel { } /** A special interface for panels that need to do additional * validation when Next or Finish button is clicked. * @since 4.28 */ public interface ValidatingPanel extends Panel { /** * 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. * *

During background validation is Next or Finish button * disabled. On validation success wizard automatically * progress to next panel or finishes. * *

During background validation Cancel button is hooked * to signal the validation thread using interrupt(). * *

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 extends ValidatingPanel { /** * 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. * *

During background validation is Next or Finish button * disabled. On validation success wizard automatically * progress to next panel or finishes. * *

During background validation Cancel button is hooked * to signal the validation thread using interrupt(). * * @param * * @since 7.31 */ public interface ExtendedAsynchronousValidatingPanel extends AsynchronousValidatingPanel { /** * 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 extends Panel { /** 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 * TemplateWizard.Iterator * in a template's declaration.) * @param in practice this should be {@link WizardDescriptor} * @since org.openide/1 4.33 */ public interface InstantiatingIterator extends Iterator { /** 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 * TemplateWizard.Iterator * in a template's declaration.) * @param in practice this should be {@link WizardDescriptor} * @since org.openide/1 6.5 */ public interface AsynchronousInstantiatingIterator extends InstantiatingIterator { /** * 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 in practice this should be {@link WizardDescriptor} * @since org.openide.dialogs 7.22 */ public interface BackgroundInstantiatingIterator extends AsynchronousInstantiatingIterator { /** * 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 instantiate is called outside ATW queue. * (This interface can replace * TemplateWizard.Iterator * in a template's declaration.) * @param in practice this should be {@link WizardDescriptor} * @since org.openide.dialogs 7.1 */ public interface ProgressInstantiatingIterator extends AsynchronousInstantiatingIterator { /** * 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 ProgressHandle is not started, need to start it and report progress by * messages in the progress() 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 Panels. */ public static class ArrayIterator extends Object implements Iterator { /** Array of items. */ private Panel[] 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[] 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> panels) { this.panels = panels.toArray(new Panel[0]); 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[] initializePanels() { return new Panel[0]; } /* The current panel. */ @Override public Panel 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 WizardPanel. * @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", " "); // NOI18N if (! message.toUpperCase().startsWith("")) { // NOI18N message = "" + 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 boolean whether content will be numbered * @param leftDimension Dimension 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 Component 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); setFocusable(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 { private final Iterator panels; private final Data settings; private final boolean useThis; /** current panel */ private Panel current; public SettingsAndIterator(Iterator iterator, Data settings) { this(iterator, settings, false); } public SettingsAndIterator(Iterator iterator, Data settings, boolean useThis) { this.panels = iterator; this.settings = settings; this.useThis = useThis; } public static SettingsAndIterator create(Iterator iterator) { return new SettingsAndIterator(iterator, null, true); } public static SettingsAndIterator empty() { return new SettingsAndIterator(new EmptyPanel(), (Void)null); } public Iterator getIterator(WizardDescriptor caller) { return panels; } @SuppressWarnings("unchecked") public Data getSettings(WizardDescriptor caller) { return useThis ? (Data)caller : settings; } public SettingsAndIterator clone(Iterator it) { SettingsAndIterator s = new SettingsAndIterator(it, settings, useThis); return s; } } private static final class EmptyPanel implements Panel, Iterator { @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 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 }