/*******************************************************************************
* 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.internal.utils;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.core.databinding.observable.value.IObservableValue;
import org.eclipse.core.databinding.observable.value.IValueChangeListener;
import org.eclipse.core.databinding.observable.value.ValueChangeEvent;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Layout;
import com.rcpcompany.uibindings.BindingState;
import com.rcpcompany.uibindings.IBindingContext;
import com.rcpcompany.uibindings.IBindingContext.FinishOption;
import com.rcpcompany.uibindings.observables.DisposePendingWritableValue;
import com.rcpcompany.uibindings.utils.IFormChooser;
import com.rcpcompany.uibindings.utils.IFormChooserCreator;
import com.rcpcompany.utils.logging.LogUtils;
/**
* The implementation of {@link IFormChooser}.
*
* @author Tonny Madsen, The RCP Company
*/
public class FormChooser implements IFormChooser {
/**
* The contaxt passed on to the choosers.
*/
protected final IBindingContext myContext;
/**
* The discriminant {@link IObservableValue}.
*/
protected final IObservableValue myDiscriminant;
/**
* Proxy for {@link #myDiscriminant}, that prevents propergation of dispose.
*/
protected DisposePendingWritableValue myChildDiscriminant;
/**
* The top {@link Composite} for all children created by the choosers.
*/
protected final Composite myTop;
private final IFormChooserCreator theDummyCreator = new IFormChooserCreator() {
@Override
public void createForm(IBindingContext context, IObservableValue discriminant, Composite parent) {
}
};
/**
* The current creator.
*/
protected IFormChooserCreator myCurrentCreator = theDummyCreator;
/**
* Map with all registered form creators.
*/
private final Map<IFormChooserTester, IFormChooserCreator> myMap = new HashMap<IFormChooserTester, IFormChooserCreator>();
/**
* Dispose listener.
*/
private final DisposeListener myDisposeListener = new DisposeListener() {
@Override
public void widgetDisposed(DisposeEvent e) {
dispose();
}
};
/**
* Constructs and returns a new form creator.
*
* @param context the context
* @param discriminant the discriminant used to decide on the chosen form
* @param top the top level {@link Composite} that will parent all forms created by the chooser
*/
public FormChooser(IBindingContext context, IObservableValue discriminant, Composite top) {
myContext = context;
myDiscriminant = discriminant;
myTop = top;
myTop.setLayout(new FillLayout());
myDiscriminant.addValueChangeListener(myValueListener);
myTop.addDisposeListener(myDisposeListener);
updateChild();
}
@Override
public void dispose() {
myDiscriminant.removeValueChangeListener(myValueListener);
if (!myTop.isDisposed()) {
removeChild();
myTop.removeDisposeListener(myDisposeListener);
}
}
private final IValueChangeListener myValueListener = new IValueChangeListener() {
@Override
public void handleValueChange(ValueChangeEvent event) {
updateChild();
}
};
/**
* Finds and returns the form creator to use based on the current value of the discriminant.
*
* @return the form creator or <code>null</code>
*/
protected IFormChooserCreator findCreator() {
final Object value = myDiscriminant.getValue();
for (final Map.Entry<IFormChooserTester, IFormChooserCreator> t : myMap.entrySet()) {
if (t.getKey().isSelected(value)) return t.getValue();
}
return null;
}
/**
* Updates the sole child of the top Composite based on the current creator.
*/
protected void updateChild() {
final IFormChooserCreator creator = findCreator();
if (creator == myCurrentCreator) {
if (myChildDiscriminant != null) {
myChildDiscriminant.setValue(myDiscriminant.getValue());
}
return;
}
myTop.setLayoutDeferred(true);
removeChild();
myCurrentCreator = creator;
if (myCurrentCreator != null) {
try {
myChildDiscriminant = DisposePendingWritableValue.withValueType(myDiscriminant.getValueType());
myChildDiscriminant.setValue(myDiscriminant.getValue());
myCurrentCreator.createForm(myContext, myChildDiscriminant, myTop);
} catch (final Exception ex) {
LogUtils.error(myCurrentCreator, ex);
}
myContext.finish(FinishOption.IF_ALREADY_FINISHED);
/*
* Common mistake: the layout data is not null or a FillData!
*
* Note: FillData is package protected - therefore the getClass... below
*/
final Control[] children = myTop.getChildren();
switch (children.length) {
case 0:
break;
case 1:
final Object layoutData = children[0].getLayoutData();
if (layoutData != null && !layoutData.getClass().getName().equals("org.eclipse.swt.layout.FillData")) {
children[0].setLayoutData(null);
LogUtils.debug(myCurrentCreator, "Child has layout ('" + layoutData + "'). Removed!");
}
break;
default:
LogUtils.throwException(myCurrentCreator, "Creator created multiple children", null);
}
} else {
createEmptyComposite();
}
myTop.setLayoutDeferred(false);
if (myContext != null && myContext.getState() == BindingState.OK) {
myContext.reflow();
}
myTop.update();
/*
* Hmmm?
*
* We make sure all outstanding asyncExecs are executed at this point...
*/
// final Listener l = new Listener() {
// @Override
// public void handleEvent(Event event) {
// LogUtils.debug(this, ToStringUtils.toString(event));
// }
// };
// for (int i = SWT.None; i < SWT.ImeComposition; i++) {
// Display.getCurrent().addFilter(i, l);
// }
// while (myTop.getDisplay().readAndDispatch()) {
// // Do nothing
// LogUtils.debug(this, "readAndDispatch");
// }
// for (int i = SWT.None; i < SWT.ImeComposition; i++) {
// Display.getCurrent().removeFilter(i, l);
// }
// LogUtils.debug(this, "readAndDispatch ok");
}
private void createEmptyComposite() {
// final Label c = new Label(myTop, SWT.NONE);
// c.setText("");
final Composite c = new Composite(myTop, SWT.NONE);
c.setSize(new Point(1, 1));
// c.setBackground(c.getDisplay().getSystemColor(SWT.COLOR_BLUE));
c.setLayout(new Layout() {
@Override
protected void layout(Composite composite, boolean flushCache) {
}
@Override
protected Point computeSize(Composite composite, int wHint, int hHint, boolean flushCache) {
return new Point(1, 1);
}
});
}
/**
* Removes all children from the top composite.
*/
protected void removeChild() {
try {
myTop.setRedraw(false);
final Control[] children = myTop.getChildren();
for (int i = children.length - 1; i >= 0; i--) {
children[i].dispose();
}
} finally {
myTop.setRedraw(true);
}
myCurrentCreator = null;
if (myChildDiscriminant != null && !myChildDiscriminant.isDisposed()) {
myChildDiscriminant.fireDisposePending();
}
if (myChildDiscriminant != null && !myChildDiscriminant.isDisposed()) {
myChildDiscriminant.dispose();
myChildDiscriminant = null;
}
}
@Override
public void addFormInstanceof(final Class<?> clz, IFormChooserCreator creator) {
addForm(new IFormChooserTester() {
@Override
public boolean isSelected(Object value) {
return clz.isInstance(value);
}
}, creator);
}
@Override
public void addFormEClass(final EClass clz, IFormChooserCreator creator) {
addFormInstanceof(clz.getInstanceClass(), creator);
}
@Override
public void addFormExactEClass(final EClass clz, IFormChooserCreator creator) {
addForm(new IFormChooserTester() {
@Override
public boolean isSelected(Object value) {
if (value == null) return false;
if (!(value instanceof EObject)) return false;
return clz == ((EObject) value).eClass();
}
}, creator);
}
@Override
public void addFormValue(final Object value, IFormChooserCreator creator) {
addForm(new IFormChooserTester() {
@Override
public boolean isSelected(Object v) {
return value == null ? v == null : value.equals(v);
}
}, creator);
}
@Override
public void addForm(IFormChooserTester tester, IFormChooserCreator creator) {
myMap.put(tester, creator);
updateChild();
}
}