001    /*
002     * Copyright 2002-2004 the original author or authors.
003     * 
004     * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005     * use this file except in compliance with the License. You may obtain a copy of
006     * the License at
007     * 
008     * http://www.apache.org/licenses/LICENSE-2.0
009     * 
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012     * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013     * License for the specific language governing permissions and limitations under
014     * the License.
015     */
016    package org.springframework.richclient.wizard;
017    
018    import java.awt.Image;
019    import java.util.ArrayList;
020    import java.util.Iterator;
021    import java.util.List;
022    
023    import org.springframework.richclient.application.support.ApplicationServicesAccessor;
024    import org.springframework.richclient.core.TitleConfigurable;
025    import org.springframework.richclient.form.Form;
026    import org.springframework.richclient.image.ImageSource;
027    import org.springframework.richclient.util.EventListenerListHelper;
028    import org.springframework.util.Assert;
029    
030    /**
031     * A convenience implementation of the {@link Wizard} interface. This abstract class provides the 
032     * following basic wizard functionaliy:
033     * 
034     * <ul>
035     * <li>Adding and removing pages from the wizard.</li>
036     * <li>Stepping forward and back through the wizard pages.</li>
037     * <li>Adding and removing wizard listeners.</li>
038     * <li>Notifying listeners of events such as {@code cancel} and {@code finish}.</li>
039     * <li>Provides access to application services via its superclass, {@link ApplicationServicesAccessor}.</li>
040     * </ul>
041     * 
042     * 
043     * @author Keith Donald
044     */
045    public abstract class AbstractWizard extends ApplicationServicesAccessor implements Wizard, TitleConfigurable {
046        
047        /** The key that will be used to retrieve the default page image icon for the wizard. */
048        public static final String DEFAULT_IMAGE_KEY = "wizard.pageIcon";
049    
050        private String wizardId;
051    
052        private String title;
053    
054        private boolean forcePreviousAndNextButtons;
055    
056        private List pages = new ArrayList(6);
057    
058        private WizardContainer container;
059    
060        private EventListenerListHelper listeners = new EventListenerListHelper(WizardListener.class);
061    
062        private boolean autoConfigureChildPages = true;
063    
064        /**
065         * Creates a new uninitialized {@code AbstractWizard}.
066         */
067        public AbstractWizard() {
068            this(null);
069        }
070    
071        /**
072         * Creates a new {@code AbstractWizard} with the given identifier.
073         *
074         * @param wizardId The id used to identify this wizard. 
075         */
076        public AbstractWizard(String wizardId) {
077            this.wizardId = wizardId;
078        }
079    
080        /**
081         * Returns this wizard's identifier.
082         * 
083         * @return the identifier of this wizard, may be null.
084         */
085        public String getId() {
086            return wizardId;
087        }
088    
089        /**
090         * Sets the flag that determines whether or not wizard pages will be configured as they are 
091         * added to this wizard.
092         *
093         * @param autoConfigure
094         */
095        public void setAutoConfigureChildPages(boolean autoConfigure) {
096            this.autoConfigureChildPages = autoConfigure;
097        }
098    
099        /**
100         * Controls whether the wizard needs Previous and Next buttons even if it
101         * currently contains only one page.
102         * <p>
103         * This flag should be set on wizards where the first wizard page adds
104         * follow-on wizard pages based on user input.
105         * </p>
106         * 
107         * @param b
108         *            <code>true</code> to always show Next and Previous buttons,
109         *            and <code>false</code> to suppress Next and Previous buttons
110         *            for single page wizards
111         */
112        public void setForcePreviousAndNextButtons(boolean b) {
113            this.forcePreviousAndNextButtons = b;
114        }
115    
116        /**
117         * Returns the window title for the container that host this wizard.
118         * 
119         * @return the wizard title, may be null.
120         */
121        public String getTitle() {
122            return title;
123        }
124    
125        /**
126         * Sets the window title for the container that hosts this page to the given
127         * string.
128         * 
129         * @param newTitle
130         *            the window title for the container
131         */
132        public void setTitle(String newTitle) {
133            this.title = newTitle;
134        }
135    
136        /**
137         * Returns the component that contains this wizard.
138         * 
139         * @return the wizard container.
140         */
141        public WizardContainer getContainer() {
142            return container;
143        }
144    
145        /**
146         * Sets the component that contains this wizard.
147         * 
148         * @param container the container to set
149         */
150        public void setContainer(WizardContainer container) {
151            this.container = container;
152        }
153    
154        /**
155         * Adds a new page to this wizard. The page is inserted at the end of the
156         * page list.
157         * 
158         * @param page
159         *            the new page
160         */
161        public void addPage(WizardPage page) {
162            addPage(getId(), page);
163        }
164    
165        /**
166         * Adds a new page to this wizard. The page is inserted at the end of the
167         * page list.
168         * 
169         * @param wizardConfigurationKey
170         *            the parent configuration key of the page, used for
171         *            configuration, by default this wizard's id *
172         * @param page
173         *            the new page
174         */
175        protected void addPage(String wizardConfigurationKey, WizardPage page) {
176            pages.add(page);
177            page.setWizard(this);
178            if (autoConfigureChildPages) {
179                String key = ((wizardConfigurationKey != null) ? wizardConfigurationKey + "." : "") + page.getId();
180                getObjectConfigurer().configure(page, key);
181            }
182        }
183    
184        /**
185         * Adds a new page to this wizard. The page is created by wrapping the form in a 
186         * {@link FormBackedWizardPage} and appending it to the end of the page list.
187         * 
188         * @param formPage The form page to be added to the wizard.
189         * @return the newly created wizard page that wraps the given form. 
190         * 
191         * @throws IllegalArgumentException if {@code formPage} is null.
192         */
193        public WizardPage addForm(Form formPage) {
194            Assert.notNull(formPage, "The form page cannot be null");
195            WizardPage page = new FormBackedWizardPage(formPage, !autoConfigureChildPages);
196            addPage(page);
197            return page;
198        }
199    
200        /**
201         * Removes the given page from this wizard.
202         * 
203         * @param page The page to be removed.
204         */
205        public void removePage(WizardPage page) {
206            if (pages.remove(page)) {
207                page.setWizard(null);
208            }
209        }
210    
211        /**
212         * This implementation of {@link Wizard#addPages()} does nothing. Subclasses should override 
213         * this method if extra pages need to be added before the wizard is displayed. New pages should 
214         * be added by calling {@link #addPage(WizardPage)}.
215         */
216        public void addPages() {
217            //do nothing
218        }
219    
220        /**
221         * Returns true if all the pages of this wizard have been completed.
222         * 
223         */
224        public boolean canFinish() {
225            // Default implementation is to check if all pages are complete.
226            for (int i = 0; i < pages.size(); i++) {
227                if (!((WizardPage)pages.get(i)).isPageComplete())
228                    return false;
229            }
230            return true;
231        }
232    
233        /**
234         * Returns the image stored under the key {@value #DEFAULT_IMAGE_KEY}.
235         *
236         * @see ImageSource#getImage(String)
237         */
238        public Image getDefaultPageImage() {
239            return getImageSource().getImage(DEFAULT_IMAGE_KEY);
240        }
241    
242        /**
243         * {@inheritDoc}
244         */
245        public WizardPage getNextPage(WizardPage page) {
246            int index = pages.indexOf(page);
247            if (index == pages.size() - 1 || index == -1) {
248                // last page or page not found
249                return null;
250            }
251            return (WizardPage)pages.get(index + 1);
252        }
253    
254        /**
255         * {@inheritDoc}
256         */
257        public WizardPage getPage(String pageId) {
258            Iterator it = pages.iterator();
259            while (it.hasNext()) {
260                WizardPage page = (WizardPage)it.next();
261                if (page.getId().equals(pageId)) {
262                    return page;
263                }
264            }
265            return null;
266        }
267    
268        /**
269         * {@inheritDoc}
270         */
271        public int getPageCount() {
272            return pages.size();
273        }
274    
275        /**
276         * {@inheritDoc}
277         */
278        public WizardPage[] getPages() {
279            return (WizardPage[])pages.toArray(new WizardPage[pages.size()]);
280        }
281    
282        /**
283         * {@inheritDoc}
284         */
285        public WizardPage getPreviousPage(WizardPage page) {
286            int index = pages.indexOf(page);
287            if (index == 0 || index == -1) {
288                // first page or page not found
289                return null;
290            }
291    
292            logger.debug("Returning previous page...");
293            return (WizardPage)pages.get(index - 1);
294        }
295    
296        /**
297         * {@inheritDoc}
298         */
299        public WizardPage getStartingPage() {
300            if (pages.size() == 0) {
301                return null;
302            }
303            return (WizardPage)pages.get(0);
304        }
305    
306        /**
307         * {@inheritDoc}
308         */
309        public boolean needsPreviousAndNextButtons() {
310            return forcePreviousAndNextButtons || pages.size() > 1;
311        }
312    
313        /**
314         * {@inheritDoc}
315         */
316        public void addWizardListener(WizardListener wizardListener) {
317            listeners.add(wizardListener);
318        }
319    
320        /**
321         * {@inheritDoc}
322         */
323        public void removeWizardListener(WizardListener wizardListener) {
324            listeners.remove(wizardListener);
325        }
326    
327        /**
328         * Fires an {@code onPerformFinish} event to all listeners.
329         */
330        protected void fireFinishedPerformed(boolean result) {
331            listeners.fire("onPerformFinish", this, Boolean.valueOf(result));
332        }
333    
334        /**
335         * Fires an {@code onPerformCancel} event to all listeners.
336         */
337        protected void fireCancelPerformed(boolean result) {
338            listeners.fire("onPerformCancel", this, Boolean.valueOf(result));
339        }
340    
341        /**
342         * Performs any required processing when the wizard receives a finish request, and then fires
343         * an appropriate event to any wizard listeners listening to this wizard.
344         * 
345         * @return {@code true} to indicate that the finish request was accepted, {@code false} to 
346         * indicate that it was refused.
347         */
348        public boolean performFinish() {
349            boolean result = onFinish();
350            fireFinishedPerformed(result);
351            return result;
352        }
353    
354        /**
355         * Performs any required processing when the wizard is cancelled, and then fires an appropriate 
356         * event to any wizard listeners listening to this wizard.
357         * 
358         * @return {@code true} to indicate that the cancel request was accepted, {@code false} to 
359         * indicate that it was refused.
360         */
361        public boolean performCancel() {
362            boolean result = onCancel();
363            fireCancelPerformed(result);
364            return result;
365        }
366    
367        /**
368         * Subclasses must implement this method to perform any processing when the wizard receives a 
369         * finish request.
370         * 
371         * @return {@code true} to indicate that the finish request was accepted, {@code false} to 
372         * indicate that it was refused.
373         */
374        protected abstract boolean onFinish();
375    
376        /**
377         * Subclasses can override this method to perform processing when the wizard receives a cancel
378         * request. This default implementation always returns true.
379         * 
380         * @returrn {@code true} to indicate that the cancel request was accepted, {@code false} to 
381         * indicate that it was refused.
382         */
383        protected boolean onCancel() {
384            return true;
385        }
386    
387    }