1
0
Fork 0
mirror of https://github.com/eclipse-cdt/cdt synced 2025-07-13 12:05:21 +02:00

Bug 496249: Tags for disabling/enabling CDT code formatter

Change-Id: I4389c61612da6a4ee0011a49d6aeed7b52152436
Signed-off-by: Marco Stornelli <marco.stornelli@gmail.com>
This commit is contained in:
Marco Stornelli 2019-02-16 11:02:42 +01:00 committed by Jonah Graham
parent f60bbf25dd
commit a6d06902b1
11 changed files with 484 additions and 7 deletions

View file

@ -2,7 +2,7 @@ Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: %pluginName
Bundle-SymbolicName: org.eclipse.cdt.core; singleton:=true
Bundle-Version: 6.6.100.qualifier
Bundle-Version: 6.7.0.qualifier
Bundle-Activator: org.eclipse.cdt.core.CCorePlugin
Bundle-Vendor: %providerName
Bundle-Localization: plugin

View file

@ -91,6 +91,38 @@ public class DefaultCodeFormatterConstants {
*/
public static final String FALSE = "false"; //$NON-NLS-1$
/**
* <pre>
* FORMATTER / Formatter on tag format option
* - option id: "org.eclipse.cdt.core.formatter.comment_formatter_on_tag"
* - default: @formatter:on
* </pre>
* @see CCorePlugin#FORMAT_ON_TAG
* @since 6.7
*/
public static final String FORMATTER_COMMENT_ON_TAG = CCorePlugin.PLUGIN_ID + ".formatter.comment_formatter_on_tag"; //$NON-NLS-1$
/**
* <pre>
* FORMATTER / Formatter off tag format option
* - option id: "org.eclipse.cdt.core.formatter.comment_formatter_off_tag"
* - default: @formatter:off
* </pre>
* @see CCorePlugin#FORMAT_OFF_TAG
* @since 6.7
*/
public static final String FORMATTER_COMMENT_OFF_TAG = CCorePlugin.PLUGIN_ID
+ ".formatter.comment_formatter_off_tag"; //$NON-NLS-1$
/**
* <pre>
* FORMATTER / Formatter on tag format option
* - option id: "org.eclipse.cdt.core.formatter.use_comment_formatter_tag"
* - default: true
* </pre>
* @since 6.7
*/
public static final String FORMATTER_USE_COMMENT_TAG = CCorePlugin.PLUGIN_ID
+ ".formatter.use_comment_formatter_tag"; //$NON-NLS-1$
// /**
// * <pre>
// * FORMATTER / Option to align type members of a type declaration on column
@ -2484,6 +2516,21 @@ public class DefaultCodeFormatterConstants {
*/
public static final int WRAP_ONE_PER_LINE = 3;
/**
* <pre>
* FORMATTER / Default formatter on tag
* </pre>
* @since 6.7
*/
public static final String FORMATTER_ON_TAG = "@formatter:on"; //$NON-NLS-1$
/**
* <pre>
* FORMATTER / Default formatter off tag
* </pre>
* @since 6.7
*/
public static final String FORMATTER_OFF_TAG = "@formatter:off"; //$NON-NLS-1$
/*
* Private constants.
*/

View file

@ -264,6 +264,12 @@ public class DefaultCodeFormatterOptions {
public boolean use_tabs_only_for_leading_indentations;
public int initial_indentation_level;
public String line_separator;
/** @since 6.7 */
public String comment_formatter_on_tag;
/** @since 6.7 */
public String comment_formatter_off_tag;
/** @since 6.7 */
public boolean use_fomatter_comment_tag;
private DefaultCodeFormatterOptions() {
// cannot be instantiated
@ -282,6 +288,11 @@ public class DefaultCodeFormatterOptions {
public Map<String, String> getMap() {
Map<String, String> options = new HashMap<>();
options.put(DefaultCodeFormatterConstants.FORMATTER_COMMENT_ON_TAG, comment_formatter_on_tag);
options.put(DefaultCodeFormatterConstants.FORMATTER_COMMENT_OFF_TAG, comment_formatter_off_tag);
options.put(DefaultCodeFormatterConstants.FORMATTER_USE_COMMENT_TAG,
this.use_fomatter_comment_tag ? DefaultCodeFormatterConstants.TRUE
: DefaultCodeFormatterConstants.FALSE);
// options.put(DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_ARGUMENTS_IN_ALLOCATION_EXPRESSION, getAlignment(this.alignment_for_arguments_in_allocation_expression));
options.put(DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_ARGUMENTS_IN_METHOD_INVOCATION,
getAlignment(this.alignment_for_arguments_in_method_invocation));
@ -2025,10 +2036,25 @@ public class DefaultCodeFormatterOptions {
this.tab_char = MIXED;
}
}
final Object formatterCommentOnTag = settings.get(DefaultCodeFormatterConstants.FORMATTER_COMMENT_ON_TAG);
if (formatterCommentOnTag != null) {
this.comment_formatter_on_tag = (String) formatterCommentOnTag;
}
final Object formatterCommentOffTag = settings.get(DefaultCodeFormatterConstants.FORMATTER_COMMENT_OFF_TAG);
if (formatterCommentOffTag != null) {
this.comment_formatter_off_tag = (String) formatterCommentOffTag;
}
final Object useFormatterCommentTag = settings.get(DefaultCodeFormatterConstants.FORMATTER_USE_COMMENT_TAG);
if (useFormatterCommentTag != null) {
this.use_fomatter_comment_tag = DefaultCodeFormatterConstants.TRUE.equals(useFormatterCommentTag);
}
}
public void setDefaultSettings() {
// this.alignment_for_arguments_in_allocation_expression = Alignment.M_COMPACT_SPLIT;
this.comment_formatter_on_tag = DefaultCodeFormatterConstants.FORMATTER_ON_TAG;
this.comment_formatter_off_tag = DefaultCodeFormatterConstants.FORMATTER_OFF_TAG;
this.use_fomatter_comment_tag = true;
this.alignment_for_arguments_in_method_invocation = Alignment.M_COMPACT_SPLIT;
this.alignment_for_assignment = Alignment.M_COMPACT_SPLIT;
this.alignment_for_base_clause_in_type_declaration = Alignment.M_NEXT_PER_LINE_SPLIT;

View file

@ -35,6 +35,7 @@ import org.eclipse.cdt.core.dom.ast.IASTBinaryExpression;
import org.eclipse.cdt.core.dom.ast.IASTBreakStatement;
import org.eclipse.cdt.core.dom.ast.IASTCaseStatement;
import org.eclipse.cdt.core.dom.ast.IASTCastExpression;
import org.eclipse.cdt.core.dom.ast.IASTComment;
import org.eclipse.cdt.core.dom.ast.IASTCompositeTypeSpecifier;
import org.eclipse.cdt.core.dom.ast.IASTCompoundStatement;
import org.eclipse.cdt.core.dom.ast.IASTConditionalExpression;
@ -438,7 +439,8 @@ public class CodeFormatterVisitor extends ASTVisitor implements ICPPASTVisitor,
localScanner.setSource(compilationUnitSource);
scribe.initializeScanner(compilationUnitSource);
scribe.setSkipPositions(collectInactiveCodePositions(unit));
scribe.setSkipInactivePositions(collectInactiveCodePositions(unit));
scribe.setSkipForbiddenPositions(collectNoFormatCodePositions(unit));
fStatus = new MultiStatus(CCorePlugin.PLUGIN_ID, 0, "Formatting problem(s) in '" + unit.getFilePath() + "'", //$NON-NLS-1$//$NON-NLS-2$
null);
@ -4465,6 +4467,64 @@ public class CodeFormatterVisitor extends ASTVisitor implements ICPPASTVisitor,
return false;
}
/**
* Collect source positions of no-format sections in the given translation unit.
*
* @param translationUnit the {@link IASTTranslationUnit}, may be <code>null</code>
* @return a {@link List} of {@link Position}s
*/
private List<Position> collectNoFormatCodePositions(IASTTranslationUnit translationUnit) {
if (translationUnit == null || !this.preferences.use_fomatter_comment_tag) {
return Collections.emptyList();
}
String fileName = translationUnit.getFilePath();
if (fileName == null) {
return Collections.emptyList();
}
List<Position> positions = new ArrayList<>();
int inactiveCodeStart = -1;
boolean inInactiveCode = false;
IASTComment[] commentsStmts = translationUnit.getComments();
for (IASTComment commentStmt : commentsStmts) {
IASTComment statement = commentStmt;
if (!statement.isPartOfTranslationUnitFile()) {
// comment is from a different file
continue;
}
IASTNodeLocation nodeLocation = statement.getFileLocation();
if (nodeLocation == null) {
continue;
}
String comment = new String(statement.getComment());
/**
* According to JDT formatter rules, we need to evaluate the latest tag if both
* are defined at the same time in the comment.
*/
int offPos = comment.lastIndexOf(this.preferences.comment_formatter_off_tag);
int onPos = comment.lastIndexOf(this.preferences.comment_formatter_on_tag);
if (offPos != -1 && offPos > onPos) {
if (!inInactiveCode) {
inactiveCodeStart = nodeLocation.getNodeOffset();
inInactiveCode = true;
}
} else if (onPos != -1 && onPos > offPos) {
if (inInactiveCode) {
int inactiveCodeEnd = nodeLocation.getNodeOffset();
positions.add(new Position(inactiveCodeStart, inactiveCodeEnd - inactiveCodeStart));
}
inInactiveCode = false;
}
}
if (inInactiveCode) {
positions.add(new Position(inactiveCodeStart, translationUnit.getFileLocation().getNodeLength()));
inInactiveCode = false;
}
return positions;
}
/**
* Collect source positions of preprocessor-hidden branches
* in the given translation unit.

View file

@ -88,7 +88,14 @@ public class Scribe {
private int textRegionStart;
public int scannerEndPosition;
private List<Position> fSkipPositions = Collections.emptyList();
/**
* It keeps the list of inactive region.
*/
private List<Position> fSkipInactivePositions = Collections.emptyList();
/**
* It keeps the list of no-format region.
*/
private List<Position> fSkipForbiddenPositions = Collections.emptyList();
private boolean skipOverInactive;
@ -667,11 +674,20 @@ public class Scribe {
}
}
/**
* Set the positions where we don't want to perform any check
* @param list The list of positions
*/
public void setSkipForbiddenPositions(List<Position> list) {
if (list != null)
fSkipForbiddenPositions = list;
}
/**
* @param list
*/
public void setSkipPositions(List<Position> list) {
fSkipPositions = list;
public void setSkipInactivePositions(List<Position> list) {
fSkipInactivePositions = list;
skipOverInactive = !list.isEmpty();
}
@ -1283,12 +1299,26 @@ public class Scribe {
return hasWhitespace;
}
/**
* @param offset
* @return
*/
private boolean isForbidden(int offset) {
for (Iterator<Position> iter = fSkipForbiddenPositions.iterator(); iter.hasNext();) {
Position pos = iter.next();
if (pos.includes(offset)) {
return true;
}
}
return false;
}
/**
* @param offset
* @return
*/
private Position getInactivePosAt(int offset) {
for (Iterator<Position> iter = fSkipPositions.iterator(); iter.hasNext();) {
for (Iterator<Position> iter = fSkipInactivePositions.iterator(); iter.hasNext();) {
Position pos = iter.next();
if (pos.includes(offset)) {
return pos;
@ -2037,7 +2067,7 @@ public class Scribe {
}
boolean shouldSkip(int offset) {
return offset >= fSkipStartOffset && offset < fSkipEndOffset;
return ((offset >= fSkipStartOffset && offset < fSkipEndOffset) || isForbidden(offset));
}
void skipRange(int offset, int endOffset) {

View file

@ -3563,4 +3563,44 @@ public class CodeFormatterTest extends BaseUITestCase {
public void testAttributedNamedScopedEnumDeclaration_Bug535256_4() throws Exception {
assertFormatterResult();
}
////@formatter:off
//int
//main(){
//return
//0
//;}
////@formatter:on
////@formatter:off
//int
//main(){
//return
//0
//;}
////@formatter:on
public void testOnOffTags() throws Exception {
fOptions.put(DefaultCodeFormatterConstants.FORMATTER_USE_COMMENT_TAG, true);
fOptions.put(DefaultCodeFormatterConstants.FORMATTER_COMMENT_ON_TAG, "@formatter:on");
fOptions.put(DefaultCodeFormatterConstants.FORMATTER_COMMENT_OFF_TAG, "@formatter:off");
assertFormatterResult();
}
////@formatter:off
// void this_line_intentionally_indented() {
// int x;
// }
////@formatter:on
////@formatter:off
// void this_line_intentionally_indented() {
// int x;
// }
////@formatter:on
public void testOnOffTagsDoesNotChangeFirstLineIndent() throws Exception {
fOptions.put(DefaultCodeFormatterConstants.FORMATTER_USE_COMMENT_TAG, true);
fOptions.put(DefaultCodeFormatterConstants.FORMATTER_COMMENT_ON_TAG, "@formatter:on");
fOptions.put(DefaultCodeFormatterConstants.FORMATTER_COMMENT_OFF_TAG, "@formatter:off");
assertFormatterResult();
}
}

View file

@ -369,6 +369,7 @@ final class FormatterMessages extends NLS {
public static String ModifyDialog_tabpage_braces_title;
public static String ModifyDialog_tabpage_indentation_title;
public static String ModifyDialog_tabpage_whitespace_title;
public static String ModifyDialog_tabpage_formatter_tag_title;
// public static String ModifyDialog_tabpage_blank_lines_title;
public static String ModifyDialog_tabpage_new_lines_title;
public static String ModifyDialog_tabpage_control_statements_title;
@ -402,6 +403,15 @@ final class FormatterMessages extends NLS {
public static String CPreview_formatter_exception;
public static String FormatterModifyDialog_offOn_preview_header;
public static String FormatterModifyDialog_offOn_description;
public static String FormatterModifyDialog_offOn_pref_enable;
public static String FormatterModifyDialog_offOn_pref_off_tag;
public static String FormatterModifyDialog_offOn_pref_on_tag;
public static String FormatterModifyDialog_offOn_error_startsWithWhitespace;
public static String FormatterModifyDialog_offOn_error_endsWithWhitespace;
public static String FormatterModifyDialog_offOn_error_empty;
private FormatterMessages() {
// Do not instantiate
}

View file

@ -433,6 +433,7 @@ ModifyDialog_tabpage_new_lines_title=New &Lines
ModifyDialog_tabpage_control_statements_title=Con&trol Statements
ModifyDialog_tabpage_line_wrapping_title=Line Wra&pping
ModifyDialog_tabpage_comments_title=Co&mments
ModifyDialog_tabpage_formatter_tag_title=Off/On Tags
ModifyDialog_dialog_title=Profile ''{0}''
ModifyDialog_apply_button=Apply
@ -476,3 +477,12 @@ CPreview_formatter_exception=The formatter threw an unhandled exception while fo
ProfileConfigurationBlock_load_profile_wrong_profile_message=Import failed. This is not a valid profile: Expected ''{0}'' but encountered ''{1}''.
FormatterTabPage_ShowInvisibleCharacters_label=Show &invisible characters
FormatterModifyDialog_offOn_preview_header=Off/On tags
FormatterModifyDialog_offOn_description=Off/On tags can be used in any comments to turn the formatter off and on in a source file.\n- At the beginning of each file, the formatter is enabled.\n- Each time the formatter sees an off tag, it disables formatting for that comment and the source after it.\n- Each time the formatter sees an on tag, it enables formatting for the source after that comment.\n
FormatterModifyDialog_offOn_pref_enable=Enable Off/On tags
FormatterModifyDialog_offOn_pref_off_tag=Off tag:
FormatterModifyDialog_offOn_pref_on_tag=On tag:
FormatterModifyDialog_offOn_error_startsWithWhitespace=This value must not start with a white space.
FormatterModifyDialog_offOn_error_endsWithWhitespace=This value must not end with a white space.
FormatterModifyDialog_offOn_error_empty=This value must not be empty.

View file

@ -38,5 +38,6 @@ public class FormatterModifyDialog extends ModifyDialog {
new ControlStatementsTabPage(this, values));
addTabPage(FormatterMessages.ModifyDialog_tabpage_line_wrapping_title, new LineWrappingTabPage(this, values));
addTabPage(FormatterMessages.ModifyDialog_tabpage_comments_title, new CommentsTabPage(this, values));
addTabPage(FormatterMessages.ModifyDialog_tabpage_formatter_tag_title, new FormatterTagTabPage(this, values));
}
}

View file

@ -0,0 +1,93 @@
/*******************************************************************************
* Copyright (c) 2019 Marco Stornelli
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
*******************************************************************************/
package org.eclipse.cdt.internal.ui.preferences.formatter;
import java.util.Map;
import java.util.Observable;
import java.util.Observer;
import org.eclipse.cdt.core.formatter.DefaultCodeFormatterConstants;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Group;
public class FormatterTagTabPage extends FormatterTabPage {
private final String PREVIEW = createPreviewHeader(FormatterMessages.FormatterModifyDialog_offOn_preview_header)
+ "void method1() { doSomething(); }\n\n// @formatter:off\n" //$NON-NLS-1$
+ "void method2() { doSomething(); }\n// @formatter:on\n\n" //$NON-NLS-1$
+ "void method3() { doSomething(); }\n\n" //$NON-NLS-1$
+ "/* @formatter:off */\n\nvoid\nfoo()\n;"; //$NON-NLS-1$
private StringPreference fOnTag;
private StringPreference fOffTag;
private CheckboxPreference fUseTag;
private TranslationUnitPreview fPreview;
public FormatterTagTabPage(IModificationListener modifyListener, Map<String, String> workingValues) {
super(modifyListener, workingValues);
}
@Override
protected void initializePage() {
fPreview.setPreviewText(PREVIEW);
}
@Override
protected void doUpdatePreview() {
super.doUpdatePreview();
fPreview.update();
}
@Override
protected void doCreatePreferences(Composite composite, int numColumns) {
final Group generalGroup = createGroup(numColumns, composite,
FormatterMessages.ModifyDialog_tabpage_formatter_tag_title);
createLabel(numColumns, generalGroup, FormatterMessages.FormatterModifyDialog_offOn_description);
fUseTag = createCheckboxPref(generalGroup, numColumns,
FormatterMessages.FormatterModifyDialog_offOn_pref_enable,
DefaultCodeFormatterConstants.FORMATTER_USE_COMMENT_TAG, FALSE_TRUE);
fUseTag.addObserver(new Observer() {
@Override
public void update(Observable arg0, Object arg1) {
fOnTag.setEnabled(fUseTag.getChecked());
fOffTag.setEnabled(fUseTag.getChecked());
}
});
PreferenceValidator validator = new PreferenceValidator() {
@Override
public String validate(String value) {
if (value != null && !value.isEmpty()) {
if (Character.isWhitespace(value.charAt(0))) {
return FormatterMessages.FormatterModifyDialog_offOn_error_startsWithWhitespace;
} else if (Character.isWhitespace(value.charAt(value.length() - 1))) {
return FormatterMessages.FormatterModifyDialog_offOn_error_endsWithWhitespace;
}
return null;
}
return FormatterMessages.FormatterModifyDialog_offOn_error_empty;
}
};
fOffTag = createStringPref(generalGroup, numColumns, FormatterMessages.FormatterModifyDialog_offOn_pref_off_tag,
DefaultCodeFormatterConstants.FORMATTER_COMMENT_OFF_TAG);
fOffTag.setValidator(validator);
fOnTag = createStringPref(generalGroup, numColumns, FormatterMessages.FormatterModifyDialog_offOn_pref_on_tag,
DefaultCodeFormatterConstants.FORMATTER_COMMENT_ON_TAG);
fOnTag.setValidator(validator);
}
@Override
protected CPreview doCreateCPreview(Composite parent) {
fPreview = new TranslationUnitPreview(fWorkingValues, parent);
return fPreview;
}
}

View file

@ -68,6 +68,18 @@ public abstract class ModifyDialogTabPage implements IModifyDialogTabPage {
}
};
/**
* Preference validator
*/
interface PreferenceValidator {
/**
* Validate callback
* @param value The value to be checked
* @return String error or null otherwise
*/
String validate(String value);
}
/**
* The base class of all Preference classes. A preference class provides a wrapper
* around one or more SWT widgets and handles the input of values for some key.
@ -77,6 +89,7 @@ public abstract class ModifyDialogTabPage implements IModifyDialogTabPage {
private final Map<String, String> fPreferences;
private boolean fEnabled;
private String fKey;
private PreferenceValidator fValidator;
/**
* Create a new Preference.
@ -143,6 +156,22 @@ public abstract class ModifyDialogTabPage implements IModifyDialogTabPage {
* of this object has changed (enabled, key, ...).
*/
protected abstract void updateWidget();
public void setValidator(PreferenceValidator validator) {
fValidator = validator;
}
/**
* Check if preference is valid according to its validator
* @param value The preference value
* @return Null if valid, the error string otherwise
*/
protected String isValid(String value) {
if (fValidator != null) {
return fValidator.validate(value);
}
return null;
}
}
/**
@ -318,6 +347,126 @@ public abstract class ModifyDialogTabPage implements IModifyDialogTabPage {
}
}
/**
* Wrapper around a textfied
*/
protected final class StringPreference extends Preference {
private final Label fLabel;
private final Text fText;
protected String fSelected;
protected String fOldSelected;
/**
* Create a new NumberPreference.
* @param composite The composite on which the SWT widgets are added.
* @param numColumns The number of columns in the composite's GridLayout.
* @param preferences The map to store the values.
* @param key The key to store the values.
* @param text The label text for this Preference.
*/
public StringPreference(Composite composite, int numColumns, Map<String, String> preferences, String key,
String text) {
super(preferences, key);
fLabel = createLabel(numColumns - 1, composite, text, GridData.FILL_HORIZONTAL);
fText = new Text(composite, SWT.SINGLE | SWT.BORDER | SWT.LEFT);
fText.setFont(composite.getFont());
fText.setLayoutData(
createGridData(1, GridData.HORIZONTAL_ALIGN_END, fPixelConverter.convertWidthInCharsToPixels(20)));
updateWidget();
fText.addFocusListener(new FocusListener() {
@Override
public void focusGained(FocusEvent e) {
StringPreference.this.focusGained();
}
@Override
public void focusLost(FocusEvent e) {
StringPreference.this.focusLost();
}
});
fText.addModifyListener(new ModifyListener() {
@Override
public void modifyText(ModifyEvent e) {
fieldModified();
}
});
}
private IStatus createErrorStatus(String error) {
return new Status(IStatus.ERROR, CUIPlugin.getPluginId(), 0, Messages.format(error), null);
}
protected void focusGained() {
fOldSelected = fSelected;
fText.setSelection(0, fText.getCharCount());
}
protected void focusLost() {
updateStatus(null);
final String input = fText.getText();
if (validInput(input) != null)
fSelected = fOldSelected;
else
fSelected = input;
if (fSelected != fOldSelected) {
saveSelected();
fText.setText(fSelected);
}
}
protected void fieldModified() {
final String trimInput = fText.getText().trim();
final String error = validInput(fText.getText());
updateStatus(error == null ? null : createErrorStatus(error));
if (error == null) {
if (fSelected.equals(trimInput)) {
fSelected = trimInput;
saveSelected();
}
}
}
private String validInput(String input) {
return isValid(input);
}
private void saveSelected() {
getPreferences().put(getKey(), fSelected);
setChanged();
notifyObservers();
}
@Override
protected void updateWidget() {
final boolean hasKey = getKey() != null;
fLabel.setEnabled(hasKey && getEnabled());
fText.setEnabled(hasKey && getEnabled());
if (hasKey) {
String s = getPreferences().get(getKey());
fSelected = s;
fText.setText(s);
} else {
fText.setText(""); //$NON-NLS-1$
}
}
@Override
public Control getControl() {
return fText;
}
}
/**
* Wrapper around a textfied which requests an integer input of a given range.
*/
@ -897,6 +1046,17 @@ public abstract class ModifyDialogTabPage implements IModifyDialogTabPage {
return group;
}
/*
* Convenience method to create a NumberPreference. The widget is registered as
* a potential focus holder, and the default updater is added.
*/
protected StringPreference createStringPref(Composite composite, int numColumns, String name, String key) {
final StringPreference pref = new StringPreference(composite, numColumns, fWorkingValues, key, name);
fDefaultFocusManager.add(pref);
pref.addObserver(fUpdater);
return pref;
}
/*
* Convenience method to create a NumberPreference. The widget is registered as
* a potential focus holder, and the default updater is added.