Index: test/unit/src/org/openide/WizardDescTest.java =================================================================== RCS file: /cvs/openide/test/unit/src/org/openide/WizardDescTest.java,v retrieving revision 1.2 retrieving revision 1.2.54.1 diff -u -r1.2 -r1.2.54.1 --- test/unit/src/org/openide/WizardDescTest.java 21 Aug 2003 12:45:49 -0000 1.2 +++ test/unit/src/org/openide/WizardDescTest.java 14 Mar 2004 22:43:34 -0000 1.2.54.1 @@ -102,16 +102,79 @@ assertEquals ("Closed with cancel option.", WizardDescriptor.CANCEL_OPTION, wd.getValue ()); } + public void testNextOptionWhenLazyValidationFails () throws Exception { + Panel panels[] = new Panel[3]; + + class MyPanel extends Panel implements WizardDescriptor.ValidatingPanel { + public String validateMsg; + public String failedMsg; + + public MyPanel () { + super ("enhanced panel"); + } + + public void validate () throws WizardValidationException { + if (validateMsg != null) { + failedMsg = validateMsg; + throw new WizardValidationException (null, validateMsg); + } + return; + } + } + + class MyFinishPanel extends MyPanel implements WizardDescriptor.FinishPanel { + } + + MyPanel mp = new MyPanel (); + MyFinishPanel mfp = new MyFinishPanel (); + panels[0] = mp; + panels[1] = mfp; + panels[2] = new Panel ("Last one"); + wd = new WizardDescriptor(panels); + + assertNull ("Component has not been yet initialized", panels[1].component); + mp.failedMsg = null; + mp.validateMsg = "xtest-fail-without-msg"; + wd.doNextClick (); + assertEquals ("The lazy validation failed on Next.", mp.validateMsg, mp.failedMsg); + assertNull ("The lazy validation failed, still no initialiaation", panels[1].component); + assertNull ("The lazy validation failed, still no initialiaation", panels[2].component); + mp.failedMsg = null; + mp.validateMsg = null; + wd.doNextClick (); + assertNull ("Validation on Next passes", mp.failedMsg); + assertNotNull ("Now we switched to another panel", panels[1].component); + assertNull ("The lazy validation failed, still no initialiaation", panels[2].component); + + // remember previous state + Object state = wd.getValue(); + mfp.validateMsg = "xtest-fail-without-msg"; + mfp.failedMsg = null; + wd.doFinishClick(); + assertEquals ("The lazy validation failed on Finish.", mfp.validateMsg, mfp.failedMsg); + assertNull ("The validation failed, still no initialiaation", panels[2].component); + assertEquals ("State has not changed", state, wd.getValue ()); + + mfp.validateMsg = null; + mfp.failedMsg = null; + wd.doFinishClick (); + assertNull ("Validation on Finish passes", mfp.failedMsg); + assertNull ("Finish was clicked, no initialization either", panels[2].component); + assertEquals ("The state is finish", WizardDescriptor.FINISH_OPTION, wd.getValue ()); + } public class Panel implements WizardDescriptor.Panel, WizardDescriptor.FinishPanel { - + private JLabel component; private String text; public Panel(String text) { this.text = text; } public Component getComponent() { - return new JLabel(text); + if (component == null) { + component = new JLabel (text); + } + return component; } public void addChangeListener(ChangeListener l) { Index: src/org/openide/WizardDescriptor.java =================================================================== RCS file: /cvs/openide/src/org/openide/WizardDescriptor.java,v retrieving revision 1.94 retrieving revision 1.94.2.3 diff -u -r1.94 -r1.94.2.3 --- src/org/openide/WizardDescriptor.java 10 Mar 2004 09:02:51 -0000 1.94 +++ src/org/openide/WizardDescriptor.java 14 Mar 2004 23:44:52 -0000 1.94.2.3 @@ -7,7 +7,7 @@ * http://www.sun.com/ * * The Original Code is NetBeans. The Initial Developer of the Original - * Code is Sun Microsystems, Inc. Portions Copyright 1997-2003 Sun + * Code is Sun Microsystems, Inc. Portions Copyright 1997-2004 Sun * Microsystems, Inc. All Rights Reserved. */ @@ -267,7 +267,7 @@ cancelButton.addActionListener (listener); super.setOptions (new Object[] { previousButton, nextButton, finishButton, cancelButton }); - super.setClosingOptions (new Object[] { finishButton, cancelButton }); + super.setClosingOptions (new Object[] { WizardDescriptor.FinishAction.FINISH_ACTION, cancelButton }); this.panels = panels; panels.addChangeListener (listener); @@ -1013,6 +1013,21 @@ public interface FinishPanel extends Panel { } + /** A special interface for panels that need to do additional + * validation when Next or Finish button is clicked. + */ + 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 + */ + public void validate () throws WizardValidationException; + } + /** Special iterator that works on an array of Panels. */ public static class ArrayIterator extends Object implements Iterator { @@ -1109,6 +1124,31 @@ } } + + private boolean lazyValidate (WizardDescriptor.Panel panel, WizardDescriptor.WizardPanel wizard) { + if (panel instanceof ValidatingPanel) { + ValidatingPanel v = (ValidatingPanel)panel; + try { + // try validation current panel + v.validate(); + } catch (WizardValidationException wve) { + // cannot continue, notify user + if (wizardPanel != null) { + wizardPanel.setErrorMessage (wve.getLocalizedMessage ()); + } + // focus source of this problem + if (wve.getSource () != null) { + final JComponent comp = (JComponent) wve.getSource (); + if (comp.isFocusable ()) { + comp.requestFocus (); + } + } + // lazy validation failed + return false; + } + } + return true; + } /** Listener to changes in the iterator and panels. */ @@ -1120,8 +1160,18 @@ } /** Action listener */ public void actionPerformed (ActionEvent ev) { + if (wizardPanel != null) { + wizardPanel.setErrorMessage(" "); //NOI18N + } if (ev.getSource () == nextButton) { Dimension previousSize = panels.current().getComponent().getSize(); + + // do lazy validation + if (!lazyValidate (panels.current (), wizardPanel)) { + // if validation failed => cannot move to next panel + return ; + } + panels.nextPanel (); try { // change UI to show next step, show wait cursor during @@ -1142,15 +1192,23 @@ } if (ev.getSource () == previousButton) { - if (wizardPanel != null) { - wizardPanel.setErrorMessage(" "); //NOI18N - } panels.previousPanel (); // show wait cursor when updating previous button updateStateWithFeedback (); } if (ev.getSource () == finishButton) { + + // do lazy validation + if (!lazyValidate (panels.current (), wizardPanel)) { + // if validation failed => cannot move to next panel + return ; + } + + // all is OK + + // close wizrd + FinishAction.FINISH_ACTION.fireActionPerformed (); Object oldValue = getValue (); setValueWithoutPCH (OK_OPTION); if (Arrays.asList(getClosingOptions()).contains(finishButton)) { @@ -1168,6 +1226,7 @@ firePropertyChange (PROP_VALUE, oldValue, CANCEL_OPTION); } } + } /** Listenes on a users client property changes @@ -1526,7 +1585,7 @@ public void setErrorMessage(String msg) { m_lblMessage.setText(msg); } - + /** Creates content panel. * @param contentNumbered boolean whether content will be numbered * @param leftDimension Dimension dimension of content pane @@ -1789,6 +1848,25 @@ void doCancelClick () { if (cancelButton.isEnabled ()) { cancelButton.doClick (); + } + } + + // helper, make possible close wizard as finish + static class FinishAction extends Object { + static public FinishAction FINISH_ACTION = new FinishAction (); + 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, "")); + } } } Index: src/org/openide/DialogDisplayer.java =================================================================== RCS file: /cvs/openide/src/org/openide/DialogDisplayer.java,v retrieving revision 1.9 retrieving revision 1.9.42.1 diff -u -r1.9 -r1.9.42.1 --- src/org/openide/DialogDisplayer.java 7 Oct 2003 20:27:04 -0000 1.9 +++ src/org/openide/DialogDisplayer.java 19 Mar 2004 08:22:54 -0000 1.9.42.1 @@ -7,7 +7,7 @@ * http://www.sun.com/ * * The Original Code is NetBeans. The Initial Developer of the Original - * Code is Sun Microsystems, Inc. Portions Copyright 1997-2003 Sun + * Code is Sun Microsystems, Inc. Portions Copyright 1997-2004 Sun * Microsystems, Inc. All Rights Reserved. */ @@ -22,6 +22,8 @@ import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; import javax.swing.*; import org.openide.util.Lookup; @@ -172,6 +174,7 @@ } public void updateOptions() { + Set addedOptions = new HashSet (5); Object[] options = nd.getOptions(); if (options == null) { switch (nd.getOptionType()) { @@ -203,16 +206,58 @@ buttonPanel.removeAll(); JRootPane rp = getRootPane(); for (int i = 0; i < options.length; i++) { + addedOptions.add (options[i]); buttonPanel.add(option2Button(options[i], nd, makeListener(options[i]), rp)); } options = nd.getAdditionalOptions(); if (options != null) { for (int i = 0; i < options.length; i++) { + addedOptions.add (options[i]); buttonPanel.add(option2Button(options[i], nd, makeListener(options[i]), rp)); } } + if (closingOptions != null) { + for (int i = 0; i < closingOptions.length; i++) { + if (addedOptions.add (closingOptions[i])) { + ActionListener l = makeListener (closingOptions[i]); + attachActionListener (closingOptions[i], l); + } + } + } } + private void attachActionListener (Object comp, ActionListener l) { + // on JButtons attach simply by method call + if (comp instanceof JButton) { + JButton b = (JButton)comp; + b.addActionListener(l); + return; + } else { + // we will have to use dynamic method invocation to add the action listener + // to generic component (and we succeed only if it has the addActionListener method) + java.lang.reflect.Method m = null; + try { + m = comp.getClass().getMethod("addActionListener", new Class[] { ActionListener.class });// NOI18N + try { + m.setAccessible (true); + } catch (SecurityException se) { + m = null; // no jo, we cannot make accessible + } + } catch (NoSuchMethodException e) { + m = null; // no jo, we cannot attach ActionListener to this Component + } catch (SecurityException e2) { + m = null; // no jo, we cannot attach ActionListener to this Component + } + if (m != null) { + try { + m.invoke(comp, new Object[] { l }); + } catch (Exception e) { + // not succeeded, so give up + } + } + } + } + private ActionListener makeListener(final Object option) { return new ActionListener() { public void actionPerformed(ActionEvent e) { Index: src/org/netbeans/core/windows/services/NbPresenter.java =================================================================== RCS file: /cvs/core/windows/src/org/netbeans/core/windows/services/NbPresenter.java,v retrieving revision 1.8 retrieving revision 1.8.12.2 diff -u -r1.8 -r1.8.12.2 --- src/org/netbeans/core/windows/services/NbPresenter.java 24 Feb 2004 17:14:45 -0000 1.8 +++ src/org/netbeans/core/windows/services/NbPresenter.java 14 Mar 2004 23:44:51 -0000 1.8.12.2 @@ -7,7 +7,7 @@ * http://www.sun.com/ * * The Original Code is NetBeans. The Initial Developer of the Original - * Code is Sun Microsystems, Inc. Portions Copyright 1997-2003 Sun + * Code is Sun Microsystems, Inc. Portions Copyright 1997-2004 Sun * Microsystems, Inc. All Rights Reserved. */ @@ -313,6 +313,8 @@ descriptor.addPropertyChangeListener(this); addWindowListener(this); + + initializeClosingOptions (); } /** Descriptor can be cached and reused. We need to remove listeners @@ -322,6 +324,7 @@ descriptor.removePropertyChangeListener(this); uninitializeMessage(); uninitializeButtons(); + uninitializeClosingOptions (); } public void addNotify() { @@ -415,6 +418,22 @@ } } + private void initializeClosingOptions (boolean init) { + Object[] options = getClosingOptions (); + if (options == null) return ; + for (int i = 0; i < options.length; i++) { + modifyListener (options[i], buttonListener, init); + } + } + + private void initializeClosingOptions () { + initializeClosingOptions (true); + } + + private void uninitializeClosingOptions () { + initializeClosingOptions (false); + } + protected final void initializeButtons() { // ----------------------------------------------------------------------------- // If there were any buttons previously, remove them and removeActionListener from them @@ -681,11 +700,11 @@ } } - private void modifyListener(Component comp, ButtonListener l, boolean add) { + private void modifyListener(Object comp, ButtonListener l, boolean add) { // on JButtons attach simply by method call if (comp instanceof JButton) { JButton b = (JButton)comp; - if (add) { + if (add) { b.addActionListener(l); b.addComponentListener(l); b.addPropertyChangeListener(l); @@ -701,6 +720,11 @@ java.lang.reflect.Method m = null; try { m = comp.getClass().getMethod(add ? "addActionListener" : "removeActionListener", new Class[] { ActionListener.class });// NOI18N + try { + m.setAccessible (true); + } catch (SecurityException se) { + m = null; // no jo, we cannot make accessible + } } catch (NoSuchMethodException e) { m = null; // no jo, we cannot attach ActionListener to this Component } catch (SecurityException e2) { Index: src/org/openide/WizardValidationException.java =================================================================== /* * Sun Public License Notice * * The contents of this file are subject to the Sun Public License * Version 1.0 (the "License"). You may not use this file except in * compliance with the License. A copy of the License is available at * http://www.sun.com/ * * The Original Code is NetBeans. The Initial Developer of the Original * Code is Sun Microsystems, Inc. Portions Copyright 1997-2004 Sun * Microsystems, Inc. All Rights Reserved. */ package org.openide; import javax.swing.JComponent; /** The exception informs about fail in wizard panel validation and provides * a localized description what's wrong. Also can return JComponent which should * be focused to correct wrong values. * * @author Jiri Rechtacek * @since XXX */ public class WizardValidationException extends Exception { private String localizedMessage; private JComponent source; /** Creates a new instance of WizardValidationException */ private WizardValidationException () { } /** * Creates a new instance of WizardValidationException * @param source JComponent which should have focus to correct wrong values * @param localizedMessage description notifies an user what value must be corrected */ public WizardValidationException (JComponent source, String localizedMessage) { this.source = source; this.localizedMessage = localizedMessage; } /** * * @return JComponent for request focus to correct wrong values * or null if there is no useful compoment to focus it */ public JComponent getSource () { return source; } /** * * @return description will notifies an user what value must be corrected */ public String getLocalizedMessage () { return localizedMessage != null ? localizedMessage : this.getMessage (); } }