/*-
* Copyright © 2013 Diamond Light Source Ltd.
*
* This file is part of GDA.
*
* GDA is free software: you can redistribute it and/or modify it under the
* terms of the GNU General Public License version 3 as published by the Free
* Software Foundation.
*
* GDA is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along
* with GDA. If not, see <http://www.gnu.org/licenses/>.
*/
package uk.ac.gda.ui.components;
import java.net.URL;
import java.text.DecimalFormat;
import org.apache.commons.beanutils.PropertyUtils;
import org.eclipse.core.databinding.Binding;
import org.eclipse.core.databinding.DataBindingContext;
import org.eclipse.core.databinding.UpdateValueStrategy;
import org.eclipse.core.databinding.beans.BeanProperties;
import org.eclipse.core.databinding.conversion.IConverter;
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.core.databinding.validation.IValidator;
import org.eclipse.core.databinding.validation.ValidationStatus;
import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.jface.databinding.fieldassist.ControlDecorationSupport;
import org.eclipse.jface.databinding.swt.ISWTObservableValue;
import org.eclipse.jface.databinding.swt.WidgetProperties;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StackLayout;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.FocusListener;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.events.MouseTrackListener;
import org.eclipse.swt.events.TraverseEvent;
import org.eclipse.swt.events.TraverseListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Cursor;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Text;
import org.osgi.framework.Bundle;
import org.osgi.framework.FrameworkUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import uk.ac.gda.beans.ObservableModel;
public class NumberEditorControl extends Composite {
private static final Logger logger = LoggerFactory.getLogger(NumberEditorControl.class);
public static final String EDITABLE_PROP_NAME = "editable";
private static final String ICONS_PATH = "icons/";
private static final int LARGE_INCREMENT_WIDTH_PADDING = 6;
private static final int MIN_STEP_LABEL_WIDTH = 43;
protected static final int DEFAULT_DECIMAL_PLACES = 3;
protected Object targetObject;
private String propertyName;
private Label numberLabel;
private Control incrementButton;
private Control decrementButton;
private Image upArrowIcon;
private Image downArrowIcon;
private final StackLayout layout;
private Composite editorComposite;
private Text stepText;
private NumberEditorText numberEditorText;
private Binding numberLabelValueBinding;
private boolean commitOnOutOfFocus = true;
protected NumberEditorWidgetModel controlModel;
protected final DataBindingContext ctx = new DataBindingContext();
private final boolean useSpinner;
protected boolean horizonalSpinner;
private StackLayout stepLayout;
private Label incrementText;
private boolean isEditing;
private Composite incrementComposite;
private Binding incrementTextBinding;
private Binding incrementLabelBinding;
private Color disabledColor;
private Color enabledColor;
private static final double INITIAL_STEP = (int) (0.1 * Math.pow(10, DEFAULT_DECIMAL_PLACES));
private final StepListener incrementStepListener = new StepListener(true);
private final StepListener decrementStepListener = new StepListener(false);
private boolean binded;
private Binding decrementButtonEditableBinding;
private Binding incrementButtonEditableBinding;
private IConverter modelToTargetConverter;
private IConverter targetToModelConverter;
private IValidator modelToTargetValidator;
private IValidator targetToModelValidator;
public NumberEditorControl(final Composite parent, int style, Object targetObject, String propertyName, boolean useSpinner) throws Exception {
this(parent, style, targetObject, propertyName, useSpinner, false);
}
public NumberEditorControl(final Composite parent, int style, Object targetObject, String propertyName, boolean useSpinner,
boolean horizonalSpinner) throws Exception {
super(parent, style);
this.horizonalSpinner = horizonalSpinner;
this.useSpinner = useSpinner;
layout = new StackLayout();
this.setLayout(layout);
setupControls();
if (targetObject != null & propertyName != null) {
setModel(targetObject, propertyName);
}
if (this.useSpinner) {
setupIncrementCompWidthHint();
}
this.setTabList(new Control[]{editorComposite});
}
public NumberEditorControl(final Composite parent, int style, boolean useSpinner) throws Exception {
this(parent, style, null, null, useSpinner);
}
private NumberEditorWidgetModel createModel() throws Exception {
NumberEditorWidgetModel numberEditorWidgetModel = new NumberEditorWidgetModel();
try {
Class<?> objectType = PropertyUtils.getPropertyType(targetObject, propertyName);
if (objectType.equals(double.class)) {
numberEditorWidgetModel.setBindingPropertyType(objectType);
numberEditorWidgetModel.setDigits(DEFAULT_DECIMAL_PLACES);
numberEditorWidgetModel.setIncrement(INITIAL_STEP);
} else if (objectType.equals(int.class)) {
numberEditorWidgetModel.setBindingPropertyType(objectType);
numberEditorWidgetModel.setIncrement(1);
} else {
throw new Exception("Unsupported property type");
}
} catch (Exception e) {
throw new Exception("Unable to process the perperty", e);
}
return numberEditorWidgetModel;
}
private void bind() {
IObservableValue objectValue = BeanProperties.value(propertyName).observe(targetObject);
ISWTObservableValue textValue = WidgetProperties.text().observe(numberLabel);
UpdateValueStrategy modelToTargetUpdateValueStrategy = new UpdateValueStrategy() {
@Override
public Object convert(Object value) {
if (modelToTargetConverter != null) {
value = modelToTargetConverter.convert(value);
}
return getFormattedText(value);
}
};
if (modelToTargetValidator != null) {
modelToTargetUpdateValueStrategy.setBeforeSetValidator(modelToTargetValidator);
}
numberLabelValueBinding = ctx.bindValue(textValue, objectValue, null, modelToTargetUpdateValueStrategy);
if (useSpinner) {
incrementLabelBinding = ctx.bindValue(
WidgetProperties.text().observe(incrementText),
BeanProperties.value(NumberEditorWidgetModel.INCREMENT_PROP_NAME).observe(controlModel),
new UpdateValueStrategy() {
@Override
public Object convert(Object value) {
if (targetToModelConverter != null) {
value = targetToModelConverter.convert(value);
}
if (controlModel.getBindingPropertyType().equals(double.class)) {
return (((Number) value).doubleValue() * (int) Math.pow(10, controlModel.getDigits()));
}
return super.convert(value);
}
}, new UpdateValueStrategy() {
@Override
public Object convert(Object value) {
if (modelToTargetConverter != null) {
value = modelToTargetConverter.convert(value);
}
if (controlModel.getBindingPropertyType().equals(double.class)) {
double incrementValue = controlModel.getIncrement() / Math.pow(10, controlModel.getDigits());
return roundDoubletoString(incrementValue, controlModel.getDigits());
}
return Integer.toString((int) Math.round((Double) value));
}
});
decrementButtonEditableBinding = ctx.bindValue(
WidgetProperties.enabled().observe(decrementButton),
BeanProperties.value(EDITABLE_PROP_NAME).observe(controlModel));
incrementButtonEditableBinding = ctx.bindValue(
WidgetProperties.enabled().observe(incrementButton),
BeanProperties.value(EDITABLE_PROP_NAME).observe(controlModel));
incrementText.addListener(SWT.MouseUp, openStepEditorListener);
incrementText.addMouseTrackListener(editableMouseListener);
incrementText.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_WHITE));
incrementButton.addListener(SWT.MouseUp, incrementStepListener);
incrementButton.addMouseListener(incrementEnabledListener);
incrementButton.addMouseTrackListener(editableIncrementMouseListener);
decrementButton.addListener(SWT.MouseUp, decrementStepListener);
decrementButton.addMouseListener(incrementEnabledListener);
decrementButton.addMouseTrackListener(editableIncrementMouseListener);
}
numberLabel.addMouseTrackListener(editableMouseListener);
numberLabel.addListener(SWT.MouseUp, openEditorListener);
numberLabel.setBackground(enabledColor);
binded = true;
}
private void unbind() {
if (controlModel == null) {
return;
}
if (numberLabelValueBinding!=null && !numberLabelValueBinding.isDisposed()) {
numberLabelValueBinding.dispose();
ctx.removeBinding(numberLabelValueBinding);
}
if (useSpinner) {
if (incrementLabelBinding != null && !incrementLabelBinding.isDisposed()) {
incrementLabelBinding.dispose();
ctx.removeBinding(incrementLabelBinding);
}
if (decrementButtonEditableBinding != null && !decrementButtonEditableBinding.isDisposed()) {
decrementButtonEditableBinding.dispose();
ctx.removeBinding(decrementButtonEditableBinding);
}
if (incrementButtonEditableBinding != null && !incrementButtonEditableBinding.isDisposed()) {
incrementButtonEditableBinding.dispose();
ctx.removeBinding(incrementButtonEditableBinding);
}
incrementText.removeListener(SWT.MouseUp, openStepEditorListener);
incrementText.removeMouseTrackListener(editableMouseListener);
incrementText.setBackground(disabledColor);
incrementButton.removeListener(SWT.MouseUp, incrementStepListener);
incrementButton.removeMouseListener(incrementEnabledListener);
incrementButton.removeMouseTrackListener(editableIncrementMouseListener);
decrementButton.removeListener(SWT.MouseUp, decrementStepListener);
decrementButton.removeMouseListener(incrementEnabledListener);
decrementButton.removeMouseTrackListener(editableIncrementMouseListener);
incrementText.setText("");
}
numberLabel.removeMouseTrackListener(editableMouseListener);
numberLabel.removeListener(SWT.MouseUp, openEditorListener);
numberLabel.setText("");
numberLabel.setBackground(disabledColor);
binded = false;
}
public void setModel(Object targetObject, String propertyName) throws Exception {
if (binded) {
unbind();
controlModel = null;
this.targetObject = null;
this.propertyName = null;
}
if (targetObject != null & propertyName != null) {
this.targetObject = targetObject;
this.propertyName = propertyName;
controlModel = createModel();
bind();
}
}
@Override
public void dispose() {
if (upArrowIcon != null && !upArrowIcon.isDisposed()) {
upArrowIcon.dispose();
}
if (downArrowIcon != null && !downArrowIcon.isDisposed()) {
downArrowIcon.dispose();
}
if (disabledColor != null && !disabledColor.isDisposed()) {
disabledColor.dispose();
}
ctx.dispose();
super.dispose();
}
public void setRange(int minValue, int maxValue) {
controlModel.setMinValue(minValue);
controlModel.setMaxValue(maxValue);
}
public void setRange(double minValue, double maxValue) {
controlModel.setMinValue(minValue);
controlModel.setMaxValue(maxValue);
}
public Number getMaxValue(){
return controlModel.getMaxValue();
}
public Number getMinValue(){
return controlModel.getMinValue();
}
public void setDigits(int value) throws NumberFormatException {
if (!controlModel.getBindingPropertyType().equals(double.class)) {
throw new NumberFormatException("Invalid data type set to digits");
}
controlModel.setDigits(value);
numberLabelValueBinding.updateModelToTarget();
if (useSpinner) {
incrementLabelBinding.updateModelToTarget();
}
}
public void setIncrement(int value) throws Exception {
if (!useSpinner) {
throw new Exception("Increment spinner is not used");
}
controlModel.setIncrement(value);
}
public static final String UNIT_PROP_NAME = "unit";
public void setUnit(String value) {
controlModel.setUnit(value);
numberLabelValueBinding.updateModelToTarget();
if (useSpinner) {
incrementLabelBinding.updateModelToTarget();
}
}
@Override
public void setToolTipText(String toolTopText) {
numberLabel.setToolTipText(toolTopText);
}
public void setConverters(IConverter modelToTargetConverter, IConverter targetToModelConverter) {
this.modelToTargetConverter = modelToTargetConverter;
this.targetToModelConverter = targetToModelConverter;
}
public void setValidators(IValidator modelToTargetValidator, IValidator targetToModelValidator) {
this.modelToTargetValidator = modelToTargetValidator;
this.targetToModelValidator = targetToModelValidator;
}
public boolean isEditable() {
return controlModel.isEditable();
}
public void setEditable(boolean value) {
controlModel.setEditable(value);
if (!numberLabel.isDisposed()) {
Color color = controlModel.isEditable() ? Display.getDefault().getSystemColor(SWT.COLOR_WHITE) : disabledColor;
numberLabel.setBackground(color);
}
}
public boolean isEditing() {
return isEditing;
}
public boolean isCommitOnOutOfFocus() {
return commitOnOutOfFocus;
}
public void setCommitOnOutOfFocus(boolean commitOnOutOfFocus) {
this.commitOnOutOfFocus = commitOnOutOfFocus;
}
private final MouseListener incrementEnabledListener = new MouseListener() {
private Color color;
@Override
public void mouseUp(MouseEvent e) {
if (controlModel.isEditable()) {
((Control) e.getSource()).setBackground(color);
}
}
@Override
public void mouseDown(MouseEvent e) {
if (controlModel.isEditable()) {
color = ((Control) e.getSource()).getBackground();
((Control) e.getSource()).setBackground(Display.getDefault().getSystemColor(SWT.COLOR_DARK_GRAY));
}
}
@Override
public void mouseDoubleClick(MouseEvent e) {}
};
private final MouseTrackListener editableMouseListener = new MouseTrackListener() {
private Cursor cursor;
@Override
public void mouseHover(MouseEvent e) {}
@Override
public void mouseExit(MouseEvent e) {
if (cursor != null) {
((Control) e.getSource()).setCursor(cursor);
}
}
@Override
public void mouseEnter(MouseEvent e) {
if (!controlModel.isEditable()) {
return;
}
cursor = ((Control) e.getSource()).getCursor();
((Control) e.getSource()).setCursor(Display.getDefault().getSystemCursor(SWT.CURSOR_HAND));
}
};
private final MouseTrackListener editableIncrementMouseListener = new MouseTrackListener() {
private Cursor cursor;
@Override
public void mouseHover(MouseEvent e) {}
@Override
public void mouseExit(MouseEvent e) {
if (cursor != null) {
((Control) e.getSource()).setCursor(cursor);
}
}
@Override
public void mouseEnter(MouseEvent e) {
if (!controlModel.isEditable()) {
cursor = ((Control) e.getSource()).getCursor();
((Control) e.getSource()).setCursor(Display.getDefault().getSystemCursor(SWT.CURSOR_NO));
}
}
};
protected void setupControls() {
disabledColor = new Color (this.getDisplay(), 230, 230, 230);
enabledColor = this.getDisplay().getSystemColor(SWT.COLOR_WHITE);
editorComposite = new Composite(this, SWT.None);
editorComposite.setLayoutData(new GridData(GridData.FILL, GridData.CENTER, true, false));
int columns = (useSpinner) ? 3 : 1;
GridLayout grid = new GridLayout(columns, false);
removeMargins(grid);
editorComposite.setLayout(grid);
numberLabel = new Label(editorComposite, SWT.BORDER);
GridData gridData = new GridData(GridData.FILL,GridData.CENTER, true, false);
gridData.heightHint = 23;
numberLabel.setLayoutData(gridData);
if (useSpinner) {
Composite spinners = new Composite(editorComposite, SWT.None);
if (horizonalSpinner) {
grid = new GridLayout(2, true);
removeMargins(grid);
spinners.setLayout(grid);
gridData = new GridData(GridData.END, GridData.CENTER, false, false);
gridData.heightHint = 26;
spinners.setLayoutData(gridData);
decrementButton = new Button(spinners, SWT.FLAT);
decrementButton.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, true));
((Button) decrementButton).setText("-");
incrementButton = new Button(spinners, SWT.FLAT);
incrementButton.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, true));
((Button) incrementButton).setText("+");
} else {
grid = new GridLayout(1, false);
removeMargins(grid);
spinners.setLayout(grid);
gridData = new GridData(GridData.END, GridData.BEGINNING, false, false);
gridData.heightHint = 27;
gridData.widthHint = 25;
spinners.setLayoutData(gridData);
incrementButton = new Label(spinners, SWT.BORDER);
upArrowIcon = getImageDescriptor("up_arrow.png").createImage();
((Label) incrementButton).setImage(upArrowIcon);
incrementButton.setLayoutData(new GridData(GridData.FILL_BOTH));
decrementButton = new Label(spinners, SWT.BORDER);
decrementButton.setLayoutData(new GridData(GridData.FILL_BOTH));
downArrowIcon = getImageDescriptor("down_arrow.png").createImage();
((Label) decrementButton).setImage(downArrowIcon);
}
incrementComposite = new Composite(editorComposite, SWT.None);
gridData = new GridData(SWT.END, SWT.CENTER, false, false);
gridData.heightHint = 26;
gridData.widthHint = MIN_STEP_LABEL_WIDTH;
incrementComposite.setLayoutData(gridData);
stepLayout = new StackLayout();
incrementComposite.setLayout(stepLayout);
incrementText = new Label(incrementComposite, SWT.BORDER);
incrementText.setAlignment(SWT.CENTER);
stepLayout.topControl = incrementText;
}
layout.topControl = editorComposite;
}
private int getCompositeWidth(Composite cmp, String text){
GC gc = new GC(cmp);
Point point = gc.stringExtent(text);
gc.dispose();
return point.x;
}
private int getIncrementCompWidth(){
double incrementValue = controlModel.getIncrement() / Math.pow(10, controlModel.getDigits());
int widthOfText = getCompositeWidth(incrementComposite, Double.toString(incrementValue));
//int width = MIN_STEP_LABEL_WIDTH;
if (widthOfText > MIN_STEP_LABEL_WIDTH) {
return widthOfText + LARGE_INCREMENT_WIDTH_PADDING;
}
return MIN_STEP_LABEL_WIDTH + LARGE_INCREMENT_WIDTH_PADDING;
}
private void setupIncrementCompWidthHint(){
GridData gridData = (GridData) incrementComposite.getLayoutData();
gridData.widthHint = getIncrementCompWidth();
incrementComposite.layout();
incrementComposite.getParent().layout();
}
@Override
public boolean setFocus() {
return false;
}
private class StepListener implements Listener {
private final boolean isIncrement;
public StepListener(boolean isIncrement) {
this.isIncrement = isIncrement;
}
@Override
public void handleEvent(Event event) {
if (controlModel.isEditable()) {
try {
if (controlModel.getBindingPropertyType().equals(double.class)) {
double value = ((Double) PropertyUtils.getProperty(targetObject, propertyName)).doubleValue();
double increment = controlModel.getIncrement() / Math.pow(10, controlModel.getDigits());
if (isIncrement) {
if (!controlModel.isRangeSet()) {
PropertyUtils.setProperty(targetObject, propertyName, value + increment);
} else {
double incremented = value + increment;
if (incremented <= controlModel.getMaxValue().doubleValue()) {
PropertyUtils.setProperty(targetObject, propertyName, incremented);
}
}
} else {
if (!controlModel.isRangeSet()) {
PropertyUtils.setProperty(targetObject, propertyName, value - increment);
} else {
double decremented = value - increment;
if (decremented >= controlModel.getMinValue().doubleValue()) {
PropertyUtils.setProperty(targetObject, propertyName, decremented);
}
}
}
} else if (controlModel.getBindingPropertyType().equals(int.class)) {
int value = ((Number) PropertyUtils.getProperty(targetObject, propertyName)).intValue();
double increment = controlModel.getIncrement() / (int) Math.pow(10, controlModel.getDigits());
if (isIncrement) {
if (!controlModel.isRangeSet()) {
PropertyUtils.setProperty(targetObject, propertyName, (int) (value + increment));
} else {
double incremented = value + increment;
if (incremented <= controlModel.getMaxValue().intValue()) {
PropertyUtils.setProperty(targetObject, propertyName, (int) incremented);
}
}
} else {
if (!controlModel.isRangeSet()) {
PropertyUtils.setProperty(targetObject, propertyName, (int) (value - increment));
} else {
double decremented = value - increment;
if (decremented >= controlModel.getMinValue().intValue()) {
PropertyUtils.setProperty(targetObject, propertyName, (int) decremented);
}
}
}
}
} catch (Exception e) {
logger.error("Error binding view", e);
}
}
}
}
private final Listener openEditorListener = new Listener() {
@Override
public void handleEvent(Event event) {
openEditor();
}
};
private void openEditor() {
if (controlModel.isEditable()) {
numberEditorText = new NumberEditorText(NumberEditorControl.this, SWT.None);
GridData gridData = new GridData(SWT.FILL,SWT.BEGINNING, true, false);
numberEditorText.setLayoutData(gridData);
layout.topControl = numberEditorText;
NumberEditorControl.this.layout();
numberEditorText.addDisposeListener(new DisposeListener() {
@Override
public void widgetDisposed(DisposeEvent e) {
isEditing = false;
numberEditorText = null;
layout.topControl = editorComposite;
NumberEditorControl.this.layout(true, true);
}
});
isEditing = true;
}
}
private final Listener openStepEditorListener = new Listener() {
@Override
public void handleEvent(Event event) {
if (controlModel.isEditable()) {
stepText = new Text(incrementComposite, SWT.BORDER);
UpdateValueStrategy targetToModelStep = new UpdateValueStrategy(UpdateValueStrategy.POLICY_ON_REQUEST).setConverter(new IConverter() {
@Override
public Object getToType() {
return double.class;
}
@Override
public Object getFromType() {
return String.class;
}
@Override
public Object convert(Object fromObject) {
if (targetToModelConverter != null) {
fromObject = targetToModelConverter.convert(fromObject);
}
if (controlModel.getBindingPropertyType().equals(double.class)) {
return (int) (Double.parseDouble((String) fromObject) * (int) Math.pow(10, controlModel.getDigits()));
}
return Double.parseDouble((String) fromObject);
}
});
targetToModelStep.setBeforeSetValidator(new IValidator() {
@Override
public IStatus validate(Object value) {
if (controlModel.getBindingPropertyType().equals(int.class)) {
double val = ((Double) value).doubleValue();
if (!(val == Math.floor(val) && !Double.isInfinite(val))) {
return ValidationStatus.error("Invalid");
}
}
return ValidationStatus.ok();
}
});
incrementTextBinding = ctx.bindValue(
WidgetProperties.text(SWT.Modify).observe(stepText),
BeanProperties.value(NumberEditorWidgetModel.INCREMENT_PROP_NAME).observe(controlModel),
targetToModelStep, new UpdateValueStrategy() {
@Override
public Object convert(Object value) {
if (modelToTargetConverter != null) {
value = modelToTargetConverter.convert(value);
}
if (controlModel.getBindingPropertyType().equals(double.class)) {
double incrementValue = controlModel.getIncrement() / Math.pow(10, controlModel.getDigits());
return roundDoubletoString(incrementValue, controlModel.getDigits());
}
return super.convert(value);
}
});
ControlDecorationSupport.create(incrementTextBinding, SWT.TOP | SWT.RIGHT);
stepText.addModifyListener(new ModifyListener() {
@Override
public void modifyText(ModifyEvent e) {
incrementTextBinding.validateTargetToModel();
}
});
stepText.addTraverseListener(new TraverseListener() {
@Override
public void keyTraversed(TraverseEvent event) {
if (event.detail == SWT.TRAVERSE_RETURN) {
updateIncrementChangesAndDispose(true);
}
if (event.detail == SWT.TRAVERSE_ESCAPE) {
updateIncrementChangesAndDispose(false);
}
}
});
stepText.addFocusListener(new FocusListener() {
@Override
public void focusLost(FocusEvent e) {
updateIncrementChangesAndDispose(true);
}
@Override
public void focusGained(FocusEvent e) {
// Do nothing
}
});
stepLayout.topControl = stepText;
incrementComposite.layout();
stepText.setFocus();
stepText.selectAll();
}
}
};
private void updateIncrementChangesAndDispose(boolean saveChanges) {
if (saveChanges) {
incrementTextBinding.updateTargetToModel();
}
else {
incrementTextBinding.updateModelToTarget();
}
IStatus status = (IStatus) incrementTextBinding.getValidationStatus().getValue();
if (status.isOK()) {
ctx.removeBinding(incrementTextBinding);
incrementTextBinding.dispose();
incrementTextBinding = null;
stepLayout.topControl = incrementText;
stepText.dispose();
setupIncrementCompWidthHint();
}
}
private ImageDescriptor getImageDescriptor(String imageName) {
Bundle bundle = FrameworkUtil.getBundle(NumberEditorControl.class);
URL url = FileLocator.find(bundle,
new Path(ICONS_PATH + imageName), null);
return ImageDescriptor.createFromURL(url);
}
private static void removeMargins(GridLayout grid) {
grid.marginBottom = 0;
grid.marginTop = 0;
grid.marginHeight = 0;
grid.marginLeft = 0;
grid.marginRight = 0;
grid.verticalSpacing = 0;
grid.horizontalSpacing = 0;
}
protected class NumberEditorWidgetModel extends ObservableModel {
private boolean editable = true;
public static final String MAX_VALUE_PROP_NAME = "maxValue";
private Number maxValue;
public static final String MIN_VALUE_PROP_NAME = "minValue";
private Number minValue;
public static final String RANGE_SET_PROP_NAME = "rangeSet";
private boolean rangeSet;
public static final String DIGITS_PROP_NAME = "digits";
private int digits;
public static final String INCREMENT_PROP_NAME = "increment";
private double increment;
private String unit;
private Object bindingPropertyType;
private DecimalFormat decimalFormat;
public boolean isEditable() {
return editable;
}
public void setEditable(boolean value) {
firePropertyChange(EDITABLE_PROP_NAME, editable, editable = value);
}
public Number getMaxValue() {
return maxValue;
}
public void setMaxValue(Number value) {
firePropertyChange(MAX_VALUE_PROP_NAME, maxValue, maxValue = value);
firePropertyChange(RANGE_SET_PROP_NAME, rangeSet, rangeSet = true);
}
public Number getMinValue() {
return minValue;
}
public void setMinValue(Number value) {
firePropertyChange(MIN_VALUE_PROP_NAME, minValue, minValue = value);
firePropertyChange(RANGE_SET_PROP_NAME, rangeSet, rangeSet = true);
}
public boolean isRangeSet() {
return rangeSet;
}
public int getDigits() {
return digits;
}
public void setDigits(int value) {
firePropertyChange(DIGITS_PROP_NAME, digits, digits = value);
StringBuilder string = new StringBuilder("#.");
for (int i = 0; i < digits; i++) {
string.append("#");
}
decimalFormat = new DecimalFormat(string.toString());
}
public String getFormattedValue(double value) {
return decimalFormat.format(value);
}
public double getIncrement() {
return increment;
}
public void setIncrement(double value) {
firePropertyChange(INCREMENT_PROP_NAME, increment, increment = value);
}
public String getUnit() {
return unit;
}
public void setUnit(String value) {
firePropertyChange(UNIT_PROP_NAME, unit, unit = value);
}
public Object getBindingPropertyType() {
return bindingPropertyType;
}
public void setBindingPropertyType(Object bindingPropertyType) {
this.bindingPropertyType = bindingPropertyType;
}
}
private class NumberEditorText extends Composite {
private final Text editorText;
private final Button editorAcceptButton;
private final Button editorCancelButton;
private final DataBindingContext editorCtx = new DataBindingContext();
private Binding binding;
private final ImageDescriptor cancelImageDescriptor = getImageDescriptor("cancel.png");
private final Image cancelImage = cancelImageDescriptor.createImage();
private final ImageDescriptor acceptImageDescriptor = getImageDescriptor("accept.png");
private final Image acceptImage = acceptImageDescriptor.createImage();
protected boolean lostFocus;
protected boolean cancelOrCommit;
private final Listener inFocus;
private final FocusListener textOutFocus;
public NumberEditorText(Composite parent, int style) {
super(parent, style);
GridLayout grid = new GridLayout(3, false);
removeMargins(grid);
this.setLayout(grid);
editorText = new Text(this, SWT.BORDER);
GridData gridData = new GridData(SWT.FILL, SWT.BEGINNING, true, false);
editorText.setLayoutData(gridData);
editorCancelButton = new Button(this, SWT.NONE);
editorCancelButton.setImage(cancelImage);
gridData = new GridData(SWT.END, SWT.BEGINNING, false, false);
editorCancelButton.setLayoutData(gridData);
editorAcceptButton = new Button(this, SWT.NONE);
editorAcceptButton.setLayoutData(gridData);
editorAcceptButton.setImage(acceptImage);
editorCancelButton.addListener(SWT.Selection, new Listener() {
@Override
public void handleEvent(Event event) {
cancelOrCommit = true;
updateChangesAndDispose(false);
}
});
editorAcceptButton.addListener(SWT.Selection, new Listener() {
@Override
public void handleEvent(Event event) {
cancelOrCommit = true;
updateChangesAndDispose(true);
}
});
bind();
editorText.selectAll();
editorText.setFocus();
inFocus = new Listener() {
@Override
public void handleEvent(Event event) {
if (lostFocus & event.widget != editorText & event.widget != editorCancelButton & event.widget != editorAcceptButton & !cancelOrCommit) {
NumberEditorText.this.updateChangesAndDispose(commitOnOutOfFocus);
}
}
};
textOutFocus = new FocusListener() {
@Override
public void focusLost(FocusEvent e) {
lostFocus = true;
}
@Override
public void focusGained(FocusEvent e) {
// Do nothing
}
};
getDisplay().addFilter(SWT.FocusIn, inFocus);
editorText.addFocusListener(textOutFocus);
}
private void bind() {
IObservableValue objectValue = BeanProperties.value(propertyName).observe(targetObject);
ISWTObservableValue textValue = WidgetProperties.text().observe(editorText);
UpdateValueStrategy targetToModelUpdateValueStrategy = new UpdateValueStrategy(UpdateValueStrategy.POLICY_ON_REQUEST) {
@Override
public Object convert(Object value) {
if (targetToModelConverter != null) {
value = targetToModelConverter.convert(value);
}
if (controlModel.getBindingPropertyType().equals(double.class)) {
return Double.parseDouble((String) value);
}
return Integer.parseInt((String) value);
}
};
if (targetToModelValidator != null) {
targetToModelUpdateValueStrategy.setBeforeSetValidator(targetToModelValidator);
} else {
if (controlModel.isRangeSet()) {
targetToModelUpdateValueStrategy.setBeforeSetValidator(new IValidator() {
@Override
public IStatus validate(Object value) {
if (targetToModelConverter != null) {
value = targetToModelConverter.convert(value);
}
// TODO Max and min are int, review
if (controlModel.getBindingPropertyType().equals(double.class)) {
if (((Number) value).doubleValue() >= controlModel.getMinValue().doubleValue() & ((Number) value).doubleValue() <= controlModel.getMaxValue().doubleValue()) {
return ValidationStatus.ok();
}
return ValidationStatus.error("Out of range");
} else if (controlModel.getBindingPropertyType().equals(int.class)) {
if (((Number) value).intValue() >= controlModel.getMinValue().intValue() & ((Number) value).intValue() <= controlModel.getMaxValue().intValue()) {
return ValidationStatus.ok();
}
return ValidationStatus.error("Out of range");
}
return ValidationStatus.error("Unknown type");
}
});
}
}
binding = editorCtx.bindValue(textValue, objectValue, targetToModelUpdateValueStrategy, new UpdateValueStrategy() {
@Override
public Object convert(Object value) {
if (modelToTargetConverter != null) {
value = modelToTargetConverter.convert(value);
}
if (controlModel.getBindingPropertyType().equals(double.class)) {
return roundDoubletoString(((Number) value).doubleValue(), controlModel.getDigits());
}
return Integer.toString(((Number) value).intValue());
}
});
editorText.addModifyListener(new ModifyListener() {
@Override
public void modifyText(ModifyEvent e) {
binding.validateTargetToModel();
}
});
editorText.addTraverseListener(new TraverseListener() {
@Override
public void keyTraversed(TraverseEvent event) {
if (event.detail == SWT.TRAVERSE_RETURN) {
updateChangesAndDispose(true);
}
if (event.detail == SWT.TRAVERSE_ESCAPE) {
updateChangesAndDispose(false);
}
}
});
ControlDecorationSupport.create(binding, SWT.TOP | SWT.LEFT);
binding.getValidationStatus().addValueChangeListener(new IValueChangeListener() {
@Override
public void handleValueChange(ValueChangeEvent event) {
IStatus status = (IStatus) binding.getValidationStatus().getValue();
if (!editorAcceptButton.isDisposed()) {
editorAcceptButton.setEnabled(status.isOK() || status.getSeverity() == IStatus.INFO);
}
}
});
}
@Override
public void dispose() {
getDisplay().removeFilter(SWT.FocusIn, inFocus);
editorText.removeFocusListener(textOutFocus);
editorCtx.dispose();
acceptImage.dispose();
cancelImage.dispose();
super.dispose();
}
private void updateChangesAndDispose(boolean saveChanges) {
IStatus status = (IStatus) binding.getValidationStatus().getValue();
if (saveChanges) {
binding.updateTargetToModel();
} else {
binding.updateModelToTarget();
}
status = (IStatus) binding.getValidationStatus().getValue();
if (status.isOK() || status.getSeverity() == IStatus.INFO) {
NumberEditorText.this.dispose();
}
}
}
protected static String roundDoubletoString(double value, int digits) {
return String.format("%." + digits + "f", value);
}
protected String getFormattedText(Object value) {
String formattedValue = null;
if (controlModel.getBindingPropertyType().equals(double.class)) {
formattedValue = controlModel.getFormattedValue(((Number) value).doubleValue());
} else if (controlModel.getBindingPropertyType().equals(int.class)) {
formattedValue = Integer.toString(((Number) value).intValue());
}
if (controlModel.getUnit() != null) {
return formattedValue + " " + controlModel.getUnit();
}
return formattedValue;
}
public String _getTextForTesting() {
return numberLabel.getText();
}
}