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 }