Source code: org/eclipse/jface/preference/PreferencePage.java
1 /*******************************************************************************
2 * Copyright (c) 2000, 2003 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 * Sebastian Davids <sdavids@gmx.de> - Fix for bug 38729 - [Preferences]
11 * NPE PreferencePage isValid.
12 *******************************************************************************/
13 package org.eclipse.jface.preference;
14
15 import org.eclipse.jface.dialogs.*;
16 import org.eclipse.jface.dialogs.Dialog;
17 import org.eclipse.jface.resource.ImageDescriptor;
18 import org.eclipse.jface.resource.JFaceResources;
19 import org.eclipse.jface.util.IPropertyChangeListener;
20 import org.eclipse.jface.util.PropertyChangeEvent;
21 import org.eclipse.swt.SWT;
22 import org.eclipse.swt.events.*;
23 import org.eclipse.swt.graphics.Font;
24 import org.eclipse.swt.graphics.Point;
25 import org.eclipse.swt.layout.GridData;
26 import org.eclipse.swt.layout.GridLayout;
27 import org.eclipse.swt.widgets.*;
28
29 /**
30 * Abstract base implementation for all preference page implementations.
31 * <p>
32 * Subclasses must implement the <code>createControl</code> framework
33 * method to supply the page's main control.
34 * </p>
35 * <p>
36 * Subclasses should extend the <code>doComputeSize</code> framework
37 * method to compute the size of the page's control.
38 * </p>
39 * <p>
40 * Subclasses may override the <code>performOk</code>, <code>performApply</code>,
41 * <code>performDefaults</code>, <code>performCancel</code>, and <code>performHelp</code>
42 * framework methods to react to the standard button events.
43 * </p>
44 * <p>
45 * Subclasses may call the <code>noDefaultAndApplyButton</code> framework
46 * method before the page's control has been created to suppress
47 * the standard Apply and Defaults buttons.
48 * </p>
49 */
50 public abstract class PreferencePage
51 extends DialogPage
52 implements IPreferencePage {
53
54 /**
55 * Preference store, or <code>null</code>.
56 */
57 private IPreferenceStore preferenceStore;
58
59 /**
60 * Valid state for this page; <code>true</code> by default.
61 *
62 * @see #isValid
63 */
64 private boolean isValid = true;
65
66 /**
67 * Body of page.
68 */
69 private Control body;
70
71 /**
72 * Whether this page has the standard Apply and Defaults buttons;
73 * <code>true</code> by default.
74 *
75 * @see #noDefaultAndApplyButton
76 */
77 private boolean createDefaultAndApplyButton = true;
78
79 /**
80 * Standard Defaults button, or <code>null</code> if none.
81 * This button has id <code>DEFAULTS_ID</code>.
82 */
83 private Button defaultsButton = null;
84 /**
85 * The container this preference page belongs to; <code>null</code>
86 * if none.
87 */
88 private IPreferencePageContainer container = null;
89
90 /**
91 * Standard Apply button, or <code>null</code> if none.
92 * This button has id <code>APPLY_ID</code>.
93 */
94 private Button applyButton = null;
95
96 /**
97 * Description label.
98 *
99 * @see #createDescriptionLabel.
100 */
101 private Label descriptionLabel;
102
103 /**
104 * Caches size of page.
105 */
106 private Point size = null;
107
108 /**
109 * Creates a new preference page with an empty title and no image.
110 */
111 protected PreferencePage() {
112 this(""); //$NON-NLS-1$
113 }
114 /**
115 * Creates a new preference page with the given title and no image.
116 *
117 * @param title the title of this preference page
118 */
119 protected PreferencePage(String title) {
120 super(title);
121 }
122 /**
123 * Creates a new abstract preference page with the given title and image.
124 *
125 * @param title the title of this preference page
126 * @param image the image for this preference page,
127 * or <code>null</code> if none
128 */
129 protected PreferencePage(String title, ImageDescriptor image) {
130 super(title, image);
131 }
132 /**
133 * Computes the size for this page's UI control.
134 * <p>
135 * The default implementation of this <code>IPreferencePage</code>
136 * method returns the size set by <code>setSize</code>; if no size
137 * has been set, but the page has a UI control, the framework
138 * method <code>doComputeSize</code> is called to compute the size.
139 * </p>
140 *
141 * @return the size of the preference page encoded as
142 * <code>new Point(width,height)</code>, or
143 * <code>(0,0)</code> if the page doesn't currently have any UI component
144 */
145 public Point computeSize() {
146 if (size != null)
147 return size;
148 Control control = getControl();
149 if (control != null) {
150 size = doComputeSize();
151 return size;
152 }
153 return new Point(0, 0);
154 }
155 /**
156 * Contributes additional buttons to the given composite.
157 * <p>
158 * The default implementation of this framework hook method does
159 * nothing. Subclasses should override this method to contribute buttons
160 * to this page's button bar. For each button a subclass contributes,
161 * it must also increase the parent's grid layout number of columns
162 * by one; that is,
163 * <pre>
164 * ((GridLayout) parent.getLayout()).numColumns++);
165 * </pre>
166 * </p>
167 *
168 * @param parent the button bar
169 */
170 protected void contributeButtons(Composite parent) {
171 }
172 /**
173 * Creates and returns the SWT control for the customized body
174 * of this preference page under the given parent composite.
175 * <p>
176 * This framework method must be implemented by concrete subclasses. Any
177 * subclass returning a <code>Composite</code> object whose <code>Layout</code>
178 * has default margins (for example, a <code>GridLayout</code>) are expected to
179 * set the margins of this <code>Layout</code> to 0 pixels.
180 * </p>
181 *
182 * @param parent the parent composite
183 * @return the new control
184 */
185 protected abstract Control createContents(Composite parent);
186 /**
187 * The <code>PreferencePage</code> implementation of this
188 * <code>IDialogPage</code> method creates a description label
189 * and button bar for the page. It calls <code>createContents</code>
190 * to create the custom contents of the page.
191 * <p>
192 * If a subclass that overrides this method creates a <code>Composite</code>
193 * that has a layout with default margins (for example, a <code>GridLayout</code>)
194 * it is expected to set the margins of this <code>Layout</code> to 0 pixels.
195 */
196 public void createControl(Composite parent) {
197
198 GridData gd;
199 Composite content = new Composite(parent, SWT.NULL);
200 setControl(content);
201 GridLayout layout = new GridLayout();
202 layout.marginWidth = 0;
203 layout.marginHeight = 0;
204 content.setLayout(layout);
205 //Apply the font on creation for backward compatibility
206 applyDialogFont(content);
207
208 // initialize the dialog units
209 initializeDialogUnits(content);
210
211 descriptionLabel = createDescriptionLabel(content);
212 if (descriptionLabel != null) {
213 descriptionLabel.setLayoutData(
214 new GridData(GridData.FILL_HORIZONTAL));
215 }
216
217 body = createContents(content);
218 if (body != null)
219 // null is not a valid return value but support graceful failure
220 body.setLayoutData(new GridData(GridData.FILL_BOTH));
221
222 Composite buttonBar = new Composite(content, SWT.NULL);
223 layout = new GridLayout();
224 layout.numColumns = 0;
225 layout.marginHeight = 0;
226 layout.marginWidth = 0;
227 buttonBar.setLayout(layout);
228 gd = new GridData();
229 gd.horizontalAlignment = GridData.END;
230 buttonBar.setLayoutData(gd);
231
232 contributeButtons(buttonBar);
233
234 if (createDefaultAndApplyButton) {
235 layout.numColumns = layout.numColumns + 2;
236 String[] labels = JFaceResources.getStrings(new String[] { "defaults", "apply" }); //$NON-NLS-2$//$NON-NLS-1$
237 int heightHint =
238 convertVerticalDLUsToPixels(IDialogConstants.BUTTON_HEIGHT);
239 int widthHint =
240 convertHorizontalDLUsToPixels(IDialogConstants.BUTTON_WIDTH);
241 defaultsButton = new Button(buttonBar, SWT.PUSH);
242 defaultsButton.setText(labels[0]);
243 Dialog.applyDialogFont(defaultsButton);
244 GridData data = new GridData(GridData.HORIZONTAL_ALIGN_FILL);
245 data.heightHint = heightHint;
246 data.widthHint =
247 Math.max(
248 widthHint,
249 defaultsButton.computeSize(
250 SWT.DEFAULT,
251 SWT.DEFAULT,
252 true).x);
253 defaultsButton.setLayoutData(data);
254 defaultsButton.addSelectionListener(new SelectionAdapter() {
255 public void widgetSelected(SelectionEvent e) {
256 performDefaults();
257 }
258 });
259
260 applyButton = new Button(buttonBar, SWT.PUSH);
261 applyButton.setText(labels[1]);
262 Dialog.applyDialogFont(applyButton);
263 data = new GridData(GridData.HORIZONTAL_ALIGN_FILL);
264 data.heightHint = heightHint;
265 data.widthHint =
266 Math.max(
267 widthHint,
268 applyButton.computeSize(SWT.DEFAULT, SWT.DEFAULT, true).x);
269 applyButton.setLayoutData(data);
270 applyButton.addSelectionListener(new SelectionAdapter() {
271 public void widgetSelected(SelectionEvent e) {
272 performApply();
273 }
274 });
275 applyButton.setEnabled(isValid());
276 applyDialogFont(buttonBar);
277 } else {
278 /* Check if there are any other buttons on the button bar.
279 * If not, throw away the button bar composite. Otherwise
280 * there is an unusually large button bar.
281 */
282 if (buttonBar.getChildren().length < 1)
283 buttonBar.dispose();
284 }
285 }
286
287 /**
288 * Apply the dialog font to the composite and it's children
289 * if it is set. Subclasses may override if they wish to
290 * set the font themselves.
291 * @param composite
292 */
293 protected void applyDialogFont(Composite composite){
294 Dialog.applyDialogFont(composite);
295 }
296
297 /**
298 * Creates and returns an SWT label under the given composite.
299 *
300 * @param parent the parent composite
301 * @return the new label
302 */
303 protected Label createDescriptionLabel(Composite parent) {
304 Label result = null;
305 String description = getDescription();
306 if (description != null) {
307 result = new Label(parent, SWT.WRAP);
308 result.setFont(parent.getFont());
309 result.setText(description);
310 }
311 return result;
312 }
313 /**
314 * Computes the size needed by this page's UI control.
315 * <p>
316 * All pages should override this method and set the appropriate sizes
317 * of their widgets, and then call <code>super.doComputeSize</code>.
318 * </p>
319 *
320 * @return the size of the preference page encoded as
321 * <code>new Point(width,height)</code>
322 */
323 protected Point doComputeSize() {
324 if (descriptionLabel != null && body != null) {
325 Point bodySize = body.computeSize(SWT.DEFAULT, SWT.DEFAULT, true);
326 GridData gd = (GridData) descriptionLabel.getLayoutData();
327 gd.widthHint = bodySize.x;
328 descriptionLabel.getParent().layout(true);
329 }
330 return getControl().computeSize(SWT.DEFAULT, SWT.DEFAULT, true);
331 }
332 /**
333 * Returns the preference store of this preference page.
334 * <p>
335 * This is a framework hook method for subclasses to return a
336 * page-specific preference store. The default implementation
337 * returns <code>null</code>.
338 * </p>
339 *
340 * @return the preference store, or <code>null</code> if none
341 */
342 protected IPreferenceStore doGetPreferenceStore() {
343 return null;
344 }
345 /**
346 * Returns the container of this page.
347 *
348 * @return the preference page container, or <code>null</code> if this
349 * page has yet to be added to a container
350 */
351 public IPreferencePageContainer getContainer() {
352 return container;
353 }
354 /**
355 * Returns the preference store of this preference page.
356 *
357 * @return the preference store , or <code>null</code> if none
358 */
359 public IPreferenceStore getPreferenceStore() {
360 if (preferenceStore == null)
361 preferenceStore = doGetPreferenceStore();
362 if (preferenceStore != null)
363 return preferenceStore;
364 else if (container != null)
365 return container.getPreferenceStore();
366 return null;
367 }
368 /**
369 * The preference page implementation of an <code>IPreferencePage</code>
370 * method returns whether this preference page is valid. Preference
371 * pages are considered valid by default; call <code>setValid(false)</code>
372 * to make a page invalid.
373 */
374 public boolean isValid() {
375 return isValid;
376 }
377 /**
378 * Suppresses creation of the standard Default and Apply buttons
379 * for this page.
380 * <p>
381 * Subclasses wishing a preference page wihthout these buttons
382 * should call this framework method before the page's control
383 * has been created.
384 * </p>
385 */
386 protected void noDefaultAndApplyButton() {
387 createDefaultAndApplyButton = false;
388 }
389 /**
390 * The <code>PreferencePage</code> implementation of this
391 * <code>IPreferencePage</code> method returns <code>true</code>
392 * if the page is valid.
393 */
394 public boolean okToLeave() {
395 return isValid();
396 }
397 /**
398 * Performs special processing when this page's Apply button has been pressed.
399 * <p>
400 * This is a framework hook method for sublcasses to do special things when
401 * the Apply button has been pressed.
402 * The default implementation of this framework method simply calls
403 * <code>performOk</code> to simulate the pressing of the page's OK button.
404 * </p>
405 *
406 * @see #performOk
407 */
408 protected void performApply() {
409 performOk();
410 }
411 /**
412 * The preference page implementation of an <code>IPreferencePage</code>
413 * method performs special processing when this page's Cancel button has
414 * been pressed.
415 * <p>
416 * This is a framework hook method for sublcasses to do special things when
417 * the Cancel button has been pressed. The default implementation of this
418 * framework method does nothing and returns <code>true</code>.
419 */
420 public boolean performCancel() {
421 return true;
422 }
423 /**
424 * Performs special processing when this page's Defaults button has been pressed.
425 * <p>
426 * This is a framework hook method for subclasses to do special things when
427 * the Defaults button has been pressed.
428 * Subclasses may override, but should call <code>super.performDefaults</code>.
429 * </p>
430 */
431 protected void performDefaults() {
432 updateApplyButton();
433 }
434 /**
435 * Method declared on IPreferencePage.
436 * Subclasses should override
437 */
438 public boolean performOk() {
439 return true;
440 }
441 /** (non-Javadoc)
442 * Method declared on IPreferencePage.
443 */
444 public void setContainer(IPreferencePageContainer container) {
445 this.container = container;
446 }
447 /**
448 * The <code>PreferencePage</code> implementation of this method
449 * declared on <code>DialogPage</code> updates the container.
450 */
451 public void setErrorMessage(String newMessage) {
452 super.setErrorMessage(newMessage);
453 if (getContainer() != null) {
454 getContainer().updateMessage();
455 }
456 }
457 /**
458 * The <code>PreferencePage</code> implementation of this method
459 * declared on <code>DialogPage</code> updates the container.
460 */
461 public void setMessage(String newMessage, int newType) {
462 super.setMessage(newMessage, newType);
463 if (getContainer() != null) {
464 getContainer().updateMessage();
465 }
466 }
467 /**
468 * Sets the preference store for this preference page.
469 * <p>
470 * If preferenceStore is set to null, getPreferenceStore
471 * will invoke doGetPreferenceStore the next time it is called.
472 * </p>
473 *
474 * @param store the preference store, or <code>null</code>
475 * @see #getPreferenceStore
476 */
477 public void setPreferenceStore(IPreferenceStore store) {
478 preferenceStore = store;
479 }
480 /* (non-Javadoc)
481 * Method declared on IPreferencePage.
482 */
483 public void setSize(Point uiSize) {
484 Control control = getControl();
485 if (control != null) {
486 control.setSize(uiSize);
487 size = uiSize;
488 }
489 }
490 /**
491 * The <code>PreferencePage</code> implementation of this <code>IDialogPage</code>
492 * method extends the <code>DialogPage</code> implementation to update
493 * the preference page container title. Subclasses may extend.
494 */
495 public void setTitle(String title) {
496 super.setTitle(title);
497 if (getContainer() != null)
498 getContainer().updateTitle();
499 }
500 /**
501 * Sets whether this page is valid.
502 * The enable state of the container buttons and the
503 * apply button is updated when a page's valid state
504 * changes.
505 * <p>
506 *
507 * @param b the new valid state
508 */
509 public void setValid(boolean b) {
510 boolean oldValue = isValid;
511 isValid = b;
512 if (oldValue != isValid) {
513 // update container state
514 if (getContainer() != null)
515 getContainer().updateButtons();
516 // update page state
517 updateApplyButton();
518 }
519 }
520 /**
521 * Returns a string suitable for debugging purpose only.
522 */
523 public String toString() {
524 return getTitle();
525 }
526 /**
527 * Updates the enabled state of the Apply button to reflect whether
528 * this page is valid.
529 */
530 protected void updateApplyButton() {
531 if (applyButton != null)
532 applyButton.setEnabled(isValid());
533 }
534
535 /**
536 * Creates a composite with a highlighted Note entry and a message text.
537 * This is designed to take up the full width of the page.
538 *
539 * @param font the font to use
540 * @param composite the parent composite
541 * @param title the title of the note
542 * @param message the message for the note
543 * @return the composite for the note
544 */
545 protected Composite createNoteComposite(
546 Font font,
547 Composite composite,
548 String title,
549 String message) {
550 Composite messageComposite = new Composite(composite, SWT.NONE);
551 GridLayout messageLayout = new GridLayout();
552 messageLayout.numColumns = 2;
553 messageLayout.marginWidth = 0;
554 messageLayout.marginHeight = 0;
555 messageComposite.setLayout(messageLayout);
556 messageComposite.setLayoutData(
557 new GridData(GridData.HORIZONTAL_ALIGN_FILL));
558 messageComposite.setFont(font);
559
560 final Label noteLabel = new Label(messageComposite, SWT.BOLD);
561 noteLabel.setText(title);
562 noteLabel.setFont(JFaceResources.getBannerFont());
563 noteLabel.setLayoutData(
564 new GridData(GridData.VERTICAL_ALIGN_BEGINNING));
565
566 final IPropertyChangeListener fontListener =
567 new IPropertyChangeListener() {
568 public void propertyChange(PropertyChangeEvent event) {
569 if (JFaceResources.BANNER_FONT.equals(event.getProperty())) {
570 noteLabel.setFont(
571 JFaceResources.getFont(JFaceResources.BANNER_FONT));
572 }
573 }
574 };
575 JFaceResources.getFontRegistry().addListener(fontListener);
576 noteLabel.addDisposeListener(new DisposeListener() {
577 public void widgetDisposed(DisposeEvent event) {
578 JFaceResources.getFontRegistry().removeListener(fontListener);
579 }
580 });
581
582 Label messageLabel = new Label(messageComposite, SWT.WRAP);
583 messageLabel.setText(message);
584 messageLabel.setFont(font);
585 return messageComposite;
586 }
587
588 /**
589 * Returns the Apply button.
590 *
591 * @return the Apply button
592 */
593 protected Button getApplyButton() {
594 return applyButton;
595 }
596
597 /**
598 * Returns the Restore Defaults button.
599 *
600 * @return the Restore Defaults button
601 */
602 protected Button getDefaultsButton() {
603 return defaultsButton;
604 }
605
606 /* (non-Javadoc)
607 * @see org.eclipse.jface.dialogs.IDialogPage#performHelp()
608 */
609 public void performHelp() {
610 getControl().notifyListeners(SWT.Help,new Event());
611 }
612
613 }