/******************************************************************************* * Copyright (c) 2007 IBM Corporation. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Robert Fuhrer (rfuhrer@watson.ibm.com) - initial API and implementation *******************************************************************************/ package org.eclipse.imp.preferences.fields; import org.eclipse.core.runtime.preferences.IEclipsePreferences; import org.eclipse.imp.preferences.IPreferencesService; import org.eclipse.imp.preferences.PreferencesTab; import org.eclipse.imp.preferences.PreferencesUtilities; import org.eclipse.jface.preference.PreferencePage; import org.eclipse.swt.SWT; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Font; import org.eclipse.swt.graphics.FontData; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; import org.eclipse.ui.PlatformUI; import org.osgi.service.prefs.BackingStoreException; /** * FieldEditor implementation for boolean preferences that support the 4-level * presentation (default, installation, workspace, and project) and "details links". * Based on the org.eclipse.jface.preference class of the same name. * @author sutton * @author rfuhrer */ public class BooleanFieldEditor extends ChangeButtonFieldEditor { /* * Fields copied from jface's BooleanFieldEditor */ /** * Style constant (value <code>0</code>) indicating the default * layout where the field editor's check box appears to the left * of the label. */ public static final int DEFAULT = 0; /** * Style constant (value <code>1</code>) indicating a layout * where the field editor's label appears on the left * with a check box on the right. */ public static final int SEPARATE_LABEL = 1; /** * Style bits. Either <code>DEFAULT</code> or * <code>SEPARATE_LABEL</code>. */ private int style; /** * The checkbox control, or <code>null</code> if none. */ private Button checkBox = null; /** * Creates a boolean field editor in the given style. * * @param name the name of the preference this field editor works on * @param labelText the label text of the field editor * @param style the style, either <code>DEFAULT</code> or * <code>SEPARATE_LABEL</code> * @param parent the parent of the field editor's control * @see #DEFAULT * @see #SEPARATE_LABEL */ public BooleanFieldEditor( PreferencePage page, PreferencesTab tab, IPreferencesService service, String level, String name, String labelText, int style, final Composite parent) { super(page, tab, service, level, name, labelText, parent); this.style = style; createControl(parent); } /** * Creates a boolean field editor. * * @param name the name of the preference this field editor works on * @param labelText the label text of the field editor * @param parent the parent of the field editor's control */ public BooleanFieldEditor( PreferencePage page, PreferencesTab tab, IPreferencesService service, String level, String name, String labelText, Composite parent) { this(page, tab, service, level, name, labelText, DEFAULT, parent); } @Override public Composite getHolder() { return (getChangeControl() != null) ? getChangeControl().getParent() : null; } /* * Methods related to loading values from the preferences service * into the preferences store. * * All of the "doLoad..." methods should * - Set isInherited, presentsDefaultValue, and levelFromWhichLoaded * since these are know directly here and vary from load method to * load method * - Call setStringValue(..), which will set previousValue and * fieldModified (which can be set generally given the old and * new values), and which will also call valueChanged(), which */ /* (non-Javadoc) * Method declared on FieldEditor. */ protected void doLoad() { if (getChangeControl() != null) { boolean value; if (preferencesLevel != null) { // The "normal" case, in which field corresponds to a preferences level value = preferencesService.getBooleanPreference(preferencesLevel, getPreferenceName()); levelFromWhichLoaded = preferencesLevel; setInherited(false); } else { // Not normal, exactly, but possible if loading is being done into a // field that is not associated with a specific level value = preferencesService.getBooleanPreference(getPreferenceName()); levelFromWhichLoaded = preferencesService.getApplicableLevel(getPreferenceName(), preferencesLevel); setInherited(true); } setPresentsDefaultValue(IPreferencesService.DEFAULT_LEVEL.equals(levelFromWhichLoaded)); setBooleanValue(value); } } /* (non-Javadoc) * Method declared on FieldEditor. */ protected void doLoadDefault() { if (getChangeControl() != null) { boolean value = preferencesService.getBooleanPreference(IPreferencesService.DEFAULT_LEVEL, getPreferenceName()); levelFromWhichLoaded = IPreferencesService.DEFAULT_LEVEL; setInherited(false); // We're putting the default value here directly, not inheriting it setPresentsDefaultValue(true); setBooleanValue(value); // calls valueChanged(); } } /* (non-Javadoc) * */ protected void doLoadLevel(String level) { if (getChangeControl() != null) { boolean value; if (preferencesLevel != null) { value = preferencesService.getBooleanPreference(level, getPreferenceName()); } else { value = preferencesService.getBooleanPreference(getPreferenceName()); } // We're putting the level's value here directly, not inheriting it, so ... levelFromWhichLoaded = level; setInherited(false); setPresentsDefaultValue(IPreferencesService.DEFAULT_LEVEL.equals(level)); setBooleanValue(value); // calls valueChanged(); } } /* (non-Javadoc) * Method declared on FieldEditor. */ protected void refreshValidState() { notifyState(true); } /* * Load into the boolean field the value for this preference that is either * the value defined on this preferences level, if any, or the value inherited * from the next applicable level, if any. Return the level at which the * value loaded was found. Load nothing and return null if no value is found. */ protected String doLoadWithInheritance() { String levelLoaded = null; String[] levels = IPreferencesService.levels; int fieldLevelIndex = preferencesService.getIndexForLevel(preferencesLevel); boolean value = false; int levelAtWhichFound = -1; IEclipsePreferences[] nodes = preferencesService.getNodesForLevels(); for (int level = fieldLevelIndex; level < levels.length; level++) { // Must have a node from which to get a value if (nodes[level] == null) continue; // Get the value from the node, not the service, because we can // check the node to see whether there is a value there for this // preference String result = nodes[level].get(getPreferenceName(), null); if (result != null) { // We have a value at the node; get it as a boolean // (presumably we could also convert the result to a boolean) value = nodes[level].getBoolean(getPreferenceName(), false); levelAtWhichFound = level; levelLoaded = levels[levelAtWhichFound]; break; } } String previousLevelFromWhichLoaded = levelFromWhichLoaded; // Ok, now have all necessary information to set everything that needs to be set levelFromWhichLoaded = levelLoaded; setInherited(fieldLevelIndex != levelAtWhichFound); setPresentsDefaultValue(IPreferencesService.DEFAULT_LEVEL.equals(levelFromWhichLoaded)); setPreviousBooleanValue(getBooleanValue()); boolean valueChanged = previousValue==null || ((Boolean)previousValue).booleanValue()!=value; boolean levelChanged = previousLevelFromWhichLoaded==null && levelFromWhichLoaded!=null || previousLevelFromWhichLoaded!=null && levelFromWhichLoaded==null || previousLevelFromWhichLoaded != null && !previousLevelFromWhichLoaded.equals(levelFromWhichLoaded); if (levelChanged || valueChanged) { setBooleanValue(value); // sets fieldModified and previousValue } if (!isInherited()) getChangeControl().setBackground(PreferencesUtilities.colorWhite); else getChangeControl().setBackground(PreferencesUtilities.colorBluish); setPresentsDefaultValue(levelAtWhichFound == IPreferencesService.DEFAULT_INDEX); return levelLoaded; } protected void doStore() { boolean value = getBooleanValue(); // Not inherited, and modified: field must have been set on this level, so store it. // Storing here should trigger preference-change listeners at each level below this. preferencesService.setBooleanPreference(preferencesLevel, getPreferenceName(), value); // If we've just stored the field, we've addressed any modifications //System.out.println("SBFE.doStore: setting fieldModified to FALSE"); fieldModified = false; // If we've stored the field then it's not inherited, so be sure it's // color indicates that. // Note that for the checkbox wiget (which is the only one used so far) // the background color is the color behind the label (not the checkbox // itself), so it should be light gray like the background in the rest // of the tab. // TODO: figure out how to determine the actual prevailing background // color and use that here getChangeControl().setBackground(PreferencesUtilities.colorWhite); // Now write out the node IEclipsePreferences node = preferencesService.getNodeForLevel(preferencesLevel); try { if (node != null) node.flush(); } catch (BackingStoreException e) { System.err.println("SBFE.doStore(): BackingStoreException ; node may not have been flushed:" + "\n\tnode path = " + node.absolutePath() + ", preferences level = " + preferencesLevel); } } /* * Preferences are stored by level, so we need to provide some * way to represent and set the applicable level. Note that * preferences can be reset at the default level during exeuction * but default level preferences are never stored between * executions. */ public void setPreferencesLevel(String level) { if (!preferencesService.isaPreferencesLevel(level)) { throw new IllegalArgumentException("SafairBooleanFieldEditor.setPreferencesLevel: given level = " + level + " is invalid"); } if (level.equals(IPreferencesService.PROJECT_LEVEL) && preferencesService.getProject() == null) { throw new IllegalStateException("SafairBooleanFieldEditor.setPreferenceLevel: given level is '" + IPreferencesService.PROJECT_LEVEL + "' but project is not defined for preferences service"); } preferencesLevel = level; } public String getPreferencesLevel() { return preferencesLevel; } /** * Returns the field editor's value. * * @return the current value */ public boolean getBooleanValue() { //return getChangeControl(parent).getSelection(); return getChangeControl().getSelection(); } /** * Set the previous value for this field * @param value The value to be set */ protected void setPreviousBooleanValue(boolean value) { previousValue = value ? Boolean.TRUE : Boolean.FALSE; } /** * Get the previous value for this field * @return The previous value */ protected boolean getPreviousBooleanValue() { return ((Boolean)previousValue).booleanValue(); } /** * Set the value of this field directly, from outside of * the field, without loading a value from the preferences * service. * * Intended for use by external clients of the field. * * In addition to setting the value of the field this method * also sets several attributes to appropriately characterize * a field that has been set in this way. * * @param newValue */ public void setFieldValueFromOutside(boolean newValue) { setPreviousBooleanValue(getBooleanValue()); setInherited(false); setPresentsDefaultValue(false); levelFromWhichLoaded = null; setBooleanValue(newValue); } /** * Sets this field editor's value through the supertype. * Sets previous value and field modified. * Also calls valueChanged(..) to signal the change in * value. * * @param value the new value */ protected void setBooleanValue(boolean newValue) { Button button = getChangeControl(); if (button != null && !button.isDisposed()) { boolean currentValue = getBooleanValue(); if (previousValue == null) setPreviousBooleanValue(!currentValue); button.setSelection(newValue); fieldModified = true; // setModifiedMarkOnLabel(); valueChanged(); } else if (button.isDisposed()) { throw new IllegalStateException("BooleanFieldEditor.setBooleanValue: button is disposed"); } else if (button == null) { throw new IllegalStateException("BooleanFieldEditor.setBooleanValue: button is null"); } } /** * Should be called whenever there is an update to the field, * regardless of whether the value has changed or not. * * Informs this field editor's listener, if it has one, about a change * to the value (<code>VALUE</code> property) provided that the old and * new values are different. * * Sets the "modified" mark on the fields label regardless of * whether the value has changed (on the assumption that the * field has, or may have, changed in some significant way. * * @param oldValue the old value * @param newValue the new value */ protected boolean valueChanged() { boolean changed = false; boolean oldValue = getPreviousBooleanValue(); boolean newValue = getBooleanValue(); if (oldValue != newValue) { fireStateChanged(VALUE, oldValue, newValue); changed = true; } // Set modify mark in any case because field may // have changed, e.g., going from inherited to not // or vice versa, without the value changing setModifiedMarkOnLabel(); return changed; } // public static final org.eclipse.swt.graphics.FontData changedFontData = new org.eclipse.swt.graphics.FontData("Monaco", 11, 2); private static final FontData[] arialFonts = PlatformUI.getWorkbench().getDisplay().getFontList("Arial", true); private static FontData[] labelFonts; /** * Initialize fonts for use in checkbox labels * @return true if appropriate fonts available on system; false if not. */ private boolean getLabelFonts() { if (labelFonts==null) { labelFonts = new FontData[2]; for (FontData fd: arialFonts) { if (fd.getHeight() <=10) { switch (fd.getStyle()) { case SWT.NORMAL: labelFonts[0] = fd; break; case SWT.ITALIC: labelFonts[1] = fd; break; } } } } if (labelFonts[0]==null || labelFonts[1] == null) { // only vary font if reasonable fonts exist for both modified/unmodified return false; } return true; } /* * For boolean fields we override the following two methods because * the means of accessing the text to be modified is different. * @see org.eclipse.imp.preferences.fields.FieldEditor#setModifyMarkOnLabel() * @see org.eclipse.imp.preferences.fields.FieldEditor#clearModifyMarkOnLabel() */ public void setModifiedMarkOnLabel() { // SMS 27 Nov 2006 // Don't presume here to deal with inheritance. If called then set mark. // Let caller worry about whether field is inherited and how that affects // the marking // if (isInherited) return; if (checkBox != null) { // String labelText = checkBox.getText(); // if (!labelText.startsWith(Markings.MODIFIED_MARK)) { // labelText = Markings.MODIFIED_MARK + labelText; // checkBox.setText(labelText); // } // replace changed mark by color to eliminate text-box overflow bug checkBox.setForeground(PreferencesUtilities.colorRed); // this doesn't work on MacOSX: use font if possible if (getLabelFonts()==false) return; checkBox.setFont(new Font(getPage().getShell().getDisplay(), labelFonts[1])); } } public void clearModifiedMarkOnLabel() { if (getLabelFonts()==false) return; if (checkBox != null) { // String labelText = checkBox.getText(); // if (labelText.startsWith(Markings.MODIFIED_MARK)) // labelText = labelText.substring(1); // checkBox.setText(labelText); // replace changed mark by color to eliminate text-box overflow bug checkBox.setForeground(PreferencesUtilities.colorBlack); // this doesn't work on MacOSX: use font if possible if (getLabelFonts()==false) return; checkBox.setFont(new Font(getPage().getShell().getDisplay(), labelFonts[0])); } } /* * Returns the change button for this field editor. * This overrides the corresponding superclass method so that we can set * a listener on the control for our purposes. * */ public Button getChangeControl() { if (!parent.isDisposed()) { if (checkBox == null) { // Should actually create checkbox if it doesn't exist // so should really never be null //checkBox = getChangeControl(parent); checkBox = new Button(parent, SWT.CHECK | SWT.LEFT); checkBox.setFont(parent.getFont()); checkBox.addSelectionListener(new SelectionAdapter() { public void widgetSelected(SelectionEvent e) { // Whenever a new value is set, we have to record the previous value. // If we're here, that means that the current value has been changed // using the GUI. Since the value in the GUI has changed, we can't // use that to retrieve the previous value. But, since this is a // boolean field, we know that the previous value must have been the // negation of the current value, so we can set the previous value // from that. It's important to set the previous value here before // calling valueChanged(), because valueChanged() can't assume that // a change has occurred--since a value loaded from the preferences // service may match the current value of the field--so valueChanged() // has to compare the new and previous values. To make that comparison // work, we need to assure that the previous value is set properly here. setPreviousBooleanValue(!getBooleanValue()); fieldModified = true; // Should call setInherited(..) before calling valueChanged() because // valueChanged() will mark the field as modified, but only if isInherited // is false, which it now should be setInherited(false); levelFromWhichLoaded = preferencesLevel; setBooleanValue(getBooleanValue()); // Set presentsDefaultValue to false on the basis that // we've set it independently of the encoded default value // even if we're on the default level. setPresentsDefaultValue(false); //valueChanged(); //setPreviousBooleanValue(getBooleanValue()); } }); checkBox.addDisposeListener(new DisposeListener() { public void widgetDisposed(DisposeEvent event) { //System.out.println("SBFE.button dispose listener (from getChangeControl): checkBoxNull set to true"); checkBox = null; } }); } else { checkParent(checkBox, parent); } return checkBox; //getChangeControl(parent); } return null; } /* * Additional methods copied from BooleanFieldEditor */ /* (non-Javadoc) * Method declared on FieldEditor. */ protected void adjustForNumColumns(int numColumns) { if (style == SEPARATE_LABEL) numColumns--; ((GridData) checkBox.getLayoutData()).horizontalSpan = numColumns; } /* (non-Javadoc) * Method declared on FieldEditor. */ protected void doFillIntoGrid(Composite parent, int numColumns) { String text = getLabelText(); String toolTipText = getToolTipText(); switch (style) { case SEPARATE_LABEL: getLabelControl(parent); numColumns--; text = null; default: checkBox = getChangeControl(); GridData gd = new GridData(); gd.horizontalSpan = numColumns; checkBox.setLayoutData(gd); if (text != null) { checkBox.setText(text); if (toolTipText != null) { checkBox.setToolTipText(toolTipText); } } } } @Override protected void doSetToolTip() { if (toolTipText != null) { getChangeControl().setToolTipText(toolTipText); } } /* (non-Javadoc) * Method declared on FieldEditor. */ public int getNumberOfControls() { switch (style) { case SEPARATE_LABEL: return 2; default: return 1; } } }