comparison dwtx/jface/wizard/WizardDialog.d @ 8:a3ff22a98bef

Dialog
author Frank Benoit <benoit@tionex.de>
date Sat, 29 Mar 2008 01:25:27 +0100
parents
children 6c14e54dfc11
comparison
equal deleted inserted replaced
7:8a302fdb4140 8:a3ff22a98bef
1 /*******************************************************************************
2 * Copyright (c) 2000, 2007 IBM Corporation and others.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/epl-v10.html
7 *
8 * Contributors:
9 * IBM Corporation - initial API and implementation
10 * Chris Gross (schtoo@schtoo.com) - patch for bug 16179
11 * Port to the D programming language:
12 * Frank Benoit <benoit@tionex.de>
13 *******************************************************************************/
14 module dwtx.jface.wizard.WizardDialog;
15
16 class WizardDialog {
17 }
18
19
20 /++
21 import java.lang.reflect.InvocationTargetException;
22 import java.util.ArrayList;
23 import java.util.HashMap;
24 import java.util.Map;
25
26 import dwt.DWT;
27 import dwt.custom.BusyIndicator;
28 import dwt.events.HelpEvent;
29 import dwt.events.HelpListener;
30 import dwt.events.SelectionAdapter;
31 import dwt.events.SelectionEvent;
32 import dwt.graphics.Cursor;
33 import dwt.graphics.Point;
34 import dwt.graphics.Rectangle;
35 import dwt.layout.GridData;
36 import dwt.layout.GridLayout;
37 import dwt.widgets.Button;
38 import dwt.widgets.Composite;
39 import dwt.widgets.Control;
40 import dwt.widgets.Display;
41 import dwt.widgets.Label;
42 import dwt.widgets.Layout;
43 import dwt.widgets.Shell;
44 import dwtx.core.runtime.Assert;
45 import dwtx.core.runtime.IProgressMonitor;
46 import dwtx.core.runtime.IStatus;
47 import dwtx.core.runtime.ListenerList;
48 import dwtx.jface.dialogs.ControlEnableState;
49 import dwtx.jface.dialogs.IDialogConstants;
50 import dwtx.jface.dialogs.IMessageProvider;
51 import dwtx.jface.dialogs.IPageChangeProvider;
52 import dwtx.jface.dialogs.IPageChangedListener;
53 import dwtx.jface.dialogs.IPageChangingListener;
54 import dwtx.jface.dialogs.MessageDialog;
55 import dwtx.jface.dialogs.PageChangedEvent;
56 import dwtx.jface.dialogs.PageChangingEvent;
57 import dwtx.jface.dialogs.TitleAreaDialog;
58 import dwtx.jface.operation.IRunnableWithProgress;
59 import dwtx.jface.operation.ModalContext;
60 import dwtx.jface.resource.JFaceResources;
61 import dwtx.jface.util.SafeRunnable;
62
63 /**
64 * A dialog to show a wizard to the end user.
65 * <p>
66 * In typical usage, the client instantiates this class with a particular
67 * wizard. The dialog serves as the wizard container and orchestrates the
68 * presentation of its pages.
69 * <p>
70 * The standard layout is roughly as follows: it has an area at the top
71 * containing both the wizard's title, description, and image; the actual wizard
72 * page appears in the middle; below that is a progress indicator (which is made
73 * visible if needed); and at the bottom of the page is message line and a
74 * button bar containing Help, Next, Back, Finish, and Cancel buttons (or some
75 * subset).
76 * </p>
77 * <p>
78 * Clients may subclass <code>WizardDialog</code>, although this is rarely
79 * required.
80 * </p>
81 */
82 public class WizardDialog extends TitleAreaDialog implements IWizardContainer2,
83 IPageChangeProvider {
84 /**
85 * Image registry key for error message image (value
86 * <code>"dialog_title_error_image"</code>).
87 */
88 public static final String WIZ_IMG_ERROR = "dialog_title_error_image"; //$NON-NLS-1$
89
90 // The wizard the dialog is currently showing.
91 private IWizard wizard;
92
93 // Wizards to dispose
94 private ArrayList createdWizards = new ArrayList();
95
96 // Current nested wizards
97 private ArrayList nestedWizards = new ArrayList();
98
99 // The currently displayed page.
100 private IWizardPage currentPage = null;
101
102 // The number of long running operation executed from the dialog.
103 private long activeRunningOperations = 0;
104
105 // The current page message and description
106 private String pageMessage;
107
108 private int pageMessageType = IMessageProvider.NONE;
109
110 private String pageDescription;
111
112 // The progress monitor
113 private ProgressMonitorPart progressMonitorPart;
114
115 private Cursor waitCursor;
116
117 private Cursor arrowCursor;
118
119 private MessageDialog windowClosingDialog;
120
121 // Navigation buttons
122 private Button backButton;
123
124 private Button nextButton;
125
126 private Button finishButton;
127
128 private Button cancelButton;
129
130 private Button helpButton;
131
132 private SelectionAdapter cancelListener;
133
134 private bool isMovingToPreviousPage = false;
135
136 private Composite pageContainer;
137
138 private PageContainerFillLayout pageContainerLayout = new PageContainerFillLayout(
139 5, 5, 300, 225);
140
141 private int pageWidth = DWT.DEFAULT;
142
143 private int pageHeight = DWT.DEFAULT;
144
145 private static final String FOCUS_CONTROL = "focusControl"; //$NON-NLS-1$
146
147 private bool lockedUI = false;
148
149 private ListenerList pageChangedListeners = new ListenerList();
150
151 private ListenerList pageChangingListeners = new ListenerList();
152
153 /**
154 * A layout for a container which includes several pages, like a notebook,
155 * wizard, or preference dialog. The size computed by this layout is the
156 * maximum width and height of all pages currently inserted into the
157 * container.
158 */
159 protected class PageContainerFillLayout extends Layout {
160 /**
161 * The margin width; <code>5</code> pixels by default.
162 */
163 public int marginWidth = 5;
164
165 /**
166 * The margin height; <code>5</code> pixels by default.
167 */
168 public int marginHeight = 5;
169
170 /**
171 * The minimum width; <code>0</code> pixels by default.
172 */
173 public int minimumWidth = 0;
174
175 /**
176 * The minimum height; <code>0</code> pixels by default.
177 */
178 public int minimumHeight = 0;
179
180 /**
181 * Creates new layout object.
182 *
183 * @param mw
184 * the margin width
185 * @param mh
186 * the margin height
187 * @param minW
188 * the minimum width
189 * @param minH
190 * the minimum height
191 */
192 public PageContainerFillLayout(int mw, int mh, int minW, int minH) {
193 marginWidth = mw;
194 marginHeight = mh;
195 minimumWidth = minW;
196 minimumHeight = minH;
197 }
198
199 /*
200 * (non-Javadoc) Method declared on Layout.
201 */
202 public Point computeSize(Composite composite, int wHint, int hHint,
203 bool force) {
204 if (wHint !is DWT.DEFAULT && hHint !is DWT.DEFAULT) {
205 return new Point(wHint, hHint);
206 }
207 Point result = null;
208 Control[] children = composite.getChildren();
209 if (children.length > 0) {
210 result = new Point(0, 0);
211 for (int i = 0; i < children.length; i++) {
212 Point cp = children[i].computeSize(wHint, hHint, force);
213 result.x = Math.max(result.x, cp.x);
214 result.y = Math.max(result.y, cp.y);
215 }
216 result.x = result.x + 2 * marginWidth;
217 result.y = result.y + 2 * marginHeight;
218 } else {
219 Rectangle rect = composite.getClientArea();
220 result = new Point(rect.width, rect.height);
221 }
222 result.x = Math.max(result.x, minimumWidth);
223 result.y = Math.max(result.y, minimumHeight);
224 if (wHint !is DWT.DEFAULT) {
225 result.x = wHint;
226 }
227 if (hHint !is DWT.DEFAULT) {
228 result.y = hHint;
229 }
230 return result;
231 }
232
233 /**
234 * Returns the client area for the given composite according to this
235 * layout.
236 *
237 * @param c
238 * the composite
239 * @return the client area rectangle
240 */
241 public Rectangle getClientArea(Composite c) {
242 Rectangle rect = c.getClientArea();
243 rect.x = rect.x + marginWidth;
244 rect.y = rect.y + marginHeight;
245 rect.width = rect.width - 2 * marginWidth;
246 rect.height = rect.height - 2 * marginHeight;
247 return rect;
248 }
249
250 /*
251 * (non-Javadoc) Method declared on Layout.
252 */
253 public void layout(Composite composite, bool force) {
254 Rectangle rect = getClientArea(composite);
255 Control[] children = composite.getChildren();
256 for (int i = 0; i < children.length; i++) {
257 children[i].setBounds(rect);
258 }
259 }
260
261 /**
262 * Lays outs the page according to this layout.
263 *
264 * @param w
265 * the control
266 */
267 public void layoutPage(Control w) {
268 w.setBounds(getClientArea(w.getParent()));
269 }
270
271 /**
272 * Sets the location of the page so that its origin is in the upper left
273 * corner.
274 *
275 * @param w
276 * the control
277 */
278 public void setPageLocation(Control w) {
279 w.setLocation(marginWidth, marginHeight);
280 }
281 }
282
283 /**
284 * Creates a new wizard dialog for the given wizard.
285 *
286 * @param parentShell
287 * the parent shell
288 * @param newWizard
289 * the wizard this dialog is working on
290 */
291 public WizardDialog(Shell parentShell, IWizard newWizard) {
292 super(parentShell);
293 setShellStyle(DWT.CLOSE | DWT.MAX | DWT.TITLE | DWT.BORDER
294 | DWT.APPLICATION_MODAL | DWT.RESIZE | getDefaultOrientation());
295 setWizard(newWizard);
296 // since VAJava can't initialize an instance var with an anonymous
297 // class outside a constructor we do it here:
298 cancelListener = new SelectionAdapter() {
299 public void widgetSelected(SelectionEvent e) {
300 cancelPressed();
301 }
302 };
303 }
304
305 /**
306 * About to start a long running operation triggered through the wizard.
307 * Shows the progress monitor and disables the wizard's buttons and
308 * controls.
309 *
310 * @param enableCancelButton
311 * <code>true</code> if the Cancel button should be enabled,
312 * and <code>false</code> if it should be disabled
313 * @return the saved UI state
314 */
315 private Object aboutToStart(bool enableCancelButton) {
316 Map savedState = null;
317 if (getShell() !is null) {
318 // Save focus control
319 Control focusControl = getShell().getDisplay().getFocusControl();
320 if (focusControl !is null && focusControl.getShell() !is getShell()) {
321 focusControl = null;
322 }
323 bool needsProgressMonitor = wizard.needsProgressMonitor();
324 cancelButton.removeSelectionListener(cancelListener);
325 // Set the busy cursor to all shells.
326 Display d = getShell().getDisplay();
327 waitCursor = new Cursor(d, DWT.CURSOR_WAIT);
328 setDisplayCursor(waitCursor);
329 // Set the arrow cursor to the cancel component.
330 arrowCursor = new Cursor(d, DWT.CURSOR_ARROW);
331 cancelButton.setCursor(arrowCursor);
332 // Deactivate shell
333 savedState = saveUIState(needsProgressMonitor && enableCancelButton);
334 if (focusControl !is null) {
335 savedState.put(FOCUS_CONTROL, focusControl);
336 }
337 // Attach the progress monitor part to the cancel button
338 if (needsProgressMonitor) {
339 progressMonitorPart.attachToCancelComponent(cancelButton);
340 progressMonitorPart.setVisible(true);
341 }
342 }
343 return savedState;
344 }
345
346 /**
347 * The Back button has been pressed.
348 */
349 protected void backPressed() {
350 IWizardPage page = currentPage.getPreviousPage();
351 if (page is null) {
352 // should never happen since we have already visited the page
353 return;
354 }
355
356 // set flag to indicate that we are moving back
357 isMovingToPreviousPage = true;
358 // show the page
359 showPage(page);
360 }
361
362 /*
363 * (non-Javadoc) Method declared on Dialog.
364 */
365 protected void buttonPressed(int buttonId) {
366 switch (buttonId) {
367 case IDialogConstants.HELP_ID: {
368 helpPressed();
369 break;
370 }
371 case IDialogConstants.BACK_ID: {
372 backPressed();
373 break;
374 }
375 case IDialogConstants.NEXT_ID: {
376 nextPressed();
377 break;
378 }
379 case IDialogConstants.FINISH_ID: {
380 finishPressed();
381 break;
382 }
383 // The Cancel button has a listener which calls cancelPressed
384 // directly
385 }
386 }
387
388 /**
389 * Calculates the difference in size between the given page and the page
390 * container. A larger page results in a positive delta.
391 *
392 * @param page
393 * the page
394 * @return the size difference encoded as a
395 * <code>new Point(deltaWidth,deltaHeight)</code>
396 */
397 private Point calculatePageSizeDelta(IWizardPage page) {
398 Control pageControl = page.getControl();
399 if (pageControl is null) {
400 // control not created yet
401 return new Point(0, 0);
402 }
403 Point contentSize = pageControl.computeSize(DWT.DEFAULT, DWT.DEFAULT,
404 true);
405 Rectangle rect = pageContainerLayout.getClientArea(pageContainer);
406 Point containerSize = new Point(rect.width, rect.height);
407 return new Point(Math.max(0, contentSize.x - containerSize.x), Math
408 .max(0, contentSize.y - containerSize.y));
409 }
410
411 /*
412 * (non-Javadoc) Method declared on Dialog.
413 */
414 protected void cancelPressed() {
415 if (activeRunningOperations <= 0) {
416 // Close the dialog. The check whether the dialog can be
417 // closed or not is done in <code>okToClose</code>.
418 // This ensures that the check is also evaluated when the user
419 // presses the window's close button.
420 setReturnCode(CANCEL);
421 close();
422 } else {
423 cancelButton.setEnabled(false);
424 }
425 }
426
427 /*
428 * (non-Javadoc)
429 *
430 * @see dwtx.jface.window.Window#close()
431 */
432 public bool close() {
433 if (okToClose()) {
434 return hardClose();
435 }
436 return false;
437 }
438
439 /*
440 * (non-Javadoc) Method declared on Window.
441 */
442 protected void configureShell(Shell newShell) {
443 super.configureShell(newShell);
444 // Register help listener on the shell
445 newShell.addHelpListener(new HelpListener() {
446 public void helpRequested(HelpEvent event) {
447 // call perform help on the current page
448 if (currentPage !is null) {
449 currentPage.performHelp();
450 }
451 }
452 });
453 }
454
455 /**
456 * Creates the buttons for this dialog's button bar.
457 * <p>
458 * The <code>WizardDialog</code> implementation of this framework method
459 * prevents the parent composite's columns from being made equal width in
460 * order to remove the margin between the Back and Next buttons.
461 * </p>
462 *
463 * @param parent
464 * the parent composite to contain the buttons
465 */
466 protected void createButtonsForButtonBar(Composite parent) {
467 ((GridLayout) parent.getLayout()).makeColumnsEqualWidth = false;
468 if (wizard.isHelpAvailable()) {
469 helpButton = createButton(parent, IDialogConstants.HELP_ID,
470 IDialogConstants.HELP_LABEL, false);
471 }
472 if (wizard.needsPreviousAndNextButtons()) {
473 createPreviousAndNextButtons(parent);
474 }
475 finishButton = createButton(parent, IDialogConstants.FINISH_ID,
476 IDialogConstants.FINISH_LABEL, true);
477 cancelButton = createCancelButton(parent);
478 }
479
480 /*
481 * (non-Javadoc)
482 *
483 * @see dwtx.jface.dialogs.Dialog#setButtonLayoutData(dwt.widgets.Button)
484 */
485 protected void setButtonLayoutData(Button button) {
486 GridData data = new GridData(GridData.HORIZONTAL_ALIGN_FILL);
487 int widthHint = convertHorizontalDLUsToPixels(IDialogConstants.BUTTON_WIDTH);
488
489 // On large fonts this can make this dialog huge
490 widthHint = Math.min(widthHint,
491 button.getDisplay().getBounds().width / 5);
492 Point minSize = button.computeSize(DWT.DEFAULT, DWT.DEFAULT, true);
493 data.widthHint = Math.max(widthHint, minSize.x);
494
495 button.setLayoutData(data);
496 }
497
498 /**
499 * Creates the Cancel button for this wizard dialog. Creates a standard (<code>DWT.PUSH</code>)
500 * button and registers for its selection events. Note that the number of
501 * columns in the button bar composite is incremented. The Cancel button is
502 * created specially to give it a removeable listener.
503 *
504 * @param parent
505 * the parent button bar
506 * @return the new Cancel button
507 */
508 private Button createCancelButton(Composite parent) {
509 // increment the number of columns in the button bar
510 ((GridLayout) parent.getLayout()).numColumns++;
511 Button button = new Button(parent, DWT.PUSH);
512 button.setText(IDialogConstants.CANCEL_LABEL);
513 setButtonLayoutData(button);
514 button.setFont(parent.getFont());
515 button.setData(new Integer(IDialogConstants.CANCEL_ID));
516 button.addSelectionListener(cancelListener);
517 return button;
518 }
519
520 /**
521 * Return the cancel button if the id is a the cancel id.
522 *
523 * @param id
524 * the button id
525 * @return the button corresponding to the button id
526 */
527 protected Button getButton(int id) {
528 if (id is IDialogConstants.CANCEL_ID) {
529 return cancelButton;
530 }
531 return super.getButton(id);
532 }
533
534 /**
535 * The <code>WizardDialog</code> implementation of this
536 * <code>Window</code> method calls call <code>IWizard.addPages</code>
537 * to allow the current wizard to add extra pages, then
538 * <code>super.createContents</code> to create the controls. It then calls
539 * <code>IWizard.createPageControls</code> to allow the wizard to
540 * pre-create their page controls prior to opening, so that the wizard opens
541 * to the correct size. And finally it shows the first page.
542 */
543 protected Control createContents(Composite parent) {
544 // Allow the wizard to add pages to itself
545 // Need to call this now so page count is correct
546 // for determining if next/previous buttons are needed
547 wizard.addPages();
548 Control contents = super.createContents(parent);
549 // Allow the wizard pages to precreate their page controls
550 createPageControls();
551 // Show the first page
552 showStartingPage();
553 return contents;
554 }
555
556 /*
557 * (non-Javadoc) Method declared on Dialog.
558 */
559 protected Control createDialogArea(Composite parent) {
560 Composite composite = (Composite) super.createDialogArea(parent);
561 // Build the Page container
562 pageContainer = createPageContainer(composite);
563 GridData gd = new GridData(GridData.FILL_BOTH);
564 gd.widthHint = pageWidth;
565 gd.heightHint = pageHeight;
566 pageContainer.setLayoutData(gd);
567 pageContainer.setFont(parent.getFont());
568 // Insert a progress monitor
569 GridLayout pmlayout = new GridLayout();
570 pmlayout.numColumns = 1;
571 progressMonitorPart = createProgressMonitorPart(composite, pmlayout);
572 GridData gridData = new GridData(GridData.FILL_HORIZONTAL);
573 progressMonitorPart.setLayoutData(gridData);
574 progressMonitorPart.setVisible(false);
575 // Build the separator line
576 Label separator = new Label(composite, DWT.HORIZONTAL | DWT.SEPARATOR);
577 separator.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
578
579 applyDialogFont(progressMonitorPart);
580 return composite;
581 }
582
583 /**
584 * Create the progress monitor part in the receiver.
585 *
586 * @param composite
587 * @param pmlayout
588 * @return ProgressMonitorPart
589 */
590 protected ProgressMonitorPart createProgressMonitorPart(
591 Composite composite, GridLayout pmlayout) {
592 return new ProgressMonitorPart(composite, pmlayout, DWT.DEFAULT) {
593 String currentTask = null;
594
595 /*
596 * (non-Javadoc)
597 *
598 * @see dwtx.jface.wizard.ProgressMonitorPart#setBlocked(dwtx.core.runtime.IStatus)
599 */
600 public void setBlocked(IStatus reason) {
601 super.setBlocked(reason);
602 if (!lockedUI) {
603 getBlockedHandler().showBlocked(getShell(), this, reason,
604 currentTask);
605 }
606 }
607
608 /*
609 * (non-Javadoc)
610 *
611 * @see dwtx.jface.wizard.ProgressMonitorPart#clearBlocked()
612 */
613 public void clearBlocked() {
614 super.clearBlocked();
615 if (!lockedUI) {
616 getBlockedHandler().clearBlocked();
617 }
618 }
619
620 /*
621 * (non-Javadoc)
622 *
623 * @see dwtx.jface.wizard.ProgressMonitorPart#beginTask(java.lang.String,
624 * int)
625 */
626 public void beginTask(String name, int totalWork) {
627 super.beginTask(name, totalWork);
628 currentTask = name;
629 }
630
631 /*
632 * (non-Javadoc)
633 *
634 * @see dwtx.jface.wizard.ProgressMonitorPart#setTaskName(java.lang.String)
635 */
636 public void setTaskName(String name) {
637 super.setTaskName(name);
638 currentTask = name;
639 }
640
641 /*
642 * (non-Javadoc)
643 *
644 * @see dwtx.jface.wizard.ProgressMonitorPart#subTask(java.lang.String)
645 */
646 public void subTask(String name) {
647 super.subTask(name);
648 // If we haven't got anything yet use this value for more
649 // context
650 if (currentTask is null) {
651 currentTask = name;
652 }
653 }
654 };
655 }
656
657 /**
658 * Creates the container that holds all pages.
659 *
660 * @param parent
661 * @return Composite
662 */
663 private Composite createPageContainer(Composite parent) {
664 Composite result = new Composite(parent, DWT.NULL);
665 result.setLayout(pageContainerLayout);
666 return result;
667 }
668
669 /**
670 * Allow the wizard's pages to pre-create their page controls. This allows
671 * the wizard dialog to open to the correct size.
672 */
673 private void createPageControls() {
674 // Allow the wizard pages to precreate their page controls
675 // This allows the wizard to open to the correct size
676 wizard.createPageControls(pageContainer);
677 // Ensure that all of the created pages are initially not visible
678 IWizardPage[] pages = wizard.getPages();
679 for (int i = 0; i < pages.length; i++) {
680 IWizardPage page = pages[i];
681 if (page.getControl() !is null) {
682 page.getControl().setVisible(false);
683 }
684 }
685 }
686
687 /**
688 * Creates the Previous and Next buttons for this wizard dialog. Creates
689 * standard (<code>DWT.PUSH</code>) buttons and registers for their
690 * selection events. Note that the number of columns in the button bar
691 * composite is incremented. These buttons are created specially to prevent
692 * any space between them.
693 *
694 * @param parent
695 * the parent button bar
696 * @return a composite containing the new buttons
697 */
698 private Composite createPreviousAndNextButtons(Composite parent) {
699 // increment the number of columns in the button bar
700 ((GridLayout) parent.getLayout()).numColumns++;
701 Composite composite = new Composite(parent, DWT.NONE);
702 // create a layout with spacing and margins appropriate for the font
703 // size.
704 GridLayout layout = new GridLayout();
705 layout.numColumns = 0; // will be incremented by createButton
706 layout.marginWidth = 0;
707 layout.marginHeight = 0;
708 layout.horizontalSpacing = 0;
709 layout.verticalSpacing = 0;
710 composite.setLayout(layout);
711 GridData data = new GridData(GridData.HORIZONTAL_ALIGN_CENTER
712 | GridData.VERTICAL_ALIGN_CENTER);
713 composite.setLayoutData(data);
714 composite.setFont(parent.getFont());
715 backButton = createButton(composite, IDialogConstants.BACK_ID,
716 IDialogConstants.BACK_LABEL, false);
717 nextButton = createButton(composite, IDialogConstants.NEXT_ID,
718 IDialogConstants.NEXT_LABEL, false);
719 return composite;
720 }
721
722 /**
723 * Creates and return a new wizard closing dialog without openiong it.
724 *
725 * @return MessageDalog
726 */
727 private MessageDialog createWizardClosingDialog() {
728 MessageDialog result = new MessageDialog(getShell(),
729 JFaceResources.getString("WizardClosingDialog.title"), //$NON-NLS-1$
730 null,
731 JFaceResources.getString("WizardClosingDialog.message"), //$NON-NLS-1$
732 MessageDialog.QUESTION,
733 new String[] { IDialogConstants.OK_LABEL }, 0);
734 return result;
735 }
736
737 /**
738 * The Finish button has been pressed.
739 */
740 protected void finishPressed() {
741 // Wizards are added to the nested wizards list in setWizard.
742 // This means that the current wizard is always the last wizard in the
743 // list.
744 // Note that we first call the current wizard directly (to give it a
745 // chance to
746 // abort, do work, and save state) then call the remaining n-1 wizards
747 // in the
748 // list (to save state).
749 if (wizard.performFinish()) {
750 // Call perform finish on outer wizards in the nested chain
751 // (to allow them to save state for example)
752 for (int i = 0; i < nestedWizards.size() - 1; i++) {
753 ((IWizard) nestedWizards.get(i)).performFinish();
754 }
755 // Hard close the dialog.
756 setReturnCode(OK);
757 hardClose();
758 }
759 }
760
761 /*
762 * (non-Javadoc) Method declared on IWizardContainer.
763 */
764 public IWizardPage getCurrentPage() {
765 return currentPage;
766 }
767
768 /**
769 * Returns the progress monitor for this wizard dialog (if it has one).
770 *
771 * @return the progress monitor, or <code>null</code> if this wizard
772 * dialog does not have one
773 */
774 protected IProgressMonitor getProgressMonitor() {
775 return progressMonitorPart;
776 }
777
778 /**
779 * Returns the wizard this dialog is currently displaying.
780 *
781 * @return the current wizard
782 */
783 protected IWizard getWizard() {
784 return wizard;
785 }
786
787 /**
788 * Closes this window.
789 *
790 * @return <code>true</code> if the window is (or was already) closed, and
791 * <code>false</code> if it is still open
792 */
793 private bool hardClose() {
794 // inform wizards
795 for (int i = 0; i < createdWizards.size(); i++) {
796 IWizard createdWizard = (IWizard) createdWizards.get(i);
797 createdWizard.dispose();
798 // Remove this dialog as a parent from the managed wizard.
799 // Note that we do this after calling dispose as the wizard or
800 // its pages may need access to the container during
801 // dispose code
802 createdWizard.setContainer(null);
803 }
804 return super.close();
805 }
806
807 /**
808 * The Help button has been pressed.
809 */
810 protected void helpPressed() {
811 if (currentPage !is null) {
812 currentPage.performHelp();
813 }
814 }
815
816 /**
817 * The Next button has been pressed.
818 */
819 protected void nextPressed() {
820 IWizardPage page = currentPage.getNextPage();
821 if (page is null) {
822 // something must have happend getting the next page
823 return;
824 }
825
826 // show the next page
827 showPage(page);
828 }
829
830 /**
831 * Notifies page changing listeners and returns result of page changing
832 * processing to the sender.
833 *
834 * @param eventType
835 * @return <code>true</code> if page changing listener completes
836 * successfully, <code>false</code> otherwise
837 */
838 private bool doPageChanging(IWizardPage targetPage) {
839 PageChangingEvent e = new PageChangingEvent(this, getCurrentPage(),
840 targetPage);
841 firePageChanging(e);
842 // Prevent navigation if necessary
843 return e.doit;
844 }
845
846 /**
847 * Checks whether it is alright to close this wizard dialog and performed
848 * standard cancel processing. If there is a long running operation in
849 * progress, this method posts an alert message saying that the wizard
850 * cannot be closed.
851 *
852 * @return <code>true</code> if it is alright to close this dialog, and
853 * <code>false</code> if it is not
854 */
855 private bool okToClose() {
856 if (activeRunningOperations > 0) {
857 synchronized (this) {
858 windowClosingDialog = createWizardClosingDialog();
859 }
860 windowClosingDialog.open();
861 synchronized (this) {
862 windowClosingDialog = null;
863 }
864 return false;
865 }
866 return wizard.performCancel();
867 }
868
869 /**
870 * Restores the enabled/disabled state of the given control.
871 *
872 * @param w
873 * the control
874 * @param h
875 * the map (key type: <code>String</code>, element type:
876 * <code>bool</code>)
877 * @param key
878 * the key
879 * @see #saveEnableStateAndSet
880 */
881 private void restoreEnableState(Control w, Map h, String key) {
882 if (w !is null) {
883 bool b = (bool) h.get(key);
884 if (b !is null) {
885 w.setEnabled(b.booleanValue());
886 }
887 }
888 }
889
890 /**
891 * Restores the enabled/disabled state of the wizard dialog's buttons and
892 * the tree of controls for the currently showing page.
893 *
894 * @param state
895 * a map containing the saved state as returned by
896 * <code>saveUIState</code>
897 * @see #saveUIState
898 */
899 private void restoreUIState(Map state) {
900 restoreEnableState(backButton, state, "back"); //$NON-NLS-1$
901 restoreEnableState(nextButton, state, "next"); //$NON-NLS-1$
902 restoreEnableState(finishButton, state, "finish"); //$NON-NLS-1$
903 restoreEnableState(cancelButton, state, "cancel"); //$NON-NLS-1$
904 restoreEnableState(helpButton, state, "help"); //$NON-NLS-1$
905 Object pageValue = state.get("page"); //$NON-NLS-1$
906 if (pageValue !is null) {
907 ((ControlEnableState) pageValue).restore();
908 }
909 }
910
911 /**
912 * This implementation of IRunnableContext#run(bool, bool,
913 * IRunnableWithProgress) blocks until the runnable has been run, regardless
914 * of the value of <code>fork</code>. It is recommended that
915 * <code>fork</code> is set to true in most cases. If <code>fork</code>
916 * is set to <code>false</code>, the runnable will run in the UI thread
917 * and it is the runnable's responsibility to call
918 * <code>Display.readAndDispatch()</code> to ensure UI responsiveness.
919 *
920 * UI state is saved prior to executing the long-running operation and is
921 * restored after the long-running operation completes executing. Any
922 * attempt to change the UI state of the wizard in the long-running
923 * operation will be nullified when original UI state is restored.
924 *
925 */
926 public void run(bool fork, bool cancelable,
927 IRunnableWithProgress runnable) throws InvocationTargetException,
928 InterruptedException {
929 // The operation can only be canceled if it is executed in a separate
930 // thread.
931 // Otherwise the UI is blocked anyway.
932 Object state = null;
933 if (activeRunningOperations is 0) {
934 state = aboutToStart(fork && cancelable);
935 }
936 activeRunningOperations++;
937 try {
938 if (!fork) {
939 lockedUI = true;
940 }
941 ModalContext.run(runnable, fork, getProgressMonitor(), getShell()
942 .getDisplay());
943 lockedUI = false;
944 } finally {
945 activeRunningOperations--;
946 // Stop if this is the last one
947 if (state !is null) {
948 stopped(state);
949 }
950 }
951 }
952
953 /**
954 * Saves the enabled/disabled state of the given control in the given map,
955 * which must be modifiable.
956 *
957 * @param w
958 * the control, or <code>null</code> if none
959 * @param h
960 * the map (key type: <code>String</code>, element type:
961 * <code>bool</code>)
962 * @param key
963 * the key
964 * @param enabled
965 * <code>true</code> to enable the control, and
966 * <code>false</code> to disable it
967 * @see #restoreEnableState(Control, Map, String)
968 */
969 private void saveEnableStateAndSet(Control w, Map h, String key,
970 bool enabled) {
971 if (w !is null) {
972 h.put(key, w.getEnabled() ? bool.TRUE : bool.FALSE);
973 w.setEnabled(enabled);
974 }
975 }
976
977 /**
978 * Captures and returns the enabled/disabled state of the wizard dialog's
979 * buttons and the tree of controls for the currently showing page. All
980 * these controls are disabled in the process, with the possible exception
981 * of the Cancel button.
982 *
983 * @param keepCancelEnabled
984 * <code>true</code> if the Cancel button should remain
985 * enabled, and <code>false</code> if it should be disabled
986 * @return a map containing the saved state suitable for restoring later
987 * with <code>restoreUIState</code>
988 * @see #restoreUIState
989 */
990 private Map saveUIState(bool keepCancelEnabled) {
991 Map savedState = new HashMap(10);
992 saveEnableStateAndSet(backButton, savedState, "back", false); //$NON-NLS-1$
993 saveEnableStateAndSet(nextButton, savedState, "next", false); //$NON-NLS-1$
994 saveEnableStateAndSet(finishButton, savedState, "finish", false); //$NON-NLS-1$
995 saveEnableStateAndSet(cancelButton, savedState,
996 "cancel", keepCancelEnabled); //$NON-NLS-1$
997 saveEnableStateAndSet(helpButton, savedState, "help", false); //$NON-NLS-1$
998 if (currentPage !is null) {
999 savedState
1000 .put(
1001 "page", ControlEnableState.disable(currentPage.getControl())); //$NON-NLS-1$
1002 }
1003 return savedState;
1004 }
1005
1006 /**
1007 * Sets the given cursor for all shells currently active for this window's
1008 * display.
1009 *
1010 * @param c
1011 * the cursor
1012 */
1013 private void setDisplayCursor(Cursor c) {
1014 Shell[] shells = getShell().getDisplay().getShells();
1015 for (int i = 0; i < shells.length; i++) {
1016 shells[i].setCursor(c);
1017 }
1018 }
1019
1020 /**
1021 * Sets the minimum page size used for the pages.
1022 *
1023 * @param minWidth
1024 * the minimum page width
1025 * @param minHeight
1026 * the minimum page height
1027 * @see #setMinimumPageSize(Point)
1028 */
1029 public void setMinimumPageSize(int minWidth, int minHeight) {
1030 Assert.isTrue(minWidth >= 0 && minHeight >= 0);
1031 pageContainerLayout.minimumWidth = minWidth;
1032 pageContainerLayout.minimumHeight = minHeight;
1033 }
1034
1035 /**
1036 * Sets the minimum page size used for the pages.
1037 *
1038 * @param size
1039 * the page size encoded as <code>new Point(width,height)</code>
1040 * @see #setMinimumPageSize(int,int)
1041 */
1042 public void setMinimumPageSize(Point size) {
1043 setMinimumPageSize(size.x, size.y);
1044 }
1045
1046 /**
1047 * Sets the size of all pages. The given size takes precedence over computed
1048 * sizes.
1049 *
1050 * @param width
1051 * the page width
1052 * @param height
1053 * the page height
1054 * @see #setPageSize(Point)
1055 */
1056 public void setPageSize(int width, int height) {
1057 pageWidth = width;
1058 pageHeight = height;
1059 }
1060
1061 /**
1062 * Sets the size of all pages. The given size takes precedence over computed
1063 * sizes.
1064 *
1065 * @param size
1066 * the page size encoded as <code>new Point(width,height)</code>
1067 * @see #setPageSize(int,int)
1068 */
1069 public void setPageSize(Point size) {
1070 setPageSize(size.x, size.y);
1071 }
1072
1073 /**
1074 * Sets the wizard this dialog is currently displaying.
1075 *
1076 * @param newWizard
1077 * the wizard
1078 */
1079 protected void setWizard(IWizard newWizard) {
1080 wizard = newWizard;
1081 wizard.setContainer(this);
1082 if (!createdWizards.contains(wizard)) {
1083 createdWizards.add(wizard);
1084 // New wizard so just add it to the end of our nested list
1085 nestedWizards.add(wizard);
1086 if (pageContainer !is null) {
1087 // Dialog is already open
1088 // Allow the wizard pages to precreate their page controls
1089 // This allows the wizard to open to the correct size
1090 createPageControls();
1091 // Ensure the dialog is large enough for the wizard
1092 updateSizeForWizard(wizard);
1093 pageContainer.layout(true);
1094 }
1095 } else {
1096 // We have already seen this wizard, if it is the previous wizard
1097 // on the nested list then we assume we have gone back and remove
1098 // the last wizard from the list
1099 int size = nestedWizards.size();
1100 if (size >= 2 && nestedWizards.get(size - 2) is wizard) {
1101 nestedWizards.remove(size - 1);
1102 } else {
1103 // Assume we are going forward to revisit a wizard
1104 nestedWizards.add(wizard);
1105 }
1106 }
1107 }
1108
1109 /*
1110 * (non-Javadoc) Method declared on IWizardContainer.
1111 */
1112 public void showPage(IWizardPage page) {
1113 if (page is null || page is currentPage) {
1114 return;
1115 }
1116
1117 if (!isMovingToPreviousPage) {
1118 // remember my previous page.
1119 page.setPreviousPage(currentPage);
1120 } else {
1121 isMovingToPreviousPage = false;
1122 }
1123
1124 // If page changing evaluation unsuccessful, do not change the page
1125 if (!doPageChanging(page))
1126 return;
1127
1128 // Update for the new page in a busy cursor if possible
1129 if (getContents() is null) {
1130 updateForPage(page);
1131 } else {
1132 final IWizardPage finalPage = page;
1133 BusyIndicator.showWhile(getContents().getDisplay(), new Runnable() {
1134 public void run() {
1135 updateForPage(finalPage);
1136 }
1137 });
1138 }
1139 }
1140
1141 /**
1142 * Update the receiver for the new page.
1143 *
1144 * @param page
1145 */
1146 private void updateForPage(IWizardPage page) {
1147 // ensure this page belongs to the current wizard
1148 if (wizard !is page.getWizard()) {
1149 setWizard(page.getWizard());
1150 }
1151 // ensure that page control has been created
1152 // (this allows lazy page control creation)
1153 if (page.getControl() is null) {
1154 page.createControl(pageContainer);
1155 // the page is responsible for ensuring the created control is
1156 // accessable
1157 // via getControl.
1158 Assert.isNotNull(page.getControl(), JFaceResources.format(
1159 JFaceResources.getString("WizardDialog.missingSetControl"), //$NON-NLS-1$
1160 new Object[] { page.getName() }));
1161 // ensure the dialog is large enough for this page
1162 updateSize(page);
1163 }
1164 // make the new page visible
1165 IWizardPage oldPage = currentPage;
1166 currentPage = page;
1167
1168 currentPage.setVisible(true);
1169 if (oldPage !is null) {
1170 oldPage.setVisible(false);
1171 }
1172 // update the dialog controls
1173 update();
1174 }
1175
1176 /**
1177 * Shows the starting page of the wizard.
1178 */
1179 private void showStartingPage() {
1180 currentPage = wizard.getStartingPage();
1181 if (currentPage is null) {
1182 // something must have happend getting the page
1183 return;
1184 }
1185 // ensure the page control has been created
1186 if (currentPage.getControl() is null) {
1187 currentPage.createControl(pageContainer);
1188 // the page is responsible for ensuring the created control is
1189 // accessable
1190 // via getControl.
1191 Assert.isNotNull(currentPage.getControl());
1192 // we do not need to update the size since the call
1193 // to initialize bounds has not been made yet.
1194 }
1195 // make the new page visible
1196 currentPage.setVisible(true);
1197 // update the dialog controls
1198 update();
1199 }
1200
1201 /**
1202 * A long running operation triggered through the wizard was stopped either
1203 * by user input or by normal end. Hides the progress monitor and restores
1204 * the enable state wizard's buttons and controls.
1205 *
1206 * @param savedState
1207 * the saved UI state as returned by <code>aboutToStart</code>
1208 * @see #aboutToStart
1209 */
1210 private void stopped(Object savedState) {
1211 if (getShell() !is null) {
1212 if (wizard.needsProgressMonitor()) {
1213 progressMonitorPart.setVisible(false);
1214 progressMonitorPart.removeFromCancelComponent(cancelButton);
1215 }
1216 Map state = (Map) savedState;
1217 restoreUIState(state);
1218 cancelButton.addSelectionListener(cancelListener);
1219 setDisplayCursor(null);
1220 cancelButton.setCursor(null);
1221 waitCursor.dispose();
1222 waitCursor = null;
1223 arrowCursor.dispose();
1224 arrowCursor = null;
1225 Control focusControl = (Control) state.get(FOCUS_CONTROL);
1226 if (focusControl !is null) {
1227 focusControl.setFocus();
1228 }
1229 }
1230 }
1231
1232 /**
1233 * Updates this dialog's controls to reflect the current page.
1234 */
1235 protected void update() {
1236 // Update the window title
1237 updateWindowTitle();
1238 // Update the title bar
1239 updateTitleBar();
1240 // Update the buttons
1241 updateButtons();
1242
1243 // Fires the page change event
1244 firePageChanged(new PageChangedEvent(this, getCurrentPage()));
1245 }
1246
1247 /*
1248 * (non-Javadoc) Method declared on IWizardContainer.
1249 */
1250 public void updateButtons() {
1251 bool canFlipToNextPage = false;
1252 bool canFinish = wizard.canFinish();
1253 if (backButton !is null) {
1254 backButton.setEnabled(currentPage.getPreviousPage() !is null);
1255 }
1256 if (nextButton !is null) {
1257 canFlipToNextPage = currentPage.canFlipToNextPage();
1258 nextButton.setEnabled(canFlipToNextPage);
1259 }
1260 finishButton.setEnabled(canFinish);
1261 // finish is default unless it is diabled and next is enabled
1262 if (canFlipToNextPage && !canFinish) {
1263 getShell().setDefaultButton(nextButton);
1264 } else {
1265 getShell().setDefaultButton(finishButton);
1266 }
1267 }
1268
1269 /**
1270 * Update the message line with the page's description.
1271 * <p>
1272 * A discription is shown only if there is no message or error message.
1273 * </p>
1274 */
1275 private void updateDescriptionMessage() {
1276 pageDescription = currentPage.getDescription();
1277 setMessage(pageDescription);
1278 }
1279
1280 /*
1281 * (non-Javadoc) Method declared on IWizardContainer.
1282 */
1283 public void updateMessage() {
1284
1285 if (currentPage is null) {
1286 return;
1287 }
1288
1289 pageMessage = currentPage.getMessage();
1290 if (pageMessage !is null && currentPage instanceof IMessageProvider) {
1291 pageMessageType = ((IMessageProvider) currentPage).getMessageType();
1292 } else {
1293 pageMessageType = IMessageProvider.NONE;
1294 }
1295 if (pageMessage is null) {
1296 setMessage(pageDescription);
1297 } else {
1298 setMessage(pageMessage, pageMessageType);
1299 }
1300 setErrorMessage(currentPage.getErrorMessage());
1301 }
1302
1303 /**
1304 * Changes the shell size to the given size, ensuring that it is no larger
1305 * than the display bounds.
1306 *
1307 * @param width
1308 * the shell width
1309 * @param height
1310 * the shell height
1311 */
1312 private void setShellSize(int width, int height) {
1313 Rectangle size = getShell().getBounds();
1314 size.height = height;
1315 size.width = width;
1316 getShell().setBounds(getConstrainedShellBounds(size));
1317 }
1318
1319 /**
1320 * Computes the correct dialog size for the current page and resizes its
1321 * shell if nessessary. Also causes the container to refresh its layout.
1322 *
1323 * @param page
1324 * the wizard page to use to resize the dialog
1325 * @since 2.0
1326 */
1327 protected void updateSize(IWizardPage page) {
1328 if (page is null || page.getControl() is null) {
1329 return;
1330 }
1331 updateSizeForPage(page);
1332 pageContainerLayout.layoutPage(page.getControl());
1333 }
1334
1335 /*
1336 * (non-Javadoc)
1337 *
1338 * @see dwtx.jface.wizard.IWizardContainer2#updateSize()
1339 */
1340 public void updateSize() {
1341 updateSize(currentPage);
1342 }
1343
1344 /**
1345 * Computes the correct dialog size for the given page and resizes its shell
1346 * if nessessary.
1347 *
1348 * @param page
1349 * the wizard page
1350 */
1351 private void updateSizeForPage(IWizardPage page) {
1352 // ensure the page container is large enough
1353 Point delta = calculatePageSizeDelta(page);
1354 if (delta.x > 0 || delta.y > 0) {
1355 // increase the size of the shell
1356 Shell shell = getShell();
1357 Point shellSize = shell.getSize();
1358 setShellSize(shellSize.x + delta.x, shellSize.y + delta.y);
1359 constrainShellSize();
1360 }
1361 }
1362
1363 /**
1364 * Computes the correct dialog size for the given wizard and resizes its
1365 * shell if nessessary.
1366 *
1367 * @param sizingWizard
1368 * the wizard
1369 */
1370 private void updateSizeForWizard(IWizard sizingWizard) {
1371 Point delta = new Point(0, 0);
1372 IWizardPage[] pages = sizingWizard.getPages();
1373 for (int i = 0; i < pages.length; i++) {
1374 // ensure the page container is large enough
1375 Point pageDelta = calculatePageSizeDelta(pages[i]);
1376 delta.x = Math.max(delta.x, pageDelta.x);
1377 delta.y = Math.max(delta.y, pageDelta.y);
1378 }
1379 if (delta.x > 0 || delta.y > 0) {
1380 // increase the size of the shell
1381 Shell shell = getShell();
1382 Point shellSize = shell.getSize();
1383 setShellSize(shellSize.x + delta.x, shellSize.y + delta.y);
1384 }
1385 }
1386
1387 /*
1388 * (non-Javadoc) Method declared on IWizardContainer.
1389 */
1390 public void updateTitleBar() {
1391 String s = null;
1392 if (currentPage !is null) {
1393 s = currentPage.getTitle();
1394 }
1395 if (s is null) {
1396 s = ""; //$NON-NLS-1$
1397 }
1398 setTitle(s);
1399 if (currentPage !is null) {
1400 setTitleImage(currentPage.getImage());
1401 updateDescriptionMessage();
1402 }
1403 updateMessage();
1404 }
1405
1406 /*
1407 * (non-Javadoc) Method declared on IWizardContainer.
1408 */
1409 public void updateWindowTitle() {
1410 if (getShell() is null) {
1411 // Not created yet
1412 return;
1413 }
1414 String title = wizard.getWindowTitle();
1415 if (title is null) {
1416 title = ""; //$NON-NLS-1$
1417 }
1418 getShell().setText(title);
1419 }
1420
1421 /*
1422 * (non-Javadoc)
1423 *
1424 * @see dwtx.jface.dialogs.IPageChangeProvider#getSelectedPage()
1425 */
1426 public Object getSelectedPage() {
1427 return getCurrentPage();
1428 }
1429
1430 /*
1431 * (non-Javadoc)
1432 *
1433 * @see dwtx.jface.dialog.IPageChangeProvider#addPageChangedListener()
1434 */
1435 public void addPageChangedListener(IPageChangedListener listener) {
1436 pageChangedListeners.add(listener);
1437 }
1438
1439 /*
1440 * (non-Javadoc)
1441 *
1442 * @see dwtx.jface.dialog.IPageChangeProvider#removePageChangedListener()
1443 */
1444 public void removePageChangedListener(IPageChangedListener listener) {
1445 pageChangedListeners.remove(listener);
1446 }
1447
1448 /**
1449 * Notifies any selection changed listeners that the selected page has
1450 * changed. Only listeners registered at the time this method is called are
1451 * notified.
1452 *
1453 * @param event
1454 * a selection changed event
1455 *
1456 * @see IPageChangedListener#pageChanged
1457 *
1458 * @since 3.1
1459 */
1460 protected void firePageChanged(final PageChangedEvent event) {
1461 Object[] listeners = pageChangedListeners.getListeners();
1462 for (int i = 0; i < listeners.length; ++i) {
1463 final IPageChangedListener l = (IPageChangedListener) listeners[i];
1464 SafeRunnable.run(new SafeRunnable() {
1465 public void run() {
1466 l.pageChanged(event);
1467 }
1468 });
1469 }
1470 }
1471
1472 /**
1473 * Adds a listener for page changes to the list of page changing listeners
1474 * registered for this dialog. Has no effect if an identical listener is
1475 * already registered.
1476 *
1477 * @param listener
1478 * a page changing listener
1479 * @since 3.3
1480 */
1481 public void addPageChangingListener(IPageChangingListener listener) {
1482 pageChangingListeners.add(listener);
1483 }
1484
1485 /**
1486 * Removes the provided page changing listener from the list of page
1487 * changing listeners registered for the dialog.
1488 *
1489 * @param listener
1490 * a page changing listener
1491 * @since 3.3
1492 */
1493 public void removePageChangingListener(IPageChangingListener listener) {
1494 pageChangingListeners.remove(listener);
1495 }
1496
1497 /**
1498 * Notifies any page changing listeners that the currently selected dialog
1499 * page is changing. Only listeners registered at the time this method is
1500 * called are notified.
1501 *
1502 * @param event
1503 * a selection changing event
1504 *
1505 * @see IPageChangingListener#handlePageChanging(PageChangingEvent)
1506 * @since 3.3
1507 */
1508 protected void firePageChanging(final PageChangingEvent event) {
1509 Object[] listeners = pageChangingListeners.getListeners();
1510 for (int i = 0; i < listeners.length; ++i) {
1511 final IPageChangingListener l = (IPageChangingListener) listeners[i];
1512 SafeRunnable.run(new SafeRunnable() {
1513 public void run() {
1514 l.handlePageChanging(event);
1515 }
1516 });
1517 }
1518 }
1519 }
1520 ++/