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 }