blob: dce9e617debf85ca9b0929cc6a5ed84bd2756ce2 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2005, 2009 IBM Corporation 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:
* IBM Corporation - initial API and implementation
* Brad Reynolds - bug 116920, 159768
* Matthew Hall - bug 260329
*******************************************************************************/
package org.eclipse.jface.tests.databinding.scenarios;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.text.ParseException;
import java.util.Date;
import java.util.Locale;
import org.eclipse.core.databinding.AggregateValidationStatus;
import org.eclipse.core.databinding.Binding;
import org.eclipse.core.databinding.UpdateValueStrategy;
import org.eclipse.core.databinding.beans.BeansObservables;
import org.eclipse.core.databinding.conversion.Converter;
import org.eclipse.core.databinding.conversion.IConverter;
import org.eclipse.core.databinding.conversion.NumberToStringConverter;
import org.eclipse.core.databinding.conversion.StringToNumberConverter;
import org.eclipse.core.databinding.observable.ChangeEvent;
import org.eclipse.core.databinding.observable.IChangeListener;
import org.eclipse.core.databinding.observable.value.IObservableValue;
import org.eclipse.core.databinding.validation.IValidator;
import org.eclipse.core.databinding.validation.ValidationStatus;
import org.eclipse.core.internal.databinding.conversion.IdentityConverter;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.databinding.swt.SWTObservables;
import org.eclipse.jface.examples.databinding.model.Account;
import org.eclipse.jface.examples.databinding.model.Adventure;
import org.eclipse.jface.examples.databinding.model.Cart;
import org.eclipse.jface.examples.databinding.model.SampleData;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.FocusListener;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Spinner;
import org.eclipse.swt.widgets.Text;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import com.ibm.icu.text.NumberFormat;
/**
* To run the tests in this class, right-click and select "Run As JUnit Plug-in
* Test". This will also start an Eclipse instance. To clean up the launch
* configuration, open up its "Main" tab and select "[No Application] - Headless
* Mode" as the application to run.
*/
public class PropertyScenarios extends ScenariosTestCase {
private Adventure adventure;
@Before
public void setUp() throws Exception {
super.setUp();
// do any setup work here
adventure = SampleData.WINTER_HOLIDAY;
}
@After
public void tearDown() throws Exception {
// do any teardown work here
super.tearDown();
}
@Test
public void testEnterText() {
// just to make sure enterText() generates a FocusOut event.
Text text = new Text(getComposite(), SWT.BORDER);
final boolean[] focusLostHolder = { false };
text.addFocusListener(new FocusListener() {
@Override
public void focusGained(FocusEvent e) {
// only interested in focus lost events
}
@Override
public void focusLost(FocusEvent e) {
focusLostHolder[0] = true;
}
});
enterText(text, "hallo");
assertTrue(focusLostHolder[0]);
}
@Test
public void testScenario01() {
Text text = new Text(getComposite(), SWT.BORDER);
getDbc().bindValue(SWTObservables.observeText(text, SWT.Modify),
BeansObservables.observeValue(adventure, "name"));
// getDbc().bind(text, new Property(adventure, "name"), null);
// uncomment the following line to see what's happening
// spinEventLoop(1);
assertEquals(adventure.getName(), text.getText());
enterText(text, "foobar");
// uncomment the following line to see what's happening
// spinEventLoop(1);
assertEquals("foobar", adventure.getName());
adventure.setName("barfoo");
// uncomment the following line to see what's happening
// spinEventLoop(1);
assertEquals("barfoo", text.getText());
}
@Test
public void testScenario02() {
// Binding the name property of an Adventure object to the contents of
// Text controls, no conversion, no validation. The Text widget editable
// is set to false.by the developer (can not change the name)
Text text = new Text(getComposite(), SWT.READ_ONLY);
getDbc().bindValue(SWTObservables.observeText(text, SWT.None),
BeansObservables.observeValue(adventure, "name"));
assertEquals(adventure.getName(), text.getText());
}
@Test
public void testScenario03() {
// Binding of a read-only property of an Adventure object to the
// contents of Text controls, no conversion, no validation. Text control
// is not editable as a side effect of binding to a read-only property..
Cart cart = SampleData.CART;
cart.setAdventureDays(42);
// bind to the lodgingDays feature, which is read-only and always one
// less than the number of adventure days.
Text text = new Text(getComposite(), SWT.BORDER);
System.out.println("Expecting message about not being able to attach a listener");
getDbc().bindValue(SWTObservables.observeText(text, SWT.Modify),
BeansObservables.observeValue(cart, "lodgingDays"));
assertEquals(Integer.valueOf(cart.getLodgingDays()).toString(), text.getText());
}
@Test
public void testScenario04() {
// Binding a nested property of an Adventure object to the content of a
// Text control, no conversion, no validation.
Text text = new Text(getComposite(), SWT.BORDER);
// TODO Scenario needs to be more specific - I'm binding to the default
// lodging's description of an adventure. What do we expect to happen
// when the default lodging changes? If we expect no change, then this
// scenario does not introduce anything new. If we expect the binding to
// be to the new default lodging's description, shouldn't we move this
// scenario to the master/detail section? I'm assuming the latter for
// now.
IObservableValue defaultLodging = BeansObservables.observeDetailValue(
BeansObservables.observeValue(adventure, "defaultLodging"),
"description", String.class);
getDbc().bindValue(SWTObservables.observeText(text, SWT.Modify), defaultLodging);
// test changing the description
assertEquals(adventure.getDefaultLodging().getDescription(), text.getText());
enterText(text, "foobar");
assertEquals("foobar", adventure.getDefaultLodging().getDescription());
adventure.getDefaultLodging().setDescription("barfoo");
assertEquals(adventure.getDefaultLodging().getDescription(), text.getText());
// test changing the default lodging
adventure.setDefaultLodging(SampleData.CAMP_GROUND);
assertEquals(adventure.getDefaultLodging().getDescription(), text.getText());
adventure.getDefaultLodging().setDescription("barfo");
assertEquals(adventure.getDefaultLodging().getDescription(), text.getText());
adventure.setDefaultLodging(null);
assertEquals("", text.getText());
adventure.setDefaultLodging(SampleData.FIVE_STAR_HOTEL);
assertEquals(adventure.getDefaultLodging().getDescription(), text.getText());
adventure.getDefaultLodging().setDescription("barf");
assertEquals(adventure.getDefaultLodging().getDescription(), text.getText());
}
@Test
public void testScenario05() {
// Binding the name property of an Adventure object to the contents of
// Text controls where conversion occurs � the model data is held all
// in
// uppercase and displayed in lowercase with the first letter
// capitalized.
Text text = new Text(getComposite(), SWT.BORDER);
adventure.setName("UPPERCASE");
IConverter converter1 = new IConverter() {
@Override
public Object getFromType() {
return String.class;
}
@Override
public Object getToType() {
return String.class;
}
@Override
public Object convert(Object toObject) {
String modelValue = (String) toObject;
if (modelValue == null || modelValue.equals("")) {
return modelValue;
}
String firstChar = modelValue.substring(0, 1);
String remainingChars = modelValue.substring(1);
return firstChar.toUpperCase() + remainingChars.toLowerCase();
}
};
IConverter converter2 = new IConverter() {
@Override
public Object getFromType() {
return String.class;
}
@Override
public Object getToType() {
return String.class;
}
@Override
public Object convert(Object fromObject) {
return ((String) fromObject).toUpperCase();
}
};
getDbc().bindValue(SWTObservables.observeText(text, SWT.Modify),
BeansObservables.observeValue(adventure, "name"),
new UpdateValueStrategy().setConverter(converter2), new UpdateValueStrategy().setConverter(converter1));
// spinEventLoop(1);
assertEquals("Uppercase", text.getText());
enterText(text, "lowercase");
// spinEventLoop(1);
// TODO If we wanted to "canonicalize" the value in the text field, how
// could we do that?
assertEquals("LOWERCASE", adventure.getName());
}
@Test
public void testScenario06() {
// Binding the name property of an Adventure object to the contents of
// Text controls where validation occurs and the name cannot be longer
// than 15 characters and cannot contain spaces
Text text = new Text(getComposite(), SWT.BORDER);
final String noSpacesMessage = "Name must not contain spaces.";
final String max15CharactersMessage = "Maximum length for name is 15 characters.";
adventure.setName("ValidValue");
IValidator validator = new IValidator() {
@Override
public IStatus validate(Object value) {
String stringValue = (String) value;
if (stringValue.length() > 15) {
return ValidationStatus.error(max15CharactersMessage);
} else if (stringValue.indexOf(' ') != -1) {
return ValidationStatus.cancel(noSpacesMessage);
} else {
return Status.OK_STATUS;
}
}
};
// BindSpec bindSpec = new DefaultBindSpec().setModelToTargetConverter(new IdentityConverter(String.class))
// .setTargetToModelConverter(new IdentityConverter(String.class))
// .addTargetValidator(BindingEvent.PIPELINE_VALUE_CHANGING, validator);
Binding binding = getDbc().bindValue(
SWTObservables.observeText(text, SWT.Modify),
BeansObservables.observeValue(adventure, "name"),
new UpdateValueStrategy().setConverter(new IdentityConverter(
String.class)).setAfterGetValidator(validator),
new UpdateValueStrategy().setConverter(new IdentityConverter(
String.class)));
// no validation message
assertTrue(((IStatus)binding.getValidationStatus().getValue()).isOK());
enterText(text, "Invalid Value");
assertEquals(noSpacesMessage, ((IStatus) binding.getValidationStatus().getValue()).getMessage());
assertEquals("ValidValue", adventure.getName());
text.setText("InvalidValueBecauseTooLong");
assertEquals(max15CharactersMessage,
((IStatus) binding.getValidationStatus().getValue()).getMessage());
assertEquals("ValidValue", adventure.getName());
enterText(text, "anothervalid");
assertTrue(((IStatus)binding.getValidationStatus().getValue()).isOK());
assertEquals("anothervalid", adventure.getName());
}
@Test
public void testScenario07() {
// Binding the price property of an Adventure to a Text control. Price
// is a double and Text accepts String so conversion will have to occur.
// Validation ensure that the value is positive
Text text = new Text(getComposite(), SWT.BORDER);
adventure.setPrice(5.0);
final String cannotBeNegativeMessage = "Price cannot be negative.";
final String mustBeCurrencyMessage = "Price must be a currency.";
IValidator validator = new IValidator() {
@Override
public IStatus validate(Object value) {
String stringValue = (String) value;
try {
double doubleValue = new Double(stringValue).doubleValue();
if (doubleValue < 0.0) {
return ValidationStatus.error(cannotBeNegativeMessage);
}
return Status.OK_STATUS;
} catch (NumberFormatException ex) {
return ValidationStatus.error(mustBeCurrencyMessage);
}
}
};
//Create a number formatter that will display one decimal position.
NumberFormat numberFormat = NumberFormat.getInstance();
numberFormat.setMinimumFractionDigits(1);
IConverter targetToModelConverter = StringToNumberConverter.toDouble(
numberFormat, true);
IConverter modelToTargetConverter = NumberToStringConverter.fromDouble(
numberFormat, true);
getDbc().bindValue(
SWTObservables.observeText(text, SWT.Modify),
BeansObservables.observeValue(adventure, "price"),
new UpdateValueStrategy().setAfterGetValidator(validator)
.setConverter(targetToModelConverter),
new UpdateValueStrategy().setConverter(modelToTargetConverter));
String expected = numberFormat.format(adventure.getPrice());
assertEquals(expected, text.getText());
assertTrue(AggregateValidationStatus.getStatusMaxSeverity(getDbc().getBindings()).isOK());
String toEnter = numberFormat.format(0.65);
enterText(text, toEnter);
assertTrue(AggregateValidationStatus.getStatusMaxSeverity(getDbc().getBindings()).isOK());
assertEquals(0.65, adventure.getPrice(), 0.0001);
adventure.setPrice(42.24);
expected = numberFormat.format(adventure.getPrice());
assertEquals(expected, text.getText());
assertTrue(AggregateValidationStatus.getStatusMaxSeverity(getDbc().getBindings()).isOK());
enterText(text, "jygt");
assertEquals(mustBeCurrencyMessage, AggregateValidationStatus.getStatusMaxSeverity(getDbc().getBindings()).getMessage());
toEnter = numberFormat.format(-23.9);
enterText(text, toEnter);
assertEquals(cannotBeNegativeMessage, AggregateValidationStatus.getStatusMaxSeverity(getDbc().getBindings()).getMessage());
assertEquals(42.24, adventure.getPrice(), 0.0001);
adventure.setPrice(0.0);
assertTrue(AggregateValidationStatus.getStatusMaxSeverity(getDbc().getBindings()).isOK());
}
@Test
public void testScenario08() {
// Binding the price property of an Adventure to a Text control but with
// custom conversion � the double will be validated to only have two
// decimal places and displayed with a leading currency symbol, and can
// be entered with or without the currency symbol.
Text text = new Text(getComposite(), SWT.BORDER);
adventure.setPrice(5.0);
final String cannotBeNegativeMessage = "Price cannot be negative.";
final String mustBeCurrencyMessage = "Price must be a currency.";
final NumberFormat currencyFormat = NumberFormat.getCurrencyInstance(Locale.CANADA);
IConverter toCurrency = new Converter(double.class, String.class) {
@Override
public Object convert(Object toObject) {
return currencyFormat.format(((Double) toObject).doubleValue());
}
};
IConverter toDouble = new Converter(String.class, double.class) {
@Override
public Object convert(Object fromObject) {
try {
return new Double(currencyFormat.parse((String) fromObject).doubleValue());
} catch (ParseException e) {
// TODO throw something like
// IllegalConversionException?
return new Double(0);
}
}
};
IValidator validator = new IValidator() {
@Override
public IStatus validate(Object value) {
String stringValue = (String) value;
try {
double doubleValue = currencyFormat.parse(stringValue).doubleValue();
if (doubleValue < 0.0) {
return ValidationStatus.error(cannotBeNegativeMessage);
}
return Status.OK_STATUS;
} catch (ParseException e) {
return ValidationStatus.error(mustBeCurrencyMessage);
}
}
};
getDbc().bindValue(SWTObservables.observeText(text, SWT.Modify),
BeansObservables.observeValue(adventure, "price"),
new UpdateValueStrategy().setConverter(toDouble).setAfterGetValidator(validator),new UpdateValueStrategy().setConverter(toCurrency));
String expected = currencyFormat.format(5);
assertEquals(expected, text.getText());
assertTrue(AggregateValidationStatus.getStatusMaxSeverity(getDbc().getBindings()).isOK());
String toEnter = currencyFormat.format(0.65);
enterText(text, toEnter);
assertTrue(AggregateValidationStatus.getStatusMaxSeverity(getDbc().getBindings()).isOK());
assertEquals(0.65, adventure.getPrice(), 0.0001);
adventure.setPrice(42.24);
expected = currencyFormat.format(adventure.getPrice());
assertEquals(expected, text.getText());
assertTrue(AggregateValidationStatus.getStatusMaxSeverity(getDbc().getBindings()).isOK());
enterText(text, "jygt");
assertEquals(mustBeCurrencyMessage, AggregateValidationStatus.getStatusMaxSeverity(getDbc().getBindings()).getMessage());
toEnter = currencyFormat.format(-23.9);
enterText(text, toEnter);
assertEquals(cannotBeNegativeMessage, AggregateValidationStatus.getStatusMaxSeverity(getDbc().getBindings()).getMessage());
assertEquals(42.24, adventure.getPrice(), 0.0001);
adventure.setPrice(0.0);
assertTrue(AggregateValidationStatus.getStatusMaxSeverity(getDbc().getBindings()).isOK());
}
@Test
public void testScenario09() {
// Binding a boolean property to a CheckBox. Adventure will have a
// Boolean property �petsAllowed�
Button checkbox = new Button(getComposite(), SWT.CHECK);
// checkbox.setText("Pets allowed");
// checkbox.setLayoutData(new GridData(SWT.LEFT,SWT.TOP, false,false));
adventure.setPetsAllowed(true);
getDbc().bindValue(SWTObservables.observeSelection(checkbox),
BeansObservables.observeValue(adventure, "petsAllowed"));
assertEquals(true, checkbox.getSelection());
setButtonSelectionWithEvents(checkbox, false);
assertEquals(false, adventure.isPetsAllowed());
adventure.setPetsAllowed(true);
assertEquals(true, checkbox.getSelection());
}
@Test
public void testScenario10() {
// Binding a Transportation departure time to a Text control that
// formats and validates the time to and from a String. There are
// property bindings that bind elements of the GUI to elements to GUI
// and also elements of the domain to elements of the domain
// TODO fail("not implemented");
}
@Test
public void testScenario11() {
// Binding the max value of a spinner to another spinner.
Spinner spinner1 = new Spinner(getComposite(), SWT.NONE);
spinner1.setSelection(10);
spinner1.setMinimum(1);
spinner1.setMaximum(100);
Spinner spinner2 = new Spinner(getComposite(), SWT.NONE);
spinner2.setMaximum(1);
getDbc().bindValue(SWTObservables.observeSelection(spinner1), SWTObservables.observeMax(spinner2));
assertEquals(1, spinner1.getSelection());
spinner1.setSelection(10);
spinner1.notifyListeners(SWT.Modify, new Event());
assertEquals(10, spinner2.getMaximum());
}
@Test
public void testScenario12() {
// Binding the enabled state of several Text controls to a check box.
// There will be two check boxes, so as each is enabled/disabled the
// other one follows as do the states of the Text controls.
Button checkbox1 = new Button(getComposite(), SWT.CHECK);
checkbox1.setSelection(false);
Button checkbox2 = new Button(getComposite(), SWT.CHECK);
checkbox2.setSelection(false);
Text text1 = new Text(getComposite(), SWT.NONE);
Text text2 = new Text(getComposite(), SWT.NONE);
IObservableValue checkbox1Selected = SWTObservables.observeSelection(checkbox1);
IObservableValue checkbox2Selected = SWTObservables.observeSelection(checkbox2);
// bind the two checkboxes so that if one is checked, the other is not
// and vice versa.
Converter negatingConverter = new Converter(boolean.class, boolean.class) {
private Boolean negated(Boolean booleanObject) {
return Boolean.valueOf(!booleanObject.booleanValue());
}
@Override
public Object convert(Object targetObject) {
return negated((Boolean) targetObject);
}
};
getDbc().bindValue(checkbox1Selected,
checkbox2Selected,new UpdateValueStrategy().setConverter(negatingConverter),
new UpdateValueStrategy().setConverter(negatingConverter));
// bind the enabled state of the two text widgets to one of the
// checkboxes each.
getDbc().bindValue(SWTObservables.observeEnabled(text1), checkbox1Selected);
getDbc().bindValue(SWTObservables.observeEnabled(text2), checkbox2Selected);
assertEquals(true, text1.getEnabled());
assertEquals(false, text2.getEnabled());
assertEquals(true, checkbox1.getSelection());
setButtonSelectionWithEvents(checkbox1, false);
assertEquals(false, text1.getEnabled());
assertEquals(true, text2.getEnabled());
assertEquals(true, checkbox2.getSelection());
setButtonSelectionWithEvents(checkbox2, false);
assertEquals(true, text1.getEnabled());
assertEquals(false, text2.getEnabled());
assertEquals(true, checkbox1.getSelection());
}
@Test
public void testScenario13() {
Text text = new Text(getComposite(), SWT.BORDER);
getDbc().bindValue(SWTObservables.observeText(text, SWT.FocusOut), BeansObservables.observeValue(adventure, "name"));
// uncomment the following line to see what's happening
// happening
// spinEventLoop(1);
String adventureName = adventure.getName();
assertEquals(adventureName, text.getText());
enterText(text, "foobar");
// uncomment the following line to see what's happening
// spinEventLoop(1);
assertEquals("foobar", adventure.getName());
adventure.setName("barfoo");
// uncomment the following line to see what's happening
// spinEventLoop(1);
assertEquals("barfoo", text.getText());
}
@Test
public void testScenario14() {
Text t1 = new Text(getComposite(), SWT.BORDER);
Text t2 = new Text(getComposite(), SWT.BORDER);
getDbc().bindValue(SWTObservables.observeText(t1, SWT.Modify), BeansObservables.observeValue(adventure, "name"));
getDbc().bindValue(SWTObservables.observeText(t2, SWT.Modify), BeansObservables.observeValue(adventure, "name"));
final int[] counter = { 0 };
IObservableValue uv = BeansObservables.observeValue(adventure, "name");
uv.addChangeListener(new IChangeListener() {
@Override
public void handleChange(ChangeEvent event) {
// Count how many times adventure has changed
counter[0]++;
}
});
String name = adventure.getName() + "Foo";
enterText(t1, name);
assertEquals(name, adventure.getName());
assertEquals(name, t2.getText());
assertTrue(counter[0] == 1);
name = name + "Bar";
uv.setValue(name);
assertEquals(t1.getText(), adventure.getName());
assertEquals(2, counter[0]);
}
@Test
public void testScenario15() {
Text text = new Text(getComposite(), SWT.NONE);
Account account = new Account();
account.setExpiryDate(new Date());
Binding b = getDbc().bindValue(SWTObservables.observeText(text, SWT.Modify), BeansObservables.observeValue(account, "expiryDate"));
Text errorText = new Text(getComposite(), SWT.NONE);
getDbc().bindValue(SWTObservables.observeText(errorText, SWT.Modify), b.getValidationStatus(), new UpdateValueStrategy(false, UpdateValueStrategy.POLICY_NEVER), null);
assertTrue(((IStatus)b.getValidationStatus().getValue()).isOK());
enterText(text, "foo");
assertFalse(((IStatus)b.getValidationStatus().getValue()).isOK());
}
}