/******************************************************************************* * Copyright (c) 2006-2013 The RCP Company 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: * The RCP Company - initial API and implementation *******************************************************************************/ package com.rcpcompany.uibindings.observables; import java.util.List; import org.eclipse.core.databinding.observable.ChangeEvent; import org.eclipse.core.databinding.observable.Diffs; import org.eclipse.core.databinding.observable.IChangeListener; import org.eclipse.core.databinding.observable.IObserving; import org.eclipse.core.databinding.observable.value.AbstractObservableValue; import org.eclipse.core.databinding.observable.value.IObservableValue; import org.eclipse.core.runtime.Assert; import org.eclipse.emf.common.command.CompoundCommand; import org.eclipse.emf.common.notify.Adapter; import org.eclipse.emf.common.notify.Notification; import org.eclipse.emf.common.notify.impl.AdapterImpl; import org.eclipse.emf.common.util.EList; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EReference; import org.eclipse.emf.ecore.EStructuralFeature; import org.eclipse.emf.edit.command.AddCommand; import org.eclipse.emf.edit.command.SetCommand; import org.eclipse.emf.edit.domain.EditingDomain; import com.rcpcompany.uibindings.IManager; import com.rcpcompany.uibindings.model.utils.BasicUtils; import com.rcpcompany.uibindings.utils.EditingDomainUtils; /** * Basic {@link IObservableValue observable value} for an element in an EMF {@link EList}. * <p> * Can be configured to "auto extend" the list to the correct size (only happens on set) - if the * list is not {@link EReference#isUnique() "unique"} - by adding <code>null</code> values * (defaults) to <code>false</code>. * * @author Tonny Madsen, The RCP Company */ public class EListElementObservableValue extends AbstractObservableValue implements IKeyedObservable, IObserving { private final IObservableValue myObjectOV; private final EStructuralFeature mySF; private final int myIndex; private final EditingDomain myEditingDomain; /** * Whether the list is automatically extended as needed. */ private boolean myExpendList = false; private EObject myObject = null; private final IChangeListener myObjectOVListener = new IChangeListener() { @Override public void handleChange(ChangeEvent event) { updateValue(); } }; private final Adapter myAdapter = new AdapterImpl() { @Override public void notifyChanged(Notification msg) { if (msg.isTouch()) return; updateValue(); }; }; /** * The current value of the observable value. */ private Object myValue; /** * Constructs and returns a new {@link IObservableValue} for the specified element in the list * of the object. * <p> * The default editing domain of the {@link IManager} if used. * * @param ov observable value with the {@link EObject} * @param sf the structural feature with the list * @param index the element of the list */ public EListElementObservableValue(IObservableValue ov, EStructuralFeature sf, int index) { this(EditingDomainUtils.getEditingDomain(), ov, sf, index); } /** * Constructs and returns a new {@link IObservableValue} for the specified element in the list * of the object. * * @param editingDomain the editing domain to use * @param ov observable value with the {@link EObject} * @param sf the structural feature with the list * @param index the element of the list */ public EListElementObservableValue(EditingDomain editingDomain, IObservableValue ov, EStructuralFeature sf, int index) { myEditingDomain = editingDomain; myObjectOV = ov; mySF = sf; myIndex = index; Assert.isTrue(mySF.isMany()); myObjectOV.addChangeListener(myObjectOVListener); IManager.Factory.getManager().startMonitorObservableDispose(myObjectOV, this); updateValue(); } @Override public Object getObserved() { return myObject; } @Override public Object getObservableKey() { return myIndex; } public void updateValue() { getRealm().exec(new Runnable() { @Override public void run() { Object value = null; try { final EObject newObject = (EObject) myObjectOV.getValue(); if (myObject != newObject) { if (myObject != null) { myObject.eAdapters().remove(myAdapter); } myObject = newObject; if (myObject != null) { myObject.eAdapters().add(myAdapter); } } if (myObject == null) return; final EList<?> list = (EList<?>) myObject.eGet(mySF); if (list == null) return; if (list.size() <= myIndex) return; value = list.get(myIndex); } finally { if (BasicUtils.equals(value, myValue)) return; fireValueChange(Diffs.createValueDiff(myValue, myValue = value)); } } }); } @Override public synchronized void dispose() { IManager.Factory.getManager().stopMonitorObservableDispose(myObjectOV); if (myObjectOV != null) { myObjectOV.removeChangeListener(myObjectOVListener); } if (hasListeners()) { lastListenerRemoved(); } super.dispose(); } @Override protected void firstListenerAdded() { super.firstListenerAdded(); } @Override protected void lastListenerRemoved() { super.lastListenerRemoved(); } @Override protected final Object doGetValue() { return myValue; } @Override protected void doSetValue(final Object value) { final CompoundCommand cc = new CompoundCommand(); if (isExpendList() && !mySF.isUnique()) { final List<?> l = (List<?>) myObject.eGet(mySF); /* * Missing elements, if possible */ final int missing = myIndex + 1 - l.size(); for (int i = 0; i < missing; i++) { cc.append(new AddCommand(myEditingDomain, myObject, mySF, (Object) null)); } } cc.append(new SetCommand(myEditingDomain, myObject, mySF, value, myIndex)); myEditingDomain.getCommandStack().execute(cc.unwrap()); } @Override public Object getValueType() { return mySF.getEType(); } /** * Sets whether the list is automatically extended as needed. * * @param expendList <code>true</code> if the list should be extended automatically */ public void setExpendList(boolean myExpendList) { this.myExpendList = myExpendList; } /** * Returns whether the list is automatically extended as needed. * * @return <code>true</code> if the list is automatically extended */ public boolean isExpendList() { return myExpendList; } }