Source code: org/eclipse/jface/preference/PreferenceDialog.java
1 /*******************************************************************************
2 * Copyright (c) 2000, 2004 IBM Corporation and others.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Common Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/cpl-v10.html
7 *
8 * Contributors:
9 * IBM Corporation - initial API and implementation
10 *******************************************************************************/
11 package org.eclipse.jface.preference;
12 import java.io.IOException;
13 import java.util.Iterator;
14 import java.util.List;
15
16 import org.eclipse.core.runtime.ISafeRunnable;
17 import org.eclipse.core.runtime.IStatus;
18 import org.eclipse.core.runtime.Platform;
19 import org.eclipse.core.runtime.Status;
20 import org.eclipse.jface.dialogs.Dialog;
21 import org.eclipse.jface.dialogs.DialogMessageArea;
22 import org.eclipse.jface.dialogs.IDialogConstants;
23 import org.eclipse.jface.dialogs.IMessageProvider;
24 import org.eclipse.jface.dialogs.MessageDialog;
25 import org.eclipse.jface.resource.ImageDescriptor;
26 import org.eclipse.jface.resource.ImageRegistry;
27 import org.eclipse.jface.resource.JFaceColors;
28 import org.eclipse.jface.resource.JFaceResources;
29 import org.eclipse.jface.util.Assert;
30 import org.eclipse.jface.util.IPropertyChangeListener;
31 import org.eclipse.jface.util.PropertyChangeEvent;
32 import org.eclipse.jface.util.SafeRunnable;
33 import org.eclipse.jface.viewers.ISelection;
34 import org.eclipse.jface.viewers.ISelectionChangedListener;
35 import org.eclipse.jface.viewers.IStructuredSelection;
36 import org.eclipse.jface.viewers.SelectionChangedEvent;
37 import org.eclipse.jface.viewers.StructuredSelection;
38 import org.eclipse.jface.viewers.TreeViewer;
39 import org.eclipse.swt.SWT;
40 import org.eclipse.swt.custom.BusyIndicator;
41 import org.eclipse.swt.events.DisposeEvent;
42 import org.eclipse.swt.events.DisposeListener;
43 import org.eclipse.swt.events.HelpEvent;
44 import org.eclipse.swt.events.HelpListener;
45 import org.eclipse.swt.events.PaintEvent;
46 import org.eclipse.swt.events.PaintListener;
47 import org.eclipse.swt.events.SelectionAdapter;
48 import org.eclipse.swt.events.SelectionEvent;
49 import org.eclipse.swt.events.ShellAdapter;
50 import org.eclipse.swt.events.ShellEvent;
51 import org.eclipse.swt.graphics.Color;
52 import org.eclipse.swt.graphics.Font;
53 import org.eclipse.swt.graphics.GC;
54 import org.eclipse.swt.graphics.Point;
55 import org.eclipse.swt.graphics.Rectangle;
56 import org.eclipse.swt.layout.FormAttachment;
57 import org.eclipse.swt.layout.FormData;
58 import org.eclipse.swt.layout.FormLayout;
59 import org.eclipse.swt.layout.GridData;
60 import org.eclipse.swt.layout.GridLayout;
61 import org.eclipse.swt.widgets.Button;
62 import org.eclipse.swt.widgets.Composite;
63 import org.eclipse.swt.widgets.Control;
64 import org.eclipse.swt.widgets.Display;
65 import org.eclipse.swt.widgets.Event;
66 import org.eclipse.swt.widgets.Label;
67 import org.eclipse.swt.widgets.Layout;
68 import org.eclipse.swt.widgets.Listener;
69 import org.eclipse.swt.widgets.Sash;
70 import org.eclipse.swt.widgets.Shell;
71 import org.eclipse.swt.widgets.Tree;
72 /**
73 * A preference dialog is a hierarchical presentation of preference pages. Each
74 * page is represented by a node in the tree shown on the left hand side of the
75 * dialog; when a node is selected, the corresponding page is shown on the right
76 * hand side.
77 */
78 public class PreferenceDialog extends Dialog
79 implements
80 IPreferencePageContainer {
81 /**
82 * Layout for the page container.
83 *
84 */
85 private class PageLayout extends Layout {
86 public Point computeSize(Composite composite, int wHint, int hHint,
87 boolean force) {
88 if (wHint != SWT.DEFAULT && hHint != SWT.DEFAULT)
89 return new Point(wHint, hHint);
90 int x = minimumPageSize.x;
91 int y = minimumPageSize.y;
92 Control[] children = composite.getChildren();
93 for (int i = 0; i < children.length; i++) {
94 Point size = children[i].computeSize(SWT.DEFAULT, SWT.DEFAULT,
95 force);
96 x = Math.max(x, size.x);
97 y = Math.max(y, size.y);
98 }
99 if (wHint != SWT.DEFAULT)
100 x = wHint;
101 if (hHint != SWT.DEFAULT)
102 y = hHint;
103 return new Point(x, y);
104 }
105 public void layout(Composite composite, boolean force) {
106 Rectangle rect = composite.getClientArea();
107 Control[] children = composite.getChildren();
108 for (int i = 0; i < children.length; i++) {
109 children[i].setSize(rect.width, rect.height);
110 }
111 }
112 }
113 //The id of the last page that was selected
114 private static String lastPreferenceId = null;
115 //The last known tree width
116 private static int lastTreeWidth = 150;
117 /**
118 * Indentifier for the error image
119 */
120 public static final String PREF_DLG_IMG_TITLE_ERROR = DLG_IMG_MESSAGE_ERROR; //$NON-NLS-1$
121 /**
122 * Title area fields
123 */
124 public static final String PREF_DLG_TITLE_IMG = "preference_dialog_title_image"; //$NON-NLS-1$
125 static {
126 ImageRegistry reg = JFaceResources.getImageRegistry();
127 reg.put(PREF_DLG_TITLE_IMG, ImageDescriptor.createFromFile(
128 PreferenceDialog.class, "images/pref_dialog_title.gif")); //$NON-NLS-1$
129 }
130 /**
131 * The current preference page, or <code>null</code> if there is none.
132 */
133 private IPreferencePage currentPage;
134 private DialogMessageArea messageArea;
135 /**
136 * Indicates whether help is available; <code>false</code> by default.'
137 *
138 * @see #setHelpAvailable
139 */
140 private boolean isHelpAvailable = false;
141 private Point lastShellSize;
142 private IPreferenceNode lastSuccessfulNode;
143 /**
144 * The minimum page size; 400 by 400 by default.
145 *
146 * @see #setMinimumPageSize(Point)
147 */
148 private Point minimumPageSize = new Point(400, 400);
149 /**
150 * The OK button.
151 */
152 private Button okButton;
153 /**
154 * The Composite in which a page is shown.
155 */
156 private Composite pageContainer;
157 /**
158 * The preference manager.
159 */
160 private PreferenceManager preferenceManager;
161 /**
162 * Flag for the presence of the error message.
163 */
164 private boolean showingError = false;
165 /**
166 * Preference store, initially <code>null</code> meaning none.
167 *
168 * @see #setPreferenceStore
169 */
170 private IPreferenceStore preferenceStore;
171 private Composite titleArea;
172 private Label titleImage;
173 /**
174 * The tree viewer.
175 */
176 private TreeViewer treeViewer;
177 /**
178 * Creates a new preference dialog under the control of the given preference
179 * manager.
180 *
181 * @param parentShell
182 * the parent shell
183 * @param manager
184 * the preference manager
185 */
186 public PreferenceDialog(Shell parentShell, PreferenceManager manager) {
187 super(parentShell);
188 setShellStyle(getShellStyle() | SWT.RESIZE | SWT.MAX);
189 preferenceManager = manager;
190 }
191 /*
192 * (non-Javadoc)
193 *
194 * @see org.eclipse.jface.dialogs.Dialog#buttonPressed(int)
195 */
196 protected void buttonPressed(int buttonId) {
197 switch (buttonId) {
198 case IDialogConstants.OK_ID : {
199 okPressed();
200 return;
201 }
202 case IDialogConstants.CANCEL_ID : {
203 cancelPressed();
204 return;
205 }
206 case IDialogConstants.HELP_ID : {
207 helpPressed();
208 return;
209 }
210 }
211 }
212 /*
213 * (non-Javadoc)
214 *
215 * @see org.eclipse.jface.dialogs.Dialog#cancelPressed()
216 */
217 protected void cancelPressed() {
218 // Inform all pages that we are cancelling
219 Iterator nodes = preferenceManager.getElements(
220 PreferenceManager.PRE_ORDER).iterator();
221 while (nodes.hasNext()) {
222 final IPreferenceNode node = (IPreferenceNode) nodes.next();
223 if (node.getPage() != null) {
224 Platform.run(new SafeRunnable() {
225 public void run() {
226 if (!node.getPage().performCancel())
227 return;
228 }
229 });
230 }
231 }
232 setReturnCode(CANCEL);
233 close();
234 }
235 /**
236 * Clear the last selected node. This is so that we not chache the last
237 * selection in case of an error.
238 */
239 void clearSelectedNode() {
240 setSelectedNodePreference(null);
241 }
242 /*
243 * (non-Javadoc)
244 *
245 * @see org.eclipse.jface.window.Window#close()
246 */
247 public boolean close() {
248 List nodes = preferenceManager.getElements(PreferenceManager.PRE_ORDER);
249 for (int i = 0; i < nodes.size(); i++) {
250 IPreferenceNode node = (IPreferenceNode) nodes.get(i);
251 node.disposeResources();
252 }
253 return super.close();
254 }
255 /*
256 * (non-Javadoc)
257 *
258 * @see org.eclipse.jface.window.Window#configureShell(org.eclipse.swt.widgets.Shell)
259 */
260 protected void configureShell(Shell newShell) {
261 super.configureShell(newShell);
262 newShell.setText(JFaceResources.getString("PreferenceDialog.title")); //$NON-NLS-1$
263 newShell.addShellListener(new ShellAdapter() {
264 public void shellActivated(ShellEvent e) {
265 if (lastShellSize == null)
266 lastShellSize = getShell().getSize();
267 }
268 });
269 }
270 /*
271 * (non-Javadoc)
272 *
273 * @see org.eclipse.jface.window.Window#constrainShellSize()
274 */
275 protected void constrainShellSize() {
276 super.constrainShellSize();
277 // record opening shell size
278 if (lastShellSize == null)
279 lastShellSize = getShell().getSize();
280 }
281 /*
282 * (non-Javadoc)
283 *
284 * @see org.eclipse.jface.dialogs.Dialog#createButtonsForButtonBar(org.eclipse.swt.widgets.Composite)
285 */
286 protected void createButtonsForButtonBar(Composite parent) {
287 // create OK and Cancel buttons by default
288 okButton = createButton(parent, IDialogConstants.OK_ID,
289 IDialogConstants.OK_LABEL, true);
290 getShell().setDefaultButton(okButton);
291 createButton(parent, IDialogConstants.CANCEL_ID,
292 IDialogConstants.CANCEL_LABEL, false);
293 if (isHelpAvailable) {
294 createButton(parent, IDialogConstants.HELP_ID,
295 IDialogConstants.HELP_LABEL, false);
296 }
297 }
298 /*
299 * (non-Javadoc)
300 *
301 * @see org.eclipse.jface.window.Window#createContents(org.eclipse.swt.widgets.Composite)
302 */
303 protected Control createContents(final Composite parent) {
304 final Control[] control = new Control[1];
305 BusyIndicator.showWhile(getShell().getDisplay(), new Runnable() {
306 public void run() {
307 control[0] = PreferenceDialog.super.createContents(parent);
308 // Add the first page
309 selectSavedItem();
310 }
311 });
312 return control[0];
313 }
314 /*
315 * (non-Javadoc)
316 *
317 * @see org.eclipse.jface.dialogs.Dialog#createDialogArea(org.eclipse.swt.widgets.Composite)
318 */
319 protected Control createDialogArea(Composite parent) {
320 final Composite composite = (Composite) super.createDialogArea(parent);
321 ((GridLayout) composite.getLayout()).numColumns = 3;
322 final Control treeControl = createTreeAreaContents(composite);
323 final Sash sash = new Sash(composite, SWT.VERTICAL);
324 sash.setLayoutData(new GridData(GridData.FILL_VERTICAL));
325 // the following listener resizes the tree control based on sash deltas.
326 // If necessary, it will also grow/shrink the dialog.
327 sash.addListener(SWT.Selection, new Listener() {
328 /*
329 * (non-Javadoc)
330 *
331 * @see org.eclipse.swt.widgets.Listener#handleEvent(org.eclipse.swt.widgets.Event)
332 */
333 public void handleEvent(Event event) {
334 if (event.detail == SWT.DRAG)
335 return;
336 int shift = event.x - sash.getBounds().x;
337 GridData data = (GridData) treeControl.getLayoutData();
338 int newWidthHint = data.widthHint + shift;
339 if (newWidthHint < 20)
340 return;
341 Point computedSize = getShell().computeSize(SWT.DEFAULT,
342 SWT.DEFAULT);
343 Point currentSize = getShell().getSize();
344 // if the dialog wasn't of a custom size we know we can shrink
345 // it if necessary based on sash movement.
346 boolean customSize = !computedSize.equals(currentSize);
347 data.widthHint = newWidthHint;
348 setLastTreeWidth(newWidthHint);
349 composite.layout(true);
350 // recompute based on new widget size
351 computedSize = getShell().computeSize(SWT.DEFAULT, SWT.DEFAULT);
352 // if the dialog was of a custom size then increase it only if
353 // necessary.
354 if (customSize)
355 computedSize.x = Math.max(computedSize.x, currentSize.x);
356 computedSize.y = Math.max(computedSize.y, currentSize.y);
357 if (computedSize.equals(currentSize))
358 return;
359 setShellSize(computedSize.x, computedSize.y);
360 lastShellSize = getShell().getSize();
361 }
362 });
363 Composite pageAreaComposite = new Composite(composite, SWT.NONE);
364 pageAreaComposite.setLayoutData(new GridData(GridData.FILL_BOTH));
365 GridLayout layout = new GridLayout(1, true);
366 layout.marginHeight = 0;
367 layout.marginWidth = 10;
368 pageAreaComposite.setLayout(layout);
369 // Build the title area and separator line
370 Composite titleComposite = new Composite(pageAreaComposite, SWT.NONE);
371 layout = new GridLayout();
372 layout.marginHeight = 0;
373 layout.marginWidth = 0;
374 layout.verticalSpacing = 0;
375 layout.horizontalSpacing = 0;
376 titleComposite.setLayout(layout);
377 titleComposite.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
378 createTitleArea(titleComposite);
379 // Build the Page container
380 pageContainer = createPageContainer(pageAreaComposite);
381 pageContainer.setLayoutData(new GridData(GridData.FILL_BOTH));
382 // Build the separator line
383 Label separator = new Label(pageAreaComposite, SWT.HORIZONTAL
384 | SWT.SEPARATOR);
385 GridData gd = new GridData(GridData.FILL_HORIZONTAL);
386 separator.setLayoutData(gd);
387 return composite;
388 }
389 /**
390 * Creates the inner page container.
391 *
392 * @param parent
393 * @return Composite
394 */
395 private Composite createPageContainer(Composite parent) {
396 Composite result = new Composite(parent, SWT.NULL);
397 result.setLayout(new PageLayout());
398 return result;
399 }
400 /**
401 * Creates the wizard's title area.
402 *
403 * @param parent
404 * the SWT parent for the title area composite.
405 * @return the created title area composite.
406 */
407 private Composite createTitleArea(Composite parent) {
408 // Create the title area which will contain
409 // a title, message, and image.
410 int margins = 2;
411 titleArea = new Composite(parent, SWT.NONE);
412 FormLayout layout = new FormLayout();
413 layout.marginHeight = margins;
414 layout.marginWidth = margins;
415 titleArea.setLayout(layout);
416 // Get the background color for the title area
417 Display display = parent.getDisplay();
418 Color background = JFaceColors.getBannerBackground(display);
419 GridData layoutData = new GridData(GridData.FILL_HORIZONTAL);
420 layoutData.heightHint = JFaceResources.getImage(PREF_DLG_TITLE_IMG)
421 .getBounds().height
422 + (margins * 3);
423 titleArea.setLayoutData(layoutData);
424 titleArea.setBackground(background);
425
426 titleArea.addPaintListener(new PaintListener() {
427 public void paintControl(PaintEvent e) {
428 e.gc.setForeground(
429 titleArea.getDisplay().getSystemColor(SWT.COLOR_WIDGET_NORMAL_SHADOW));
430 Rectangle bounds = titleArea.getClientArea();
431 bounds.height = bounds.height - 2;
432 bounds.width = bounds.width - 1;
433 e.gc.drawRectangle(bounds);
434 }
435 });
436
437 // Message label
438 messageArea = new DialogMessageArea();
439 messageArea.createContents(titleArea);
440 final IPropertyChangeListener fontListener = new IPropertyChangeListener() {
441 public void propertyChange(PropertyChangeEvent event) {
442 if (JFaceResources.BANNER_FONT.equals(event.getProperty()))
443 updateMessage();
444 if (JFaceResources.DIALOG_FONT.equals(event.getProperty())) {
445 updateMessage();
446 Font dialogFont = JFaceResources.getDialogFont();
447 updateTreeFont(dialogFont);
448 Control[] children = ((Composite) buttonBar).getChildren();
449 for (int i = 0; i < children.length; i++)
450 children[i].setFont(dialogFont);
451 }
452 }
453 };
454 titleArea.addDisposeListener(new DisposeListener() {
455 public void widgetDisposed(DisposeEvent event) {
456 JFaceResources.getFontRegistry().removeListener(fontListener);
457 }
458 });
459 JFaceResources.getFontRegistry().addListener(fontListener);
460 // Title image
461 titleImage = new Label(titleArea, SWT.LEFT);
462 titleImage.setBackground(background);
463 titleImage.setImage(JFaceResources.getImage(PREF_DLG_TITLE_IMG));
464 FormData imageData = new FormData();
465 imageData.right = new FormAttachment(100);
466 imageData.top = new FormAttachment(0);
467 imageData.bottom = new FormAttachment(100);
468 titleImage.setLayoutData(imageData);
469 messageArea.setTitleLayoutData(createMessageAreaData());
470 messageArea.setMessageLayoutData(createMessageAreaData());
471 return titleArea;
472 }
473 /**
474 * Create the layout data for the message area.
475 *
476 * @return FormData for the message area.
477 */
478 private FormData createMessageAreaData() {
479 FormData messageData = new FormData();
480 messageData.top = new FormAttachment(0);
481 messageData.bottom = new FormAttachment(titleImage, 0, SWT.BOTTOM);
482 messageData.right = new FormAttachment(titleImage, 0);
483 messageData.left = new FormAttachment(0);
484 return messageData;
485 }
486 /**
487 * @param parent
488 * the SWT parent for the tree area controls.
489 * @return the new <code>Control</code>.
490 * @since 3.0
491 */
492 protected Control createTreeAreaContents(Composite parent) {
493 // Build the tree an put it into the composite.
494 treeViewer = createTreeViewer(parent);
495 treeViewer.setInput(getPreferenceManager());
496 updateTreeFont(JFaceResources.getDialogFont());
497 layoutTreeAreaControl(treeViewer.getControl());
498 return treeViewer.getControl();
499 }
500 /**
501 * Create a new <code>TreeViewer</code>.
502 *
503 * @param parent
504 * the parent <code>Composite</code>.
505 * @return the <code>TreeViewer</code>.
506 * @since 3.0
507 */
508 protected TreeViewer createTreeViewer(Composite parent) {
509 final TreeViewer viewer = new TreeViewer(parent, SWT.BORDER);
510 viewer.addPostSelectionChangedListener(new ISelectionChangedListener() {
511 private void handleError() {
512 try {
513 // remove the listener temporarily so that the events caused
514 // by the error handling dont further cause error handling
515 // to occur.
516 viewer.removePostSelectionChangedListener(this);
517 showPageFlippingAbortDialog();
518 selectCurrentPageAgain();
519 clearSelectedNode();
520 } finally {
521 viewer.addPostSelectionChangedListener(this);
522 }
523 }
524 public void selectionChanged(SelectionChangedEvent event) {
525 Object selection = getSingleSelection(event.getSelection());
526 if (selection instanceof IPreferenceNode) {
527 if (!isCurrentPageValid()) {
528 handleError();
529 } else if (!showPage((IPreferenceNode) selection)) {
530 // Page flipping wasn't successful
531 handleError();
532 } else {
533 // Everything went well
534 lastSuccessfulNode = (IPreferenceNode) selection;
535 }
536 viewer.getControl().setFocus();
537 }
538 }
539 });
540 ((Tree) viewer.getControl())
541 .addSelectionListener(new SelectionAdapter() {
542 public void widgetDefaultSelected(final SelectionEvent event) {
543 ISelection selection = viewer.getSelection();
544 if (selection.isEmpty())
545 return;
546 IPreferenceNode singleSelection = getSingleSelection(selection);
547 boolean expanded = viewer
548 .getExpandedState(singleSelection);
549 viewer.setExpandedState(singleSelection, !expanded);
550 }
551 });
552 //Register help listener on the tree to use context sensitive help
553 viewer.getControl().addHelpListener(new HelpListener() {
554 public void helpRequested(HelpEvent event) {
555 // call perform help on the current page
556 if (currentPage != null) {
557 currentPage.performHelp();
558 }
559 }
560 });
561 viewer.setLabelProvider(new PreferenceLabelProvider());
562 viewer.setContentProvider(new PreferenceContentProvider());
563 return viewer;
564 }
565 /**
566 * Find the <code>IPreferenceNode</code> that has data the same id as the
567 * supplied value.
568 *
569 * @param nodeId
570 * the id to search for.
571 * @return <code>IPreferenceNode</code> or <code>null</code> if not
572 * found.
573 */
574 protected IPreferenceNode findNodeMatching(String nodeId) {
575 List nodes = preferenceManager
576 .getElements(PreferenceManager.POST_ORDER);
577 for (Iterator i = nodes.iterator(); i.hasNext();) {
578 IPreferenceNode node = (IPreferenceNode) i.next();
579 if (node.getId().equals(nodeId))
580 return node;
581 }
582 return null;
583 }
584 /**
585 * Get the last known tree width.
586 *
587 * @return the width.
588 */
589 private int getLastTreeWidth() {
590 return lastTreeWidth;
591 }
592 /**
593 * Returns the preference mananger used by this preference dialog.
594 *
595 * @return the preference mananger
596 */
597 public PreferenceManager getPreferenceManager() {
598 return preferenceManager;
599 }
600 /*
601 * (non-Javadoc)
602 *
603 * @see org.eclipse.jface.preference.IPreferencePageContainer#getPreferenceStore()
604 */
605 public IPreferenceStore getPreferenceStore() {
606 return preferenceStore;
607 }
608 /**
609 * Get the name of the selected item preference
610 *
611 * @return String
612 */
613 protected String getSelectedNodePreference() {
614 return lastPreferenceId;
615 }
616 /**
617 * @param selection
618 * the <code>ISelection</code> to examine.
619 * @return the first element, or null if empty.
620 */
621 protected IPreferenceNode getSingleSelection(ISelection selection) {
622 if (!selection.isEmpty())
623 return (IPreferenceNode) ((IStructuredSelection) selection)
624 .getFirstElement();
625 return null;
626 }
627 /**
628 * @return the <code>TreeViewer</code> for this dialog.
629 * @since 3.0
630 */
631 protected TreeViewer getTreeViewer() {
632 return treeViewer;
633 }
634 /**
635 * Save the values specified in the pages.
636 * <p>
637 * The default implementation of this framework method saves all pages of
638 * type <code>PreferencePage</code> (if their store needs saving and is a
639 * <code>PreferenceStore</code>).
640 * </p>
641 * <p>
642 * Subclasses may override.
643 * </p>
644 */
645 protected void handleSave() {
646 Iterator nodes = preferenceManager.getElements(
647 PreferenceManager.PRE_ORDER).iterator();
648 while (nodes.hasNext()) {
649 IPreferenceNode node = (IPreferenceNode) nodes.next();
650 IPreferencePage page = node.getPage();
651 if (page instanceof PreferencePage) {
652 // Save now in case tbe workbench does not shutdown cleanly
653 IPreferenceStore store = ((PreferencePage) page)
654 .getPreferenceStore();
655 if (store != null && store.needsSaving()
656 && store instanceof IPersistentPreferenceStore) {
657 try {
658 ((IPersistentPreferenceStore) store).save();
659 } catch (IOException e) {
660 MessageDialog
661 .openError(
662 getShell(),
663 JFaceResources
664 .getString("PreferenceDialog.saveErrorTitle"), //$NON-NLS-1$
665 JFaceResources
666 .format(
667 "PreferenceDialog.saveErrorMessage", new Object[]{page.getTitle(), e.getMessage()})); //$NON-NLS-1$
668 }
669 }
670 }
671 }
672 }
673 /**
674 * Notifies that the window's close button was pressed, the close menu was
675 * selected, or the ESCAPE key pressed.
676 * <p>
677 * The default implementation of this framework method sets the window's
678 * return code to <code>CANCEL</code> and closes the window using
679 * <code>close</code>. Subclasses may extend or reimplement.
680 * </p>
681 */
682 protected void handleShellCloseEvent() {
683 // handle the same as pressing cancel
684 cancelPressed();
685 }
686 /**
687 * Notifies of the pressing of the Help button.
688 * <p>
689 * The default implementation of this framework method calls
690 * <code>performHelp</code> on the currently active page.
691 * </p>
692 */
693 protected void helpPressed() {
694 if (currentPage != null) {
695 currentPage.performHelp();
696 }
697 }
698 /**
699 * Returns whether the current page is valid.
700 *
701 * @return <code>false</code> if the current page is not valid, or or
702 * <code>true</code> if the current page is valid or there is no
703 * current page
704 */
705 protected boolean isCurrentPageValid() {
706 if (currentPage == null)
707 return true;
708 return currentPage.isValid();
709 }
710 /**
711 * @param control
712 * the <code>Control</code> to lay out.
713 * @since 3.0
714 */
715 protected void layoutTreeAreaControl(Control control) {
716 GridData gd = new GridData(GridData.FILL_VERTICAL);
717 gd.widthHint = getLastTreeWidth();
718 gd.verticalSpan = 1;
719 control.setLayoutData(gd);
720 }
721 /**
722 * The preference dialog implementation of this <code>Dialog</code>
723 * framework method sends <code>performOk</code> to all pages of the
724 * preference dialog, then calls <code>handleSave</code> on this dialog to
725 * save any state, and then calls <code>close</code> to close this dialog.
726 */
727 protected void okPressed() {
728 Platform.run(new SafeRunnable() {
729 private boolean errorOccurred;
730 /*
731 * (non-Javadoc)
732 *
733 * @see org.eclipse.core.runtime.ISafeRunnable#run()
734 */
735 public void run() {
736 getButton(IDialogConstants.OK_ID).setEnabled(false);
737 errorOccurred = false;
738 try {
739 // Notify all the pages and give them a chance to abort
740 Iterator nodes = preferenceManager.getElements(
741 PreferenceManager.PRE_ORDER).iterator();
742 while (nodes.hasNext()) {
743 IPreferenceNode node = (IPreferenceNode) nodes.next();
744 IPreferencePage page = node.getPage();
745 if (page != null) {
746 if (!page.performOk())
747 return;
748 }
749 }
750 } catch (Exception e) {
751 handleException(e);
752 } finally {
753 // Give subclasses the choice to save the state of the
754 // preference pages.
755 if (!errorOccurred)
756 handleSave();
757 // Need to restore state
758 close();
759 }
760 }
761 /*
762 * (non-Javadoc)
763 *
764 * @see org.eclipse.core.runtime.ISafeRunnable#handleException(java.lang.Throwable)
765 */
766 public void handleException(Throwable e) {
767 errorOccurred = true;
768 if (Platform.isRunning()) {
769 String bundle = Platform.PI_RUNTIME;
770 Platform.getLog(Platform.getBundle(bundle)).log(
771 new Status(IStatus.ERROR, bundle, 0, e.toString(),
772 e));
773 } else
774 e.printStackTrace();
775 clearSelectedNode();
776 String message = JFaceResources
777 .getString("SafeRunnable.errorMessage"); //$NON-NLS-1$
778 MessageDialog.openError(getShell(), JFaceResources
779 .getString("Error"), message); //$NON-NLS-1$
780
781 }
782 });
783 }
784 /**
785 * Selects the page determined by <code>lastSuccessfulNode</code> in the
786 * page hierarchy.
787 */
788 void selectCurrentPageAgain() {
789 if (lastSuccessfulNode == null)
790 return;
791 getTreeViewer().setSelection(
792 new StructuredSelection(lastSuccessfulNode));
793 currentPage.setVisible(true);
794 }
795 /**
796 * Selects the saved item in the tree of preference pages. If it cannot do
797 * this it saves the first one.
798 */
799 protected void selectSavedItem() {
800 IPreferenceNode node = findNodeMatching(getSelectedNodePreference());
801 if (node == null) {
802 IPreferenceNode[] nodes = preferenceManager.getRoot().getSubNodes();
803 if (nodes.length > 0)
804 node = nodes[0];
805 }
806 if (node != null) {
807 getTreeViewer().setSelection(new StructuredSelection(node), true);
808 // Keep focus in tree. See bugs 2692, 2621, and 6775.
809 getTreeViewer().getControl().setFocus();
810 }
811 }
812 /**
813 * Display the given error message. The currently displayed message is saved
814 * and will be redisplayed when the error message is set to
815 * <code>null</code>.
816 *
817 * @param newErrorMessage
818 * the errorMessage to display or <code>null</code>
819 */
820 public void setErrorMessage(String newErrorMessage) {
821 if (newErrorMessage == null)
822 messageArea.clearErrorMessage();
823 else
824 messageArea.updateText(newErrorMessage, IMessageProvider.ERROR);
825 }
826 /**
827 * Save the last known tree width.
828 *
829 * @param width
830 * the width.
831 */
832 private void setLastTreeWidth(int width) {
833 lastTreeWidth = width;
834 }
835 /**
836 * Sets whether a Help button is available for this dialog.
837 * <p>
838 * Clients must call this framework method before the dialog's control has
839 * been created.
840 * <p>
841 *
842 * @param b
843 * <code>true</code> to include a Help button, and
844 * <code>false</code> to not include one (the default)
845 */
846 public void setHelpAvailable(boolean b) {
847 isHelpAvailable = b;
848 }
849 /**
850 * Set the message text. If the message line currently displays an error,
851 * the message is stored and will be shown after a call to clearErrorMessage
852 * <p>
853 * Shortcut for <code>setMessage(newMessage, NONE)</code>
854 * </p>
855 *
856 * @param newMessage
857 * the message, or <code>null</code> to clear the message
858 */
859 public void setMessage(String newMessage) {
860 setMessage(newMessage, IMessageProvider.NONE);
861 }
862 /**
863 * Sets the message for this dialog with an indication of what type of
864 * message it is.
865 * <p>
866 * The valid message types are one of <code>NONE</code>,
867 * <code>INFORMATION</code>,<code>WARNING</code>, or
868 * <code>ERROR</code>.
869 * </p>
870 * <p>
871 * Note that for backward compatibility, a message of type
872 * <code>ERROR</code> is different than an error message (set using
873 * <code>setErrorMessage</code>). An error message overrides the current
874 * message until the error message is cleared. This method replaces the
875 * current message and does not affect the error message.
876 * </p>
877 *
878 * @param newMessage
879 * the message, or <code>null</code> to clear the message
880 * @param newType
881 * the message type
882 * @since 2.0
883 */
884 public void setMessage(String newMessage, int newType) {
885 messageArea.updateText(newMessage, newType);
886 }
887 /**
888 * Sets the minimum page size.
889 *
890 * @param minWidth
891 * the minimum page width
892 * @param minHeight
893 * the minimum page height
894 * @see #setMinimumPageSize(Point)
895 */
896 public void setMinimumPageSize(int minWidth, int minHeight) {
897 minimumPageSize.x = minWidth;
898 minimumPageSize.y = minHeight;
899 }
900 /**
901 * Sets the minimum page size.
902 *
903 * @param size
904 * the page size encoded as <code>new Point(width,height)</code>
905 * @see #setMinimumPageSize(int,int)
906 */
907 public void setMinimumPageSize(Point size) {
908 minimumPageSize.x = size.x;
909 minimumPageSize.y = size.y;
910 }
911 /**
912 * Sets the preference store for this preference dialog.
913 *
914 * @param store
915 * the preference store
916 * @see #getPreferenceStore
917 */
918 public void setPreferenceStore(IPreferenceStore store) {
919 Assert.isNotNull(store);
920 preferenceStore = store;
921 }
922 /**
923 * Save the currently selected node.
924 */
925 private void setSelectedNode() {
926 String storeValue = null;
927 IStructuredSelection selection = (IStructuredSelection) getTreeViewer()
928 .getSelection();
929 if (selection.size() == 1) {
930 IPreferenceNode node = (IPreferenceNode) selection
931 .getFirstElement();
932 storeValue = node.getId();
933 }
934 setSelectedNodePreference(storeValue);
935 }
936 /**
937 * Sets the name of the selected item preference.
938 * Public equivalent to <code>setSelectedNodePreference</code>.
939 *
940 * @param pageId
941 * The identifier for the page
942 * @since 3.0
943 */
944 public void setSelectedNode(String pageId) {
945 setSelectedNodePreference(pageId);
946 }
947 /**
948 * Sets the name of the selected item preference.
949 *
950 * @param pageId
951 * The identifier for the page
952 */
953 protected void setSelectedNodePreference(String pageId) {
954 lastPreferenceId = pageId;
955 }
956 /**
957 * Changes the shell size to the given size, ensuring that it is no larger
958 * than the display bounds.
959 *
960 * @param width
961 * the shell width
962 * @param height
963 * the shell height
964 */
965 private void setShellSize(int width, int height) {
966 Rectangle preferred = getShell().getBounds();
967 preferred.width = width;
968 preferred.height = height;
969 getShell().setBounds(getConstrainedShellBounds(preferred));
970 }
971 /**
972 * Shows the preference page corresponding to the given preference node.
973 * Does nothing if that page is already current.
974 *
975 * @param node
976 * the preference node, or <code>null</code> if none
977 * @return <code>true</code> if the page flip was successful, and
978 * <code>false</code> is unsuccessful
979 */
980 protected boolean showPage(IPreferenceNode node) {
981 if (node == null)
982 return false;
983 // Create the page if nessessary
984 if (node.getPage() == null)
985 node.createPage();
986 if (node.getPage() == null)
987 return false;
988 IPreferencePage newPage = node.getPage();
989 if (newPage == currentPage)
990 return true;
991 if (currentPage != null) {
992 if (!currentPage.okToLeave())
993 return false;
994 }
995 IPreferencePage oldPage = currentPage;
996 currentPage = newPage;
997 // Set the new page's container
998 currentPage.setContainer(this);
999 // Ensure that the page control has been created
1000 // (this allows lazy page control creation)
1001 if (currentPage.getControl() == null) {
1002 final boolean[] failed = {false};
1003 Platform.run(new ISafeRunnable() {
1004 public void handleException(Throwable e) {
1005 failed[0] = true;
1006 }
1007 public void run() {
1008 currentPage.createControl(pageContainer);
1009 }
1010 });
1011 if (failed[0])
1012 return false;
1013 // the page is responsible for ensuring the created control is
1014 // accessable
1015 // via getControl.
1016 Assert.isNotNull(currentPage.getControl());
1017 }
1018 // Force calculation of the page's description label because
1019 // label can be wrapped.
1020 final Point[] size = new Point[1];
1021 final Point failed = new Point(-1, -1);
1022 Platform.run(new ISafeRunnable() {
1023 public void handleException(Throwable e) {
1024 size[0] = failed;
1025 }
1026 public void run() {
1027 size[0] = currentPage.computeSize();
1028 }
1029 });
1030 if (size[0].equals(failed))
1031 return false;
1032 Point contentSize = size[0];
1033 // Do we need resizing. Computation not needed if the
1034 // first page is inserted since computing the dialog's
1035 // size is done by calling dialog.open().
1036 // Also prevent auto resize if the user has manually resized
1037 Shell shell = getShell();
1038 Point shellSize = shell.getSize();
1039 if (oldPage != null) {
1040 Rectangle rect = pageContainer.getClientArea();
1041 Point containerSize = new Point(rect.width, rect.height);
1042 int hdiff = contentSize.x - containerSize.x;
1043 int vdiff = contentSize.y - containerSize.y;
1044 if (hdiff > 0 || vdiff > 0) {
1045 if (shellSize.equals(lastShellSize)) {
1046 hdiff = Math.max(0, hdiff);
1047 vdiff = Math.max(0, vdiff);
1048 setShellSize(shellSize.x + hdiff, shellSize.y + vdiff);
1049 lastShellSize = shell.getSize();
1050 } else {
1051 currentPage.setSize(containerSize);
1052 }
1053 } else if (hdiff < 0 || vdiff < 0) {
1054 currentPage.setSize(containerSize);
1055 }
1056 }
1057 // Ensure that all other pages are invisible
1058 // (including ones that triggered an exception during
1059 // their creation).
1060 Control[] children = pageContainer.getChildren();
1061 Control currentControl = currentPage.getControl();
1062 for (int i = 0; i < children.length; i++) {
1063 if (children[i] != currentControl)
1064 children[i].setVisible(false);
1065 }
1066 // Make the new page visible
1067 currentPage.setVisible(true);
1068 if (oldPage != null)
1069 oldPage.setVisible(false);
1070 // update the dialog controls
1071 update();
1072 return true;
1073 }
1074 /**
1075 * Shows the "Page Flipping abort" dialog.
1076 */
1077 void showPageFlippingAbortDialog() {
1078 MessageDialog.openError(getShell(), JFaceResources
1079 .getString("AbortPageFlippingDialog.title"), //$NON-NLS-1$
1080 JFaceResources.getString("AbortPageFlippingDialog.message")); //$NON-NLS-1$
1081 }
1082 /**
1083 * Updates this dialog's controls to reflect the current page.
1084 */
1085 protected void update() {
1086 // Update the title bar
1087 updateTitle();
1088 // Update the message line
1089 updateMessage();
1090 // Update the buttons
1091 updateButtons();
1092 //Saved the selected node in the preferences
1093 setSelectedNode();
1094 }
1095 /*
1096 * (non-Javadoc)
1097 *
1098 * @see org.eclipse.jface.preference.IPreferencePageContainer#updateButtons()
1099 */
1100 public void updateButtons() {
1101 okButton.setEnabled(isCurrentPageValid());
1102 }
1103 /*
1104 * (non-Javadoc)
1105 *
1106 * @see org.eclipse.jface.preference.IPreferencePageContainer#updateMessage()
1107 */
1108 public void updateMessage() {
1109 String message = currentPage.getMessage();
1110 int messageType = IMessageProvider.NONE;
1111 if (message != null && currentPage instanceof IMessageProvider)
1112 messageType = ((IMessageProvider) currentPage).getMessageType();
1113 String errorMessage = currentPage.getErrorMessage();
1114 if (errorMessage != null) {
1115 message = errorMessage;
1116 messageType = IMessageProvider.ERROR;
1117 if (!showingError) {
1118 // we were not previously showing an error
1119 showingError = true;
1120 titleImage.setImage(null);
1121 titleImage.setBackground(JFaceColors
1122 .getErrorBackground(titleImage.getDisplay()));
1123 titleImage.setSize(0, 0);
1124 titleImage.getParent().layout();
1125 }
1126 } else {
1127 if (showingError) {
1128 // we were previously showing an error
1129 showingError = false;
1130 titleImage
1131 .setImage(JFaceResources.getImage(PREF_DLG_TITLE_IMG));
1132 titleImage.computeSize(SWT.NULL, SWT.NULL);
1133 titleImage.getParent().layout();
1134 }
1135 }
1136 messageArea.updateText(getShortenedString(message), messageType);
1137 }
1138 private final String ellipsis = "..."; //$NON-NLS-1$
1139 /**
1140 * Shortened the message if too long.
1141 *
1142 * @param textValue
1143 * The messgae value.
1144 * @return The shortened string.
1145 */
1146 private String getShortenedString(String textValue) {
1147 if (textValue == null)
1148 return null;
1149 Display display = titleArea.getDisplay();
1150 GC gc = new GC(display);
1151 int maxWidth = titleArea.getBounds().width - 28;
1152 if (gc.textExtent(textValue).x < maxWidth) {
1153 gc.dispose();
1154 return textValue;
1155 }
1156 int length = textValue.length();
1157 int ellipsisWidth = gc.textExtent(ellipsis).x;
1158 int pivot = length / 2;
1159 int start = pivot;
1160 int end = pivot + 1;
1161 while (start >= 0 && end < length) {
1162 String s1 = textValue.substring(0, start);
1163 String s2 = textValue.substring(end, length);
1164 int l1 = gc.textExtent(s1).x;
1165 int l2 = gc.textExtent(s2).x;
1166 if (l1 + ellipsisWidth + l2 < maxWidth) {
1167 gc.dispose();
1168 return s1 + ellipsis + s2;
1169 }
1170 start--;
1171 end++;
1172 }
1173 gc.dispose();
1174 return textValue;
1175 }
1176 /*
1177 * (non-Javadoc)
1178 *
1179 * @see org.eclipse.jface.preference.IPreferencePageContainer#updateTitle()
1180 */
1181 public void updateTitle() {
1182 messageArea.showTitle(currentPage.getTitle(), currentPage.getImage());
1183 }
1184 /**
1185 * Update the tree to use the specified <code>Font</code>.
1186 *
1187 * @param dialogFont
1188 * the <code>Font</code> to use.
1189 * @since 3.0
1190 */
1191 protected void updateTreeFont(Font dialogFont) {
1192 getTreeViewer().getControl().setFont(dialogFont);
1193 }
1194}