Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

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}