uk.ac.gda.ui.components.NumberEditorControl.java Source code

Java tutorial

Introduction

Here is the source code for uk.ac.gda.ui.components.NumberEditorControl.java

Source

/*-
 * 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();
    }

}