View Javadoc

1   /*
2    * Created on Mar 15, 2005. Copyright 1999-2006 Faculty of Mathematics, Physics
3    * and Informatics, Comenius University, Bratislava. This file is protected by
4    * the Mozilla Public License version 1.1 (the "License"); you may not use this
5    * file except in compliance with the License. You may obtain a copy of the
6    * License at http://euromath2.sourceforge.net/license.html Unless required by
7    * applicable law or agreed to in writing, software distributed under the
8    * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
9    * OF ANY KIND, either express or implied. See the License for the specific
10   * language governing permissions and limitations under the License.
11   */
12  package sk.uniba.euromath.editor.wizards;
13  import java.util.ArrayList;
14  import java.util.IdentityHashMap;
15  import java.util.List;
16  import java.util.ListIterator;
17  import java.util.NoSuchElementException;
18  import org.eclipse.core.runtime.IStatus;
19  import org.eclipse.swt.graphics.RGB;
20  import sk.uniba.euromath.EuroMath;
21  /***
22   * <p>
23   * Multi-wizard switcher. Each wizard it encapsulates must comply to these
24   * rules:
25   * </p>
26   * <ul>
27   * <li><code>getPreviousPage(getNextPage(page)) == page</code> for any page
28   * the wizard ever returns, except the last page,</li>
29   * <li><code>getPreviousPage(getStartingPage()) == null</code></li>
30   * <li><code>getStartingPage()</code> must consistently return same instance
31   * of page object</li>
32   * <li><code>createPageControls()</code> is not called.</li>
33   * <li>it must be able to be cancelled anytime</li>
34   * </ul>
35   * <p>
36   * This wizard guarantees that
37   * </p>
38   * <ul>
39   * <li><code>getNextPage()</code> and <code>getPreviousPage()</code> of
40   * enclosed wizard will be called only when <code>this.getNextPage()</code>
41   * (or <code>this.getPreviousPage()</code> respectively) is called.</li>
42   * <li><code>performFinish()</code> and <code>performCancel()</code> are
43   * called only when this wizard's <code>performFinish()</code> (or
44   * <code>performCancel()</code> respectively) is called. Thus, if next wizard
45   * (in its creation time) depends on results of previous wizard, you can't
46   * compute those results in the <code>performFinish()</code> method.</li>
47   * </ul>
48   * <p>
49   * This wizard does not support any dialog settings - setting them has no
50   * effect.
51   * </p>
52   * @author Martin Vysny
53   */
54  public class MultiWizard implements IWizard {
55  	/***
56  	 * Creates instance of wizard switcher.
57  	 * @param provider provides switcher with wizard instances.
58  	 */
59  	public MultiWizard(IMultiWizardProvider provider) {
60  		super();
61  		this.provider = provider;
62  		provider.setWizard(this);
63  		if (provider.hasPrevious())
64  			throw new IllegalArgumentException(
65  					"Provider must be able to offer first wizard initially."); //$NON-NLS-1$
66  		if (provider.current() == null)
67  			throw new IllegalArgumentException(
68  					"Provider must be able to offer at least one wizard."); //$NON-NLS-1$
69  	}
70  	/***
71  	 * Provides switcher with wizard instances.
72  	 */
73  	protected final IMultiWizardProvider provider;
74  	/***
75  	 * Contains list of wizards. Last wizard is always the active wizard.
76  	 */
77  	protected final List<IWizard> wizards = new ArrayList<IWizard>();
78  	/***
79  	 * Contains pages created by the wizards.
80  	 */
81  	protected final IdentityHashMap<IWizard, List<BaseWizardPage>> pages = new IdentityHashMap<IWizard, List<BaseWizardPage>>();
82  	/***
83  	 * Returns active wizard - the last item of <code>wizards</code> array.
84  	 * @return active wizard.
85  	 */
86  	protected final IWizard getActiveWizard() {
87  		if (wizards.size() == 0) {
88  			// instantiate first wizard.
89  			assert !provider.hasPrevious();
90  			final IWizard wizard = provider.current();
91  			wizards.add(wizard);
92  			// create its first page.
93  			List<BaseWizardPage> wizardPages = new ArrayList<BaseWizardPage>();
94  			final BaseWizardPage firstPage = wizard.current();
95  			wizardPages.add(firstPage);
96  			pages.put(wizard, wizardPages);
97  		}
98  		return wizards.get(wizards.size() - 1);
99  	}
100 	/*
101 	 * (non-Javadoc)
102 	 * @see org.eclipse.jface.wizard.IWizard#canFinish()
103 	 */
104 	public boolean canFinish() {
105 		return getActiveWizard().canFinish() && (!provider.hasNext());
106 	}
107 	/*
108 	 * (non-Javadoc)
109 	 * @see org.eclipse.jface.wizard.IWizard#dispose()
110 	 */
111 	public void dispose() {
112 		// dispose all wizards automatically
113 		provider.dispose();
114 		for (IWizard w : wizards) {
115 			w.dispose();
116 		}
117 		wizards.clear();
118 		pages.clear();
119 	}
120 	/***
121 	 * Finds given page and returns its location in the <code>wizards</code>
122 	 * and <code>pages</code> properties.
123 	 * @param page page to find
124 	 * @return the page location, never <code>null</code>.
125 	 */
126 	protected PageLocator getPage(BaseWizardPage page) {
127 		// iterate backwards
128 		for (ListIterator<IWizard> i = wizards.listIterator(wizards.size()); i
129 				.hasPrevious();) {
130 			IWizard wizard = i.previous();
131 			List<BaseWizardPage> wizardPages = pages.get(wizard);
132 			int index = wizardPages.lastIndexOf(page);
133 			if (index >= 0) {
134 				return new PageLocator(wizard, i.nextIndex(), index);
135 			}
136 		}
137 		throw new IllegalArgumentException("Page " + page + " cannot be found"); //$NON-NLS-1$ //$NON-NLS-2$
138 	}
139 	/***
140 	 * Locates page.
141 	 * @author Martin Vysny
142 	 */
143 	protected class PageLocator implements Comparable<PageLocator> {
144 		/***
145 		 * Wizard containing requested page.
146 		 */
147 		protected final IWizard wizard;
148 		/***
149 		 * Index of wizard, containing requested page.
150 		 */
151 		protected final int wizardIndex;
152 		/***
153 		 * Index of requested page.
154 		 */
155 		protected final int pageIndex;
156 		/***
157 		 * Constructor.
158 		 * @param wizard the wizard.
159 		 * @param wizardIndex index of the wizard in the wizards list.
160 		 * @param pageIndex index of requested page.
161 		 */
162 		protected PageLocator(IWizard wizard, int wizardIndex, int pageIndex) {
163 			super();
164 			this.wizard = wizard;
165 			this.wizardIndex = wizardIndex;
166 			this.pageIndex = pageIndex;
167 		}
168 		/***
169 		 * Returns the page instance.
170 		 * @return the page instance.
171 		 */
172 		protected BaseWizardPage getPage() {
173 			return pages.get(wizard).get(pageIndex);
174 		}
175 		/*
176 		 * (non-Javadoc)
177 		 * @see java.lang.Comparable#compareTo(T)
178 		 */
179 		public int compareTo(PageLocator o) {
180 			if ((o == this) || equals(o))
181 				return 0;
182 			if (wizardIndex < o.wizardIndex)
183 				return -1;
184 			if (wizardIndex > o.wizardIndex)
185 				return 1;
186 			if (pageIndex < o.pageIndex)
187 				return -1;
188 			if (pageIndex > o.pageIndex)
189 				return 1;
190 			// can't happen
191 			throw new AssertionError();
192 		}
193 		/*
194 		 * (non-Javadoc)
195 		 * @see java.lang.Object#equals(java.lang.Object)
196 		 */
197 		@Override
198 		public boolean equals(Object obj) {
199 			if (obj == null)
200 				return false;
201 			if (!(obj instanceof PageLocator))
202 				return false;
203 			PageLocator other = (PageLocator) obj;
204 			if ((wizardIndex != other.wizardIndex)
205 					|| (pageIndex != other.pageIndex))
206 				return false;
207 			// sanity check
208 			assert other.wizard == wizard;
209 			return true;
210 		}
211 		/*
212 		 * (non-Javadoc)
213 		 * @see java.lang.Object#toString()
214 		 */
215 		@Override
216 		public String toString() {
217 			return "Wizard " + wizardIndex + " page " + pageIndex; //$NON-NLS-1$ //$NON-NLS-2$
218 		}
219 		/***
220 		 * Returns locator for previous page.
221 		 * @return locator for previous page, or <code>null</code> if this
222 		 * locator is first.
223 		 */
224 		protected PageLocator getPrevious() {
225 			if (pageIndex > 0)
226 				return new PageLocator(wizard, wizardIndex, pageIndex - 1);
227 			if (wizardIndex == 0)
228 				return null;
229 			IWizard prevWizard = wizards.get(wizardIndex - 1);
230 			return new PageLocator(prevWizard, wizardIndex - 1, pages.get(
231 					prevWizard).size() - 1);
232 		}
233 		/***
234 		 * Returns locator for next page.
235 		 * @return locator for next page, or <code>null</code> if next page is
236 		 * not available/created/registered.
237 		 */
238 		protected PageLocator getNext() {
239 			List<BaseWizardPage> pageDelegates = pages.get(wizard);
240 			if (pageDelegates.size() - 1 > pageIndex)
241 				return new PageLocator(wizard, wizardIndex, pageIndex + 1);
242 			if (wizards.size() - 1 > wizardIndex)
243 				return new PageLocator(wizards.get(wizardIndex + 1),
244 						wizardIndex + 1, 0);
245 			return null;
246 		}
247 	}
248 	/***
249 	 * Returns active (last) page.
250 	 * @return locator of active page.
251 	 */
252 	protected PageLocator getActivePage() {
253 		IWizard wizard = getActiveWizard();
254 		// the list of pages must not be empty - if it is then there's a bug
255 		// because the wizard should have been disposed... see
256 		// deletePagesAfter() for details
257 		return new PageLocator(wizard, wizards.size() - 1, pages.get(wizard)
258 				.size() - 1);
259 	}
260 	/***
261 	 * Deletes all pages after given page. Disposes all unused wizards
262 	 * automatically.
263 	 * @param locator the locator of the page.
264 	 * @param including if <code>true</code> then page itself will be deleted.
265 	 * In this case the locator must not denote starting page.
266 	 */
267 	protected void deletePagesAfter(PageLocator locator, boolean including) {
268 		if (including && (locator.getPage() == getStartingPage()))
269 			throw new IllegalArgumentException(
270 					"Starting page cannot be deleted."); //$NON-NLS-1$
271 		while (true) {
272 			PageLocator actPage = getActivePage();
273 			final boolean delete = including ? locator.compareTo(actPage) <= 0
274 					: locator.compareTo(actPage) < 0;
275 			if (!delete) {
276 				// bail out, we have deleted all relevant pages
277 				return;
278 			}
279 			if (actPage.pageIndex != 0) {
280 				// delete the page normally
281 				BaseWizardPage page;
282 				try {
283 					page = actPage.wizard.previous();
284 				} catch (NoSuchElementException ex) {
285 					// this should not happen..
286 					throw new IllegalStateException(
287 							"Wizard violates rules: it modifies shown pages."); //$NON-NLS-1$
288 				}
289 				// sanity check
290 				if (page != actPage.getPrevious().getPage())
291 					throw new IllegalStateException(
292 							"Wizard violates rules: it modifies shown pages"); //$NON-NLS-1$
293 				// passed. remove the page
294 				pages.get(actPage.wizard).remove(actPage.pageIndex);
295 			} else {
296 				// we have to dispose of the wizard.
297 				pages.remove(actPage.wizard);
298 				wizards.remove(wizards.size() - 1);
299 				actPage.wizard.dispose();
300 				IWizard prevWizard = provider.previous();
301 				// sanity check
302 				actPage = getActivePage();
303 				if (prevWizard != actPage.wizard)
304 					throw new IllegalStateException(
305 							"Wizard provider violates rules: it modifies already returned wizards"); //$NON-NLS-1$
306 			}
307 		}
308 	}
309 	/*
310 	 * (non-Javadoc)
311 	 * @see sk.uniba.euromath.editor.wizards.IWizard#hasNext()
312 	 */
313 	public boolean hasNext() {
314 		final PageLocator currentLocator = getActivePage();
315 		// page is the page shown on the display. check if this wizard has more
316 		// pages
317 		if (currentLocator.getPage().hasErrors()) {
318 			// page is not complete, we cannot retrieve next page
319 			return false;
320 		}
321 		if (!currentLocator.wizard.canFinish()) {
322 			// page is complete but the wizard cannot finish - there are some
323 			// pages left
324 			return true;
325 		}
326 		// page is complete and wizard can finish - are there more wizards?
327 		return provider.hasNext();
328 	}
329 	/*
330 	 * (non-Javadoc)
331 	 * @see sk.uniba.euromath.editor.wizards.IWizard#current()
332 	 */
333 	public BaseWizardPage current() {
334 		return getActivePage().getPage();
335 	}
336 	/*
337 	 * (non-Javadoc)
338 	 * @see sk.uniba.euromath.editor.wizards.IWizard#getName()
339 	 */
340 	public String getName() {
341 		if (provider.getName() != null)
342 			return provider.getName();
343 		return getActivePage().wizard.getName();
344 	}
345 	/*
346 	 * (non-Javadoc)
347 	 * @see sk.uniba.euromath.editor.wizards.IWizard#next()
348 	 */
349 	public BaseWizardPage next() throws ProviderException {
350 		if (!hasNext())
351 			throw new NoSuchElementException();
352 		final PageLocator currentLocator = getActivePage();
353 		// get next page from the wizard
354 		IWizard nextWizard = currentLocator.wizard;
355 		final BaseWizardPage nextPage;
356 		if (!nextWizard.hasNext()) {
357 			// next page is null. If this wizard can finish then move to a next
358 			// wizard, if there's any.
359 			if (!currentLocator.wizard.canFinish() || !provider.hasNext()) {
360 				// if we can't finish or if there's no next wizard then just
361 				// return null.
362 				throw new AssertionError(
363 						"hasNext() should have checked this possibility.."); //$NON-NLS-1$
364 			}
365 			// current wizard can finish AND there's next wizard. Register this
366 			// next wizard.
367 			nextWizard = provider.next();
368 			if (nextWizard == null)
369 				throw new NullPointerException("provider returned null wizard"); //$NON-NLS-1$
370 			wizards.add(nextWizard);
371 			List<BaseWizardPage> nextWizardPages = new ArrayList<BaseWizardPage>();
372 			pages.put(nextWizard, nextWizardPages);
373 			nextPage = nextWizard.current();
374 		} else {
375 			nextPage = nextWizard.next();
376 		}
377 		// ok, walk to 'nextPage'
378 		pages.get(nextWizard).add(nextPage);
379 		return nextPage;
380 	}
381 	/*
382 	 * (non-Javadoc)
383 	 * @see sk.uniba.euromath.editor.wizards.IWizard#hasPrevious()
384 	 */
385 	public boolean hasPrevious() {
386 		return getStartingPage() != getActivePage().getPage();
387 	}
388 	/*
389 	 * (non-Javadoc)
390 	 * @see sk.uniba.euromath.editor.wizards.IWizard#previous()
391 	 */
392 	public BaseWizardPage previous() {
393 		if (!hasPrevious())
394 			throw new NoSuchElementException();
395 		final PageLocator currentLocator = getActivePage();
396 		deletePagesAfter(currentLocator, true);
397 		return getActivePage().getPage();
398 	}
399 	/***
400 	 * Retrieves the first page of the first wizard.
401 	 * @return first page instance.
402 	 */
403 	final protected BaseWizardPage getStartingPage() {
404 		// ensure that first page is created
405 		getActiveWizard();
406 		final IWizard first = wizards.get(0);
407 		final BaseWizardPage firstPage = pages.get(first).get(0);
408 		return firstPage;
409 	}
410 	/*
411 	 * (non-Javadoc)
412 	 * @see org.eclipse.jface.wizard.IWizard#getTitleBarColor()
413 	 */
414 	public RGB getTitleBarColor() {
415 		return getActiveWizard().getTitleBarColor();
416 	}
417 	/*
418 	 * (non-Javadoc)
419 	 * @see org.eclipse.jface.wizard.IWizard#performCancel()
420 	 */
421 	public boolean performCancel() {
422 		// cancels all wizards, but leaves them in the array. It is illegal to
423 		// call any other method than dispose() after this call.
424 		final ListIterator<IWizard> i = wizards.listIterator(wizards.size());
425 		// perform cancel in reversed order
426 		for (; i.hasPrevious(); ) {
427 			final IWizard wizard = i.previous();
428 			// each wizard must be able to be cancelled anytime...
429 			if (!wizard.performCancel()) {
430 				EuroMath.log(IStatus.WARNING, 0, "Unexpected: Wizard " + wizard //$NON-NLS-1$
431 						+ " is not cancellable.", null); //$NON-NLS-1$
432 			}
433 		}
434 		provider.performCancel();
435 		return true;
436 	}
437 	/*
438 	 * (non-Javadoc)
439 	 * @see org.eclipse.jface.wizard.IWizard#performFinish()
440 	 */
441 	public boolean performFinish() {
442 		// return false if we cannot finish
443 		if (!canFinish())
444 			return false;
445 		// performs finish on all wizards that have been created
446 		for (IWizard wizard : wizards) {
447 			// no wizard can fail now...
448 			if (!wizard.performFinish())
449 				throw new RuntimeException("Unexpected: Wizard " + wizard //$NON-NLS-1$
450 						+ " cannot finish"); //$NON-NLS-1$
451 		}
452 		provider.performFinish();
453 		return true;
454 	}
455 	/***
456 	 * Returns the provider instance. The provider's <code>next()</code> and
457 	 * <code>previous()</code> methods must not be called.
458 	 * @return the provider instance.
459 	 */
460 	public final IMultiWizardProvider getProvider() {
461 		return provider;
462 	}
463 }