package de.hftstuttgart.cityunits.ui.renderer; import java.math.BigInteger; import java.text.DecimalFormat; import java.text.ParseException; import java.text.ParsePosition; import javax.inject.Inject; import javax.measure.Unit; import org.eclipse.core.databinding.Binding; import org.eclipse.core.databinding.DataBindingContext; import org.eclipse.core.databinding.UpdateValueStrategy; import org.eclipse.core.databinding.observable.value.IObservableValue; import org.eclipse.core.runtime.IStatus; import org.eclipse.emf.ecore.EDataType; import org.eclipse.emf.ecore.EStructuralFeature; import org.eclipse.emf.ecp.edit.internal.swt.controls.NumericalHelper; import org.eclipse.emf.ecp.edit.spi.swt.util.ECPDialogExecutor; import org.eclipse.emf.ecp.view.internal.core.swt.MessageKeys; import org.eclipse.emf.ecp.view.spi.context.ViewModelContext; import org.eclipse.emf.ecp.view.spi.core.swt.renderer.TextControlSWTRenderer; import org.eclipse.emf.ecp.view.spi.model.VControl; import org.eclipse.emf.ecp.view.spi.model.VFeaturePathDomainModelReference; import org.eclipse.emf.ecp.view.template.model.VTViewTemplateProvider; import org.eclipse.emf.edit.command.SetCommand; import org.eclipse.emfforms.spi.common.locale.EMFFormsLocaleChangeListener; import org.eclipse.emfforms.spi.common.locale.EMFFormsLocaleProvider; import org.eclipse.emfforms.spi.common.report.ReportService; import org.eclipse.emfforms.spi.core.services.databinding.DatabindingFailedException; import org.eclipse.emfforms.spi.core.services.databinding.EMFFormsDatabinding; import org.eclipse.emfforms.spi.core.services.editsupport.EMFFormsEditSupport; import org.eclipse.emfforms.spi.core.services.label.EMFFormsLabelProvider; import org.eclipse.emfforms.spi.localization.EMFFormsLocalizationService; import org.eclipse.jface.dialogs.IDialogLabelKeys; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.layout.GridDataFactory; import org.eclipse.jface.layout.GridLayoutFactory; import org.eclipse.jface.resource.JFaceResources; import org.eclipse.swt.SWT; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Text; import de.hftstuttgart.cityunits.model.NullableQuantity; import de.hftstuttgart.cityunits.model.quantities.QuantitiesPackage; public class QuantityControlRenderer extends TextControlSWTRenderer { private final EMFFormsLocalizationService localizationService; private final EMFFormsLocaleProvider localeProvider; private EMFFormsLocaleChangeListener emfFormsLocaleChangeListener; private Unit unit; /** * Default constructor. * * @param vElement the view model element to be rendered * @param viewContext the view context * @param reportService The {@link ReportService} * @param emfFormsDatabinding The {@link EMFFormsDatabinding} * @param emfFormsLabelProvider The {@link EMFFormsLabelProvider} * @param vtViewTemplateProvider The {@link VTViewTemplateProvider} * @param emfFormsEditSupport The {@link EMFFormsEditSupport} * @param localizationService The {@link EMFFormsLocalizationService} * @param localeProvider The {@link EMFFormsLocaleProvider} */ @Inject // CHECKSTYLE.OFF: ParameterNumber public QuantityControlRenderer(VControl vElement, ViewModelContext viewContext, ReportService reportService, EMFFormsDatabinding emfFormsDatabinding, EMFFormsLabelProvider emfFormsLabelProvider, VTViewTemplateProvider vtViewTemplateProvider, EMFFormsEditSupport emfFormsEditSupport, EMFFormsLocalizationService localizationService, EMFFormsLocaleProvider localeProvider) { // CHECKSTYLE.ON: ParameterNumber super(vElement, viewContext, reportService, emfFormsDatabinding, emfFormsLabelProvider, vtViewTemplateProvider, emfFormsEditSupport); this.localizationService = localizationService; this.localeProvider = localeProvider; } @Override protected Control createSWTControl(Composite parent) { final VFeaturePathDomainModelReference featureRef = (VFeaturePathDomainModelReference) getVElement() .getDomainModelReference(); final String defaultString = featureRef.getDomainModelEFeature().getDefaultValueLiteral(); final NullableQuantity defaultValue = NullableQuantity.create(defaultString); unit = defaultValue.getUnit(); final Composite composite = new Composite(parent, SWT.NONE); GridLayoutFactory.fillDefaults().numColumns(2).applyTo(composite); GridDataFactory.fillDefaults().grab(true, false).align(SWT.FILL, SWT.BEGINNING).applyTo(composite); final Control control = super.createSWTControl(composite); final Label label = new Label(composite, SWT.PUSH); label.setText(unit.toString()); return control; } @Override protected int getDefaultAlignment() { return SWT.RIGHT; } /* * (non-Javadoc) * @see org.eclipse.emf.ecp.edit.internal.swt.controls.AbstractTextControl#getTextVariantID() */ @Override protected String getTextVariantID() { return "org_eclipse_emf_ecp_control_numerical"; //$NON-NLS-1$ } @Override protected String getTextMessage() { return ""; //$NON-NLS-1$ } @Override protected Object convert(Text text, EDataType attributeType, String value) throws DatabindingFailedException { final EStructuralFeature eStructuralFeature = (EStructuralFeature) getModelValue().getValueType(); final NumericalTargetToModelUpdateStrategy converter = new NumericalTargetToModelUpdateStrategy( eStructuralFeature, getModelValue(), getDataBindingContext(), text); return converter.convert(value); } @Override protected Binding[] createBindings(final Control control) throws DatabindingFailedException { final EStructuralFeature structuralFeature = (EStructuralFeature) getModelValue().getValueType(); final UpdateValueStrategy targetToModelStrategy = withPreSetValidation( new NumericalTargetToModelUpdateStrategy(structuralFeature, getModelValue(), getDataBindingContext(), (Text) Composite.class.cast(control).getChildren()[0])); final NumericalModelToTargetUpdateStrategy modelToTargetStrategy = new NumericalModelToTargetUpdateStrategy( getInstanceClass(structuralFeature), false); final Binding binding = bindValue(control, getModelValue(), getDataBindingContext(), targetToModelStrategy, modelToTargetStrategy); final Binding tooltipBinding = createTooltipBinding(control, getModelValue(), getDataBindingContext(), targetToModelStrategy, new NumericalModelToTargetUpdateStrategy(getInstanceClass(structuralFeature), true)); emfFormsLocaleChangeListener = new EMFFormsLocaleChangeListener() { /** * {@inheritDoc} * * @see org.eclipse.emfforms.spi.common.locale.EMFFormsLocaleChangeListener#notifyLocaleChange() */ @Override public void notifyLocaleChange() { ((Text) control).setMessage(getTextMessage()); binding.updateModelToTarget(); } }; localeProvider.addEMFFormsLocaleChangeListener(emfFormsLocaleChangeListener); return new Binding[] { binding, tooltipBinding }; } private Class getInstanceClass(EStructuralFeature feature) { if (feature.getEType() == QuantitiesPackage.eINSTANCE.getQuantityDouble()) { return Double.class; } else if (feature.getEType() == QuantitiesPackage.eINSTANCE.getQuantityLong()) { return Long.class; } assert false; return null; } @Override protected String getTextFromTextField(Text text, EDataType attributeType) { return text.getText().isBlank() ? null : super.getTextFromTextField(text, attributeType); } /** * Converts the numerical value from the model to the target. Locale settings are respected, * i.e. formatting is performed according to the current locale. */ private class NumericalModelToTargetUpdateStrategy extends ModelToTargetUpdateStrategy { private final Class instanceClass; NumericalModelToTargetUpdateStrategy(Class instanceClass, boolean tooltip) { super(tooltip); this.instanceClass = instanceClass; } @Override public Object convertValue(Object value) { if (value == null) { return ""; //$NON-NLS-1$ } return ((NullableQuantity) value).getNumber() .map(n -> NumericalHelper.setupFormat(localeProvider.getLocale(), instanceClass).format(n)) .orElse(""); //$NON-NLS-1$ } } /** * More specific target to model update strategy that convert the string * in the text field to a number. If the string is a invalid number, * for instance because of the current locale, the value is reset to * the last valid value found in the mode. */ private class NumericalTargetToModelUpdateStrategy extends TargetToModelUpdateStrategy { private final Text text; private final IObservableValue modelValue; private final EStructuralFeature eStructuralFeature; private final DataBindingContext dataBindingContext; NumericalTargetToModelUpdateStrategy(EStructuralFeature eStructuralFeature, IObservableValue modelValue, DataBindingContext dataBindingContext, Text text) { super(eStructuralFeature.isUnsettable()); this.eStructuralFeature = eStructuralFeature; this.modelValue = modelValue; this.dataBindingContext = dataBindingContext; this.text = text; } @Override protected Object convertValue(final Object value) { final DecimalFormat format = NumericalHelper.setupFormat( localeProvider.getLocale(), getInstanceClass(eStructuralFeature)); try { Number number = null; if (value == null) { number = NumericalHelper.getDefaultValue(getInstanceClass(eStructuralFeature)); } else { final ParsePosition pp = new ParsePosition(0); number = format.parse((String) value, pp); if (pp.getErrorIndex() != -1 || pp.getIndex() != ((String) value).length()) { return getOldValue(value); } if (isInteger(getInstanceClass(eStructuralFeature))) { boolean maxValue = false; boolean minValue = false; final Class instanceClass = getInstanceClass(eStructuralFeature); if (number.doubleValue() >= getInstanceMaxValue(instanceClass)) { maxValue = true; } else if (number.doubleValue() <= getInstanceMinValue(instanceClass)) { minValue = true; } if (maxValue || minValue) { return numberToQuantity(number); } } } String formatedNumber = ""; //$NON-NLS-1$ if (number != null) { formatedNumber = format.format(number); } if (formatedNumber.length() == 0) { return null; } return numberToQuantity(format.parse(formatedNumber)); } catch (final ParseException ex) { return getOldValue(value); } } /** * Whether the given class is an integer. * * @param instanceClass the class to check * @return true if integer, false otherwise */ private boolean isInteger(Class instanceClass) { if (instanceClass.isPrimitive()) { return long.class == instanceClass || int.class == instanceClass || short.class == instanceClass || byte.class == instanceClass; } return BigInteger.class == instanceClass || Long.class == instanceClass || BigInteger.class == instanceClass || Short.class == instanceClass || Byte.class == instanceClass; } private Object numberToQuantity(Number number) { return NullableQuantity.create(number, unit); } private double getInstanceMinValue(Class instanceClass) { if (Integer.class == instanceClass || int.class == instanceClass) { return Integer.MIN_VALUE; } if (Long.class == instanceClass || long.class == instanceClass) { return Long.MIN_VALUE; } if (Short.class == instanceClass || short.class == instanceClass) { return Short.MIN_VALUE; } return Double.NaN; } private double getInstanceMaxValue(Class instanceClass) { if (Integer.class == instanceClass || int.class == instanceClass) { return Integer.MAX_VALUE; } if (Long.class == instanceClass || long.class == instanceClass) { return Long.MAX_VALUE; } if (Short.class == instanceClass || short.class == instanceClass) { return Short.MAX_VALUE; } return Double.NaN; } @Override protected IStatus doSet(IObservableValue observableValue, Object value) { final IStatus status = super.doSet(observableValue, value); // update targets after a model change triggered by the target to model databinding dataBindingContext.updateTargets(); return status; } private Object getOldValue(final Object value) { if (eStructuralFeature.getDefaultValue() == null && value == null || value.equals("")) { //$NON-NLS-1$ return null; } final Object result = modelValue.getValue(); final MessageDialog messageDialog = new MessageDialog(text.getShell(), localizationService.getString(getClass(), MessageKeys.NumericalControl_InvalidNumber), null, localizationService.getString(getClass(), MessageKeys.NumericalControl_InvalidNumberWillBeUnset), MessageDialog.ERROR, new String[] { JFaceResources.getString(IDialogLabelKeys.OK_LABEL_KEY) }, 0); new ECPDialogExecutor(messageDialog) { @Override public void handleResult(int codeResult) { } }.execute(); dataBindingContext.updateTargets(); if (eStructuralFeature.isUnsettable() && result == null) { // showUnsetLabel(); return SetCommand.UNSET_VALUE; } return result; } } /** * {@inheritDoc} * * @see org.eclipse.emf.ecp.view.spi.core.swt.renderer.TextControlSWTRenderer#getUnsetText() */ @Override protected String getUnsetText() { return localizationService.getString(getClass(), MessageKeys.NumericalControl_NoNumberClickToSetNumber); } /** * {@inheritDoc} * * @see org.eclipse.emf.ecp.view.spi.core.swt.SimpleControlSWTRenderer#dispose() */ @Override protected void dispose() { super.dispose(); localeProvider.removeEMFFormsLocaleChangeListener(emfFormsLocaleChangeListener); } }