Skip to content

Content of file XMLDateControlSWTRenderer.java

/*******************************************************************************
 * Copyright (c) 2011-2014 EclipseSource Muenchen GmbH and others.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 * Eugen - initial API and implementation
 ******************************************************************************/
package org.eclipse.emf.ecp.view.internal.core.swt.renderer;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import java.util.regex.Pattern;

import javax.inject.Inject;
import javax.xml.datatype.XMLGregorianCalendar;

import org.eclipse.core.databinding.Binding;
import org.eclipse.core.databinding.UpdateValueStrategy;
import org.eclipse.core.databinding.observable.value.IObservableValue;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.emf.common.util.EMap;
import org.eclipse.emf.ecore.EAnnotation;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EDataType;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecp.edit.internal.swt.util.DateUtil;
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.LabelAlignment;
import org.eclipse.emf.ecp.view.spi.model.VControl;
import org.eclipse.emf.ecp.view.spi.util.swt.ImageRegistryService;
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.AbstractReport;
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.emfforms.spi.localization.LocalizationServiceHelper;
import org.eclipse.emfforms.spi.swt.core.layout.SWTGridCell;
import org.eclipse.jface.databinding.swt.WidgetProperties;
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.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
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.DateTime;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import org.osgi.framework.FrameworkUtil;

/**
 * @author Eugen
 *
 */
public class XMLDateControlSWTRenderer extends TextControlSWTRenderer {

	private final EMFFormsLocaleProvider localeProvider;
	private final EMFFormsLocalizationService localizationService;
	private final ImageRegistryService imageRegistryService;

	/**
	 * 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}
	 * @param imageRegistryService The {@link ImageRegistryService}
	 */
	@Inject
	public XMLDateControlSWTRenderer(VControl vElement, ViewModelContext viewContext,
		ReportService reportService,
		EMFFormsDatabinding emfFormsDatabinding, EMFFormsLabelProvider emfFormsLabelProvider,
		VTViewTemplateProvider vtViewTemplateProvider, EMFFormsEditSupport emfFormsEditSupport,
		EMFFormsLocalizationService localizationService, EMFFormsLocaleProvider localeProvider,
		ImageRegistryService imageRegistryService) {
		super(vElement, viewContext, reportService, emfFormsDatabinding, emfFormsLabelProvider, vtViewTemplateProvider,
			emfFormsEditSupport);
		this.localizationService = localizationService;
		this.localeProvider = localeProvider;
		this.imageRegistryService = imageRegistryService;
	}

	private static final DateFormat CHECK_FORMAT = new SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH); //$NON-NLS-1$
	private static final Pattern CHECK_PATTERN = Pattern.compile("^\\d{4}-\\d{2}-\\d{2}$"); //$NON-NLS-1$

	/**
	 * Selection adapter for the set date button.
	 */
	private final class SelectionAdapterExtension extends SelectionAdapter {
		private final Control button;
		private final Text text;

		private SelectionAdapterExtension(Text text, Button button) {
			this.text = text;
			this.button = button;
		}

		@Override
		public void widgetSelected(SelectionEvent e) {
			if (dialog != null && !dialog.isDisposed()) {
				dialog.dispose();
				return;
			}
			IObservableValue modelValue;
			EStructuralFeature eStructuralFeature;
			try {
				modelValue = getModelValue();
				eStructuralFeature = (EStructuralFeature) modelValue.getValueType();
			} catch (final DatabindingFailedException ex) {
				getReportService().report(new AbstractReport(ex));
				return;
			}
			dialog = new Shell(button.getShell(), SWT.NONE);
			dialog.setLayout(new GridLayout(1, false));

			final DateTime calendar = new DateTime(dialog, SWT.CALENDAR | SWT.BORDER);
			final XMLGregorianCalendar gregorianCalendar = (XMLGregorianCalendar) modelValue.getValue();
			final Calendar cal = Calendar.getInstance(localeProvider.getLocale());
			if (gregorianCalendar != null) {
				cal.setTime(gregorianCalendar.toGregorianCalendar().getTime());
			}
			calendar.setDate(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH));

			final IObservableValue dateObserver = WidgetProperties.selection().observe(calendar);
			final Binding binding = getDataBindingContext().bindValue(dateObserver, modelValue,
				new DateTargetToModelUpdateStrategy(eStructuralFeature, text),
new DateModelToTargetUpdateStrategy(false, true)); binding.updateModelToTarget(); final Button okButton = new Button(dialog, SWT.PUSH); okButton.setText(JFaceResources.getString(IDialogLabelKeys.OK_LABEL_KEY)); GridDataFactory.fillDefaults().align(SWT.END, SWT.CENTER).grab(false, false).applyTo(okButton); okButton.addSelectionListener(new SelectionAdapter() { /** * {@inheritDoc} * * @see org.eclipse.swt.events.SelectionAdapter#widgetSelected(org.eclipse.swt.events.SelectionEvent) */ @Override public void widgetSelected(SelectionEvent e) { binding.updateTargetToModel(); binding.dispose(); dialog.close(); } }); dialog.pack(); dialog.layout(); final Point dialogSize = dialog.getSize(); final Rectangle displayBounds = dialog.getDisplay().getBounds(); final Point buttonLocation = button.toDisplay(button.getSize().x, button.getSize().y); // TODO what if dialogsize > displaybounds? + some other cases int dialogX = buttonLocation.x - dialogSize.x; int dialogY = buttonLocation.y; if (dialogY + dialogSize.y > displayBounds.height) { dialogY = dialogY - button.getSize().y - dialogSize.y; } if (dialogX + dialogSize.x > displayBounds.width) { dialogX = dialogX - dialogSize.x; } else if (dialogX - dialogSize.x < displayBounds.x) { dialogX = buttonLocation.x - button.getSize().x; } dialog.setLocation(dialogX, dialogY); dialog.open(); } } @Override protected Object convert(Text text, EDataType attributeType, String value) throws DatabindingFailedException { final EStructuralFeature eStructuralFeature = (EStructuralFeature) getModelValue().getValueType(); final DateTargetToModelUpdateStrategy converter = new DateTargetToModelUpdateStrategy(eStructuralFeature, text); return converter.convert(value); } /** * Model to target strategy. */ private class DateModelToTargetUpdateStrategy extends ModelToTargetUpdateStrategy { private final boolean toDate; DateModelToTargetUpdateStrategy(boolean tooltip, boolean toDate) { super(tooltip); this.toDate = toDate; } DateModelToTargetUpdateStrategy(boolean tooltip) { this(tooltip, false); } @Override public Object convertValue(Object value) { final DateFormat format = setupFormat(); final XMLGregorianCalendar gregorianCalendar = (XMLGregorianCalendar) value; if (gregorianCalendar == null) { return null; } final Date date = gregorianCalendar.toGregorianCalendar().getTime(); if (toDate) { return date; } return format.format(date); } @Override protected IStatus doSet(IObservableValue observableValue, Object value) { if (value == null && toDate) { return Status.OK_STATUS; } if (value == null && !toDate) { value = ""; //$NON-NLS-1$ } return super.doSet(observableValue, value); } } /** * Target to model strategy. */ private class DateTargetToModelUpdateStrategy extends TargetToModelUpdateStrategy { private final EStructuralFeature eStructuralFeature; private final Text text; private final boolean isDate; DateTargetToModelUpdateStrategy(EStructuralFeature eStructuralFeature, Text text) { super(eStructuralFeature.isUnsettable()); this.eStructuralFeature = eStructuralFeature; this.text = text; final EClassifier eType = eStructuralFeature.getEType(); if (eType == null) { isDate = true; return; } final EAnnotation eAnnotation = eType.getEAnnotation("http:///org/eclipse/emf/ecore/util/ExtendedMetaData");//$NON-NLS-1$ if (eAnnotation == null) { isDate = true; return; } final EMap<String, String> typeDetails = eAnnotation.getDetails(); if (typeDetails.containsKey("name")) {//$NON-NLS-1$ isDate = "date".equals(typeDetails.get("name"));//$NON-NLS-1$//$NON-NLS-2$ } else if (typeDetails.containsKey("baseType")) {//$NON-NLS-1$ isDate = typeDetails.get("baseType").endsWith("date");//$NON-NLS-1$//$NON-NLS-2$ } else { isDate = true; } } @Override protected Object convertValue(final Object value) { try { Date date = null; if (String.class.isInstance(value)) { date = setupFormat().parse((String) value); } else if (Date.class.isInstance(value)) { date = (Date) value; } else if (value == null) { return value; } final String xmlFormat = CHECK_FORMAT.format(date); if (!CHECK_PATTERN.matcher(xmlFormat).matches()) { return revertToOldValue(value); } final String formatedDate = setupFormat().format(date); text.setText(formatedDate); final Calendar targetCal = Calendar.getInstance(); targetCal.setTime(date); if (isDate) { return DateUtil.convertOnlyDateToXMLGregorianCalendar(targetCal); } return DateUtil.convertCalendarToXMLGregorianCalendar(targetCal); } catch (final ParseException ex) { return revertToOldValue(value); } } private Object revertToOldValue(final Object value) { if (eStructuralFeature.getDefaultValue() == null && (value == null || value.equals(""))) { //$NON-NLS-1$ return null; } Object result; try { result = getModelValue().getValue(); } catch (final DatabindingFailedException ex) { getReportService().report(new AbstractReport(ex)); return null; } final MessageDialog messageDialog = new MessageDialog(text.getShell(), LocalizationServiceHelper.getString(XMLDateControlSWTRenderer.class, MessageKeys.XmlDateControlText_InvalidNumber), null, LocalizationServiceHelper.getString(XMLDateControlSWTRenderer.class, MessageKeys.XmlDateControlText_NumberInvalidValueWillBeUnset), MessageDialog.ERROR, new String[] { JFaceResources.getString(IDialogLabelKeys.OK_LABEL_KEY) }, 0); new ECPDialogExecutor(messageDialog) { @Override public void handleResult(int codeResult) { } }.execute(); getDataBindingContext().updateTargets(); if (eStructuralFeature.isUnsettable() && result == null) { return SetCommand.UNSET_VALUE; } return result; } } private Shell dialog; private EMFFormsLocaleChangeListener emfFormsLocaleChangeListener; private Text text; @Override protected Control createSWTControl(Composite parent) { final Composite main = new Composite(parent, SWT.NONE); main.setBackground(parent.getBackground()); GridLayoutFactory.fillDefaults().numColumns(2).applyTo(main); GridDataFactory.fillDefaults().grab(true, false).align(SWT.FILL, SWT.BEGINNING).applyTo(main); final Control control = super.createSWTControl(main); GridDataFactory.fillDefaults().grab(true, false).align(SWT.FILL, SWT.CENTER).applyTo(control); final Button bDate = new Button(main, SWT.PUSH); GridDataFactory.fillDefaults().grab(false, false).align(SWT.CENTER, SWT.CENTER).applyTo(bDate); bDate.setImage( imageRegistryService.getImage(FrameworkUtil.getBundle(XMLDateControlSWTRenderer.class), "icons/date.png")); //$NON-NLS-1$ bDate.setData(CUSTOM_VARIANT, "org_eclipse_emf_ecp_control_xmldate"); //$NON-NLS-1$ text = (Text) Composite.class.cast(control).getChildren()[0]; bDate.addSelectionListener(new SelectionAdapterExtension(text, bDate)); return main; } @Override protected String getTextMessage() { return ((SimpleDateFormat) setupFormat()).toPattern(); } @Override protected String getTextVariantID() { return "org_eclipse_emf_ecp_control_xmldate"; //$NON-NLS-1$ } @Override protected Binding[] createBindings(Control control) throws DatabindingFailedException { final EStructuralFeature structuralFeature = (EStructuralFeature) getModelValue().getValueType(); final Text text = (Text) Composite.class.cast(Composite.class.cast(control).getChildren()[0]).getChildren()[0]; final IObservableValue value = WidgetProperties.text(SWT.FocusOut).observe(text); final UpdateValueStrategy targetToModelUpdateStrategy = withPreSetValidation( new DateTargetToModelUpdateStrategy(structuralFeature, text)); final DateModelToTargetUpdateStrategy modelToTargetUpdateStrategy = new DateModelToTargetUpdateStrategy(false); final Binding binding = getDataBindingContext().bindValue(value, getModelValue(), targetToModelUpdateStrategy, modelToTargetUpdateStrategy); final Binding tooltipBinding = createTooltipBinding(control, getModelValue(), getDataBindingContext(), new UpdateValueStrategy(UpdateValueStrategy.POLICY_NEVER), new DateModelToTargetUpdateStrategy(true)); emfFormsLocaleChangeListener = new EMFFormsLocaleChangeListener() { /** * {@inheritDoc} * * @see org.eclipse.emfforms.spi.common.locale.EMFFormsLocaleChangeListener#notifyLocaleChange() */ @Override public void notifyLocaleChange() { text.setMessage(getTextMessage()); binding.updateModelToTarget(); } }; localeProvider.addEMFFormsLocaleChangeListener(emfFormsLocaleChangeListener); return new Binding[] { binding, tooltipBinding }; } /** * Setups the {@link DateFormat}. * * @return the {@link DateFormat} */ protected DateFormat setupFormat() { final DateFormat df = DateFormat.getDateInstance(DateFormat.MEDIUM, localeProvider.getLocale()); df.setLenient(false); return df; } /** * {@inheritDoc} * * @see org.eclipse.emf.ecp.view.spi.core.swt.SimpleControlSWTControlSWTRenderer#setValidationColor(org.eclipse.swt.widgets.Control, * org.eclipse.swt.graphics.Color) */ @Override protected void setValidationColor(Control control, Color validationColor) { super.setValidationColor(getControlCompositeFromControl(control), validationColor); } @Override protected void setValidationForegroundColor(Control control, Color validationColor) { super.setValidationForegroundColor(getControlCompositeFromControl(control), validationColor); } @Override protected void setControlEnabled(SWTGridCell gridCell, Control control, boolean enabled) { if (getVElement().getLabelAlignment() == LabelAlignment.NONE && gridCell.getColumn() == 1 || hasLeftLabelAlignment() && gridCell.getColumn() == 2) { if (isUnsetButtonLeftOfControlComposite()) { super.setControlEnabled(gridCell, control, enabled); ((Button) ((Composite) control).getChildren()[0]).setVisible(enabled); } else { super.setControlEnabled(gridCell, control, enabled); ((Button) ((Composite) control).getChildren()[1]).setVisible(enabled); } } else { super.setControlEnabled(gridCell, control, enabled); } } /** * {@inheritDoc} * * @see org.eclipse.emf.ecp.view.spi.core.swt.renderer.TextControlSWTRenderer#getUnsetText() */ @Override protected String getUnsetText() { return localizationService .getString(getClass(), MessageKeys.XmlDateControlText_NoDateSetClickToSetDate); } /** * {@inheritDoc} * * @see org.eclipse.emf.ecp.view.spi.core.swt.SimpleControlSWTRenderer#dispose() */ @Override protected void dispose() { if (dialog != null && !dialog.isDisposed()) { dialog.dispose(); } localeProvider.removeEMFFormsLocaleChangeListener(emfFormsLocaleChangeListener); super.dispose(); } }