blob: 39f928bd2ce77215e982f61666bb01ba2853dce9 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008 Matthew Hall and others.
* 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:
* Matthew Hall - initial API and implementation (bug 249992)
******************************************************************************/
package org.eclipse.core.databinding.observable.value;
import org.eclipse.core.databinding.observable.Diffs;
import org.eclipse.core.databinding.observable.Realm;
import org.eclipse.core.internal.databinding.Util;
/**
* An observable value which behaves similarly to the <select> and
* <option> HTML tags. A SelectObservableValue has a number of options
* added to it via the {@link #addOption(Object, IObservableValue)} method. The
* value of the SelectObservableValue is the value of whichever option's
* observable has a value of Boolean.TRUE, or null if none of the observable's
* values are Boolean.TRUE.
*
* @noextend This class is not intended to be subclassed by clients.
*
* @since 1.2
*/
public class SelectObservableValue extends AbstractObservableValue {
private class Option {
private final Object value;
private final IObservableValue observable;
public Option(Object value, IObservableValue observable) {
this.value = value;
this.observable = observable;
}
}
private final Object valueType;
private Option[] options;
private int selectionIndex = -1; // n/a while not hasListeners()
private boolean updating = false;
private IValueChangeListener listener = new IValueChangeListener() {
public void handleValueChange(ValueChangeEvent event) {
if (!updating) {
IObservableValue observable = event.getObservableValue();
if (Boolean.TRUE.equals(observable.getValue())) {
notifyIfChanged(indexOfObservable(observable));
}
}
}
};
/**
* Constructs a SelectObservableValue on the default realm.
*/
public SelectObservableValue() {
this(Realm.getDefault(), null);
}
/**
* Constructs a SelectObservableValue on the specified realm.
*
* @param realm
* the realm
*/
public SelectObservableValue(Realm realm) {
this(realm, null);
}
/**
* Constructs a SelectObservableValue on the default realm, with the given
* value type.
*
* @param valueType
* the value type
*/
public SelectObservableValue(Object valueType) {
this(Realm.getDefault(), valueType);
}
/**
* Constructs a SelectObservableValue on the given realm, with the given
* value type.
*
* @param realm
* the realm
* @param valueType
* the value type
*/
public SelectObservableValue(Realm realm, Object valueType) {
super(realm);
this.valueType = valueType;
this.options = new Option[0];
}
protected void firstListenerAdded() {
super.firstListenerAdded();
selectionIndex = indexOfValue(getLiveValue());
for (int i = 0; i < options.length; i++) {
options[i].observable.addValueChangeListener(listener);
}
}
protected void lastListenerRemoved() {
for (int i = 0; i < options.length; i++) {
options[i].observable.removeValueChangeListener(listener);
}
selectionIndex = -1;
super.lastListenerRemoved();
}
public Object getValueType() {
return valueType;
}
private void notifyIfChanged(int index) {
if (hasListeners() && selectionIndex != index) {
Object oldValue = valueAtIndex(selectionIndex);
Object newValue = valueAtIndex(index);
selectionIndex = index;
fireValueChange(Diffs.createValueDiff(oldValue, newValue));
}
}
/**
* Adds an option to this SelectObservableValue. If the observable contains
* Boolean.TRUE then the selection changes immediately to the given value.
*
* @param value
* The value associated with the provided observable
* @param observable
* an observable of value type Boolean.class or Boolean.TYPE
*/
public void addOption(Object value, IObservableValue observable) {
checkRealm();
Option option = new Option(value, observable);
addOption(option);
if (hasListeners()) {
observable.addValueChangeListener(listener);
if (Boolean.TRUE.equals(observable.getValue())) {
notifyIfChanged(indexOfObservable(observable));
}
}
}
private void addOption(Option option) {
Option[] newOptions = new Option[options.length + 1];
System.arraycopy(options, 0, newOptions, 0, options.length);
newOptions[options.length] = option;
options = newOptions;
}
protected Object doGetValue() {
return hasListeners() ? valueAtIndex(selectionIndex) : getLiveValue();
}
private Object getLiveValue() {
for (int i = 0; i < options.length; i++) {
if (Boolean.TRUE.equals(options[i].observable.getValue()))
return options[i].value;
}
return null;
}
protected void doSetValue(Object value) {
int index = indexOfValue(value);
try {
updating = true;
for (int i = 0; i < options.length; i++) {
options[i].observable.setValue(i == index ? Boolean.TRUE
: Boolean.FALSE);
}
} finally {
updating = false;
}
notifyIfChanged(index);
}
private Object valueAtIndex(int index) {
if (index == -1)
return null;
return options[index].value;
}
private int indexOfValue(Object value) {
for (int i = 0; i < options.length; i++)
if (Util.equals(options[i].value, value))
return i;
return -1;
}
private int indexOfObservable(IObservableValue observable) {
for (int i = 0; i < options.length; i++)
if (options[i].observable == observable)
return i;
return -1;
}
}