/*******************************************************************************
* 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 java.util.ArrayList;
import java.util.List;
import org.eclipse.core.runtime.Assert;
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.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.osgi.service.prefs.BackingStoreException;
/**
* A field editor for a combo box that allows the drop-down selection of one of a list of items.
*
* SMS: This is a copy of ComboFieldEditor, found in org.eclipse.imp.preferences.fields,
* which itself is a copy from org.eclipse.search.internal.ui.util, as indicated by the
* following comment (copied from the "original"):
*
* XXX: Note this is a copy from org.eclipse.search.internal.ui.util<br>
* This class can be removed once a published (non-internal, visible) version is available.
*/
public class ComboFieldEditor extends FieldEditor {
/**
* The <code>Combo</code> widget.
*/
private Combo fCombo = null;
/**
* The value (not the name) of the currently selected item in the Combo widget.
*/
private String fValue = null;
/**
* The previous value (not the previous name) of the currently selected item in the Combo widget.
*/
//private String fPreviousValue = null;
/**
* The names (labels) and underlying values to populate the combo widget.
*/
private List<String> fEntryLabels;
private List<String> fEntryValues;
private int fNumColumns;
/*
* Note: The specialValue may be one that is used as a default or
* one that signifies no meaningful value selected. It is assumed
* here NOT to occur in entryNamesAndValues, and it is added to
* the head of the array of names and values used here to create
* the combo box entries.
*/
public ComboFieldEditor(
PreferencePage page, PreferencesTab tab,
IPreferencesService service, String level,
String name, String labelText, String[] values, String[] labels, int numColumns,
Composite parent, boolean isEnabled, boolean isRemovable)
{
Assert.isTrue(values.length == labels.length);
init(name, labelText);
preferencesService = service;
preferencesLevel = level;
this.parent = parent;
prefPage = page;
setPage(prefPage);
prefTab = tab;
fEntryLabels= new ArrayList<String>(labels.length);
fEntryValues= new ArrayList<String>(values.length);
for(int i=0; i < labels.length; i++) {
fEntryLabels.add(labels[i]);
fEntryValues.add(values[i]);
}
fNumColumns= numColumns;
// Create control after setting fEntryNamesAndValues
// because that is referenced in creating the control
createControl(parent);
this.isRemovable = isRemovable;
}
/*
* @see FieldEditor#adjustForNumColumns(int)
*/
protected void adjustForNumColumns(int numColumns) {
Control control= getLabelControl();
if (control != null) {
((GridData) control.getLayoutData()).horizontalSpan= numColumns;
}
((GridData) fCombo.getLayoutData()).horizontalSpan= numColumns;
}
/*
* @see FieldEditor#(Composite, int)
*/
protected void doFillIntoGrid(Composite parent, int numColumns) {
Control control= getLabelControl(parent);
GridData gd= new GridData();
gd.horizontalSpan= 1;
control.setLayoutData(gd);
control= getComboBoxControl();
gd= new GridData();
gd.horizontalSpan= 1;
control.setLayoutData(gd);
}
@Override
protected void doSetToolTip() {
if (toolTipText != null) {
getComboBoxControl().setToolTipText(toolTipText);
}
}
/**
* Overrides method defined in FieldEditor.
*
* Initializes this field editor with the preference value from
* the preference store.
*/
// public void load() {
// if (preferencesService != null) {
// //isDefaultPresented = false;
// doLoad();
// refreshValidState();
// }
// }
/*
* @see FieldEditor#doLoad()
*/
protected void doLoad() {
//if (getTextControl() != null) {
if (fCombo != null) {
String value = null;
if (preferencesLevel != null) {
// The "normal" case, in which field corresponds to a preferences level
value = preferencesService.getStringPreference(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.getStringPreference(getPreferenceName());
levelFromWhichLoaded = preferencesService.getApplicableLevel(getPreferenceName(), preferencesLevel);
setInherited(true);
}
if (IPreferencesService.DEFAULT_LEVEL.equals(levelFromWhichLoaded))
setPresentsDefaultValue(true);
previousValue = value;
updateComboForValue(value);
}
}
/*
* SMS 23 Dec 2006:
* Probably want to eliminate loadDefault(..) and loadLevel(..) unless a need can be
* demonstrated and their semantics reasonably defined.
*/
/*
* @see FieldEditor#doLoadDefault()
*/
protected void doLoadDefault() {
//updateComboForValue(getPreferenceStore().getDefaultString(getPreferenceName()));
if (fCombo != null) {
String value = preferencesService.getStringPreference(IPreferencesService.DEFAULT_LEVEL, getPreferenceName());
updateComboForValue(value);
}
}
/**
* Do the work of loading the value for the given level into the field.
*/
protected void doLoadLevel(String level) {
if (fCombo != null) {
String value = preferencesService.getStringPreference(level, getPreferenceName());
updateComboForValue(value);
}
}
/*
* Load into the button 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 = 0;
// If we're loading with inheritance for some field that is
// not attached to a preferences level then assume that we
// should just search from the bottom up
String tmpPreferencesLevel = (preferencesLevel == null) ? levels[0] : preferencesLevel;
// Find the index of the level to which this field belongs
for (int i = 0; i < levels.length; i++) {
if (tmpPreferencesLevel.equals(levels[i])) {
fieldLevelIndex = i;
break;
}
}
String value = null;
int levelAtWhichFound = -1;
// Search up levels starting from the level of this field
for (int level = fieldLevelIndex; level < levels.length; level++) {
value = preferencesService.getStringPreference(levels[level], getPreferenceName());
if (value == null) continue;
levelAtWhichFound = level;
levelLoaded = levels[levelAtWhichFound];
break;
}
levelFromWhichLoaded = levelLoaded;
setInherited(fieldLevelIndex != levelAtWhichFound);
setPresentsDefaultValue(IPreferencesService.DEFAULT_LEVEL.equals(levelFromWhichLoaded));
// Set the field to the value we found
updateComboForValue(value);
fieldModified = false; // Since we just loaded some new value, it won't be modified yet
previousValue = value; // TODO: Check on use of previous value
// Set the background color of the field according to where found
setFieldColors();
return levelLoaded;
}
protected void setFieldColors() {
Control comboBox = getComboBoxControl();
Color color = isInherited() ? PreferencesUtilities.colorBluish : PreferencesUtilities.colorWhite;
comboBox.setBackground(color);
}
/**
* Overrides method implemented in RadioGroupFieldEditor.
*
* Abstract method declared on FieldEditor.
*/
protected void doStore() {
String value = getStringValue();
// We have a value (possibly empty, if that is allowed) that has changed
// from the previous value, so store it
preferencesService.setStringPreference(preferencesLevel, getPreferenceName(), value);
// If we've just stored the field, we've addressed any modifications
//System.out.println("STFE.doStore: setting fieldModified to FALSE");
fieldModified = false;
// "Level from which loaded" (or set, as the case may be) is now this level
levelFromWhichLoaded = preferencesLevel;
// If we've stored the field then it's not inherited, so be sure it's
// color indicates that.
setFieldColors();
//getComboBoxControl(parent).setBackground(PreferencesUtilities.colorWhite);
IEclipsePreferences node = preferencesService.getNodeForLevel(preferencesLevel);
try {
if (node != null) node.flush();
} catch (BackingStoreException e) {
System.err.println("ComboFieldEditor.doStore(): BackingStoreException flushing node; node may not have been flushed:" +
"\n\tnode path = " + node.absolutePath() + ", preferences level = " + preferencesLevel);
}
}
/*
* @see FieldEditor#getNumberOfControls()
*/
public int getNumberOfControls() {
return fNumColumns;
}
/*
* Lazily create and return the Combo control.
*/
public Combo getComboBoxControl() {
if (fCombo == null) {
fCombo= new Combo(parent, SWT.READ_ONLY);
for(String entryName: fEntryLabels) {
fCombo.add(entryName);
}
fCombo.setFont(parent.getFont());
fCombo.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent evt) {
String name= fCombo.getText();
fValue= getValueForName(name);
setPresentsDefaultValue(false);
// SMS 23 Dec 2006
fieldModified = true;
setInherited(false);
valueChanged();
}
});
}
return fCombo;
}
@Override
public Composite getHolder() {
return getComboBoxControl().getParent();
}
/*
* Given the name (label) of an entry, return the corresponding value.
*/
protected String getValueForName(String name) {
int idx= fEntryLabels.indexOf(name);
return (idx >= 0) ? fEntryValues.get(idx) : "";
}
/*
* Set the name in the combo widget to match the specified value.
*/
protected void updateComboForValue(String value) {
if (value == null)
value = "";
previousValue = getStringValue();
setPreviousStringValue(getStringValue());
int idx= fEntryValues.indexOf(value);
if (idx < 0) { idx = 0; }
fValue = fEntryValues.get(idx);
fCombo.setText(fEntryLabels.get(idx));
valueChanged();
}
/**
* 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. Also informs the listener (if there is one)
* of a change in the validity of the field (<code>IS_VALID</code> property).
*
* This hook is <em>not</em> called when the text is initialized
* (or reset to the default value) from the preference store.
* (That comment is taken from the original implementation of this
* method. I've tried to follow it consistently for IMP preferences,
* but I'm not sure if the original intention translates into the
* multi-level model. Still, so far there seems to be no problem
* with it. SMS 16 Nov 2006)
*
* Copied from StringFieldEditor and adapted to use in IMP.
* Added return of a boolean value. Not intended to set any attributes
* of the field editor, just to signal changes to listeners.
*/
protected boolean valueChanged() {
return valueChanged(false);
}
protected boolean valueChanged(boolean assertChanged) {
// Check for change in value
boolean valueChanged = assertChanged || inheritanceChanged();
String prevValue = getPreviousStringValue();
if (!valueChanged) {
if ((fValue != null && prevValue == null) ||
(fValue == null && prevValue != null))
{
valueChanged = true;
}
if (fValue != null && prevValue != null) {
if (!fValue.equals(prevValue)) {
valueChanged = true;
}
}
}
if (valueChanged) {
fireValueChanged(VALUE, prevValue, fValue);
fieldModified = true;
setPreviousStringValue(fValue);
setModifiedMarkOnLabel();
}
return valueChanged;
}
/**
*
*/
protected String getPreviousStringValue() {
return (String) previousValue;
}
protected void setPreviousStringValue(String value) {
previousValue = value;
}
/**
* 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(String newValue) {
setPreviousStringValue(getStringValue());
setInherited(false);
setPresentsDefaultValue(false);
levelFromWhichLoaded = null;
updateComboForValue(newValue);
}
/**
* Gets the current String value of the field, corresponding
* to the currently selected item.
*
* @return The current String value of the field, corresponding
* to the currently selected item.
*/
public String getStringValue() {
return fValue;
}
public String getSpecialStringValue() {
if (!hasSpecialValue) {
throw new IllegalStateException("ComboFieldEditor.getSpecialValue(): field does not have a special value");
}
return (String) specialValue;
}
/**
* Set the special value associated with this field to be the given string.
* Overrides the method in the supertype to check that the given value is
* a String.
*
* @param specialValue The special value to associate with this field
* @throws IllegalStateException if the field has no special value
* @throws IllegalArgumentException if the given value is null or empty
*/
public void setSpecialValue(String specialValue) {
throw new IllegalStateException("ComboField.setSpecialValue(String): field has no special value");
}
}