blob: e4a216ad43c70cb1c2fe8f001a003405b9971ece [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008, 2009 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 java.util.ArrayList;
import java.util.List;
import org.eclipse.core.databinding.observable.Diffs;
import org.eclipse.core.databinding.observable.Realm;
import org.eclipse.core.internal.databinding.observable.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.
*
* @param <T>
*
* @noextend This class is not intended to be subclassed by clients.
*
* @since 1.2
*/
public class SelectObservableValue<T> extends AbstractObservableValue<T> {
private class Option {
private final T value;
private final IObservableValue<Boolean> observable;
public Option(T value, IObservableValue<Boolean> observable) {
this.value = value;
this.observable = observable;
}
}
private final Object valueType;
private List<Option> options;
private int selectionIndex = -1; // n/a while not hasListeners()
private boolean updating = false;
private IValueChangeListener<Boolean> listener = new IValueChangeListener<Boolean>() {
public void handleValueChange(ValueChangeEvent<Boolean> event) {
if (!updating) {
IObservableValue<? extends Boolean> 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 ArrayList<Option>();
}
protected void firstListenerAdded() {
super.firstListenerAdded();
selectionIndex = indexOfValue(getLiveValue());
for (Option option : options) {
option.observable.addValueChangeListener(listener);
}
}
protected void lastListenerRemoved() {
for (Option option : options) {
option.observable.removeValueChangeListener(listener);
}
selectionIndex = -1;
super.lastListenerRemoved();
}
public Object getValueType() {
return valueType;
}
private void notifyIfChanged(int index) {
if (hasListeners() && selectionIndex != index) {
T oldValue = valueAtIndex(selectionIndex);
T 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(T value, IObservableValue<Boolean> observable) {
checkRealm();
Option option = new Option(value, observable);
options.add(option);
if (hasListeners()) {
observable.addValueChangeListener(listener);
if (Boolean.TRUE.equals(observable.getValue())) {
notifyIfChanged(indexOfObservable(observable));
}
}
}
protected T doGetValue() {
return hasListeners() ? valueAtIndex(selectionIndex) : getLiveValue();
}
private T getLiveValue() {
for (Option option : options) {
if (Boolean.TRUE.equals(option.observable.getValue()))
return option.value;
}
return null;
}
protected void doSetValue(Object value) {
int index = indexOfValue(value);
try {
updating = true;
for (int i = 0; i < options.size(); i++) {
options.get(i).observable.setValue(i == index ? Boolean.TRUE
: Boolean.FALSE);
}
} finally {
updating = false;
}
notifyIfChanged(index);
}
private T valueAtIndex(int index) {
if (index == -1)
return null;
return options.get(index).value;
}
private int indexOfValue(Object value) {
for (int i = 0; i < options.size(); i++)
if (Util.equals(options.get(i).value, value))
return i;
return -1;
}
private int indexOfObservable(IObservableValue<? extends Boolean> observable) {
for (int i = 0; i < options.size(); i++)
if (options.get(i).observable == observable)
return i;
return -1;
}
}