Skip to content

Content of file DateTimeControlSWTRenderer.java

/*******************************************************************************
 * Copyright (c) 2011-2019 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
 * Christian W. Damus - bug 527686
 ******************************************************************************/
package org.eclipse.emf.ecp.view.internal.core.swt.renderer;

import java.util.Calendar;
import java.util.Date;
import java.util.Locale;

import javax.inject.Inject;

import org.eclipse.core.databinding.Binding;
import org.eclipse.core.databinding.UpdateValueStrategy;
import org.eclipse.core.databinding.observable.IObserving;
import org.eclipse.core.databinding.observable.value.DateAndTimeObservableValue;
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.command.Command;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecp.edit.spi.ViewLocaleService;
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.SimpleControlSWTControlSWTRenderer;
import org.eclipse.emf.ecp.view.spi.model.DateTimeDisplayType;
import org.eclipse.emf.ecp.view.spi.model.ModelChangeListener;
import org.eclipse.emf.ecp.view.spi.model.ModelChangeNotification;
import org.eclipse.emf.ecp.view.spi.model.VAttachment;
import org.eclipse.emf.ecp.view.spi.model.VControl;
import org.eclipse.emf.ecp.view.spi.model.VDateTimeDisplayAttachment;
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.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.DatabindingFailedReport;
import org.eclipse.emfforms.spi.core.services.databinding.EMFFormsDatabinding;
import org.eclipse.emfforms.spi.core.services.label.EMFFormsLabelProvider;
import org.eclipse.emfforms.spi.localization.EMFFormsLocalizationService;
import org.eclipse.emfforms.spi.swt.core.SWTDataElementIdHelper;
import org.eclipse.jface.databinding.swt.ISWTObservableValue;
import org.eclipse.jface.databinding.swt.WidgetProperties;
import org.eclipse.jface.dialogs.IDialogLabelKeys;
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.custom.StackLayout;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Color;
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.DateTime;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.osgi.framework.FrameworkUtil;

/**
 * A control which can handle {@link java.util.Date Date}.
 *
 * @author Eugen Neufeld
 *
 */
public class DateTimeControlSWTRenderer extends SimpleControlSWTControlSWTRenderer {

	/**
	 * ModelToTarget Strategy that handles also null values of dates.
	 *
	 * @author Eugen Neufeld
	 *
	 */
	private class DateModelToTargetUpdateStrategy extends UpdateValueStrategy {
		DateModelToTargetUpdateStrategy() {
			super();
		}

		DateModelToTargetUpdateStrategy(int policy) {
			super(policy);
		}

		@Override
		protected IStatus doSet(IObservableValue observableValue, Object value) {
			if (value == null) {
				return Status.OK_STATUS;
			}
			return super.doSet(observableValue, value);
		}
	}

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

	private Label unsetLabel;

	private StackLayout stackLayout;

	private Composite stackComposite, dateTimeComposite;

	private Composite composite;

	private Shell dialog;

	private ModelChangeListener domainModelChangeListener;

	private DateTime dateWidget;
	private DateTime timeWidget;

	private Button bUnset;

	private Button setBtn;

	@Override
	protected Binding[] createBindings(Control control) throws DatabindingFailedException {
		final ISWTObservableValue dateObserver = WidgetProperties.selection().observe(dateWidget);
		final ISWTObservableValue timeObserver = WidgetProperties.selection().observe(timeWidget);
		final IObservableValue target = new DateAndTimeObservableValue(dateObserver, timeObserver);
		final Binding binding = getDataBindingContext().bindValue(target, getModelValue(),
			withPreSetValidation(new UpdateValueStrategy()),
			new DateModelToTargetUpdateStrategy());

		if (domainModelChangeListener == null) {
			domainModelChangeListener = new ModelChangeListener() {
				@Override
				public void notifyChange(ModelChangeNotification notification) {
					EStructuralFeature structuralFeature;
					try {
						structuralFeature = (EStructuralFeature) getModelValue().getValueType();
					} catch (final DatabindingFailedException ex) {
						getReportService().report(new DatabindingFailedReport(ex));
						return;
					}
					if (structuralFeature.equals(notification.getStructuralFeature())) {
						updateChangeListener(notification.getRawNotification().getNewValue());
					}
				}
			};
			getViewModelContext().registerDomainChangeListener(domainModelChangeListener);
		}

		return new Binding[] { binding };
	}

	/**
	 * {@inheritDoc}
	 *
	 * @see org.eclipse.emf.ecp.view.spi.core.swt.SimpleControlSWTRenderer#dispose()
	 */
	@Override
	protected void dispose() {
		if (dialog != null && !dialog.isDisposed()) {
			dialog.dispose();
		}
		getViewModelContext().unregisterDomainChangeListener(domainModelChangeListener);
		super.dispose();
	}

	@Override
	protected Control createSWTControl(Composite parent) throws DatabindingFailedException {
		composite = new Composite(parent, SWT.NONE);
		composite.setBackground(parent.getBackground());
		GridLayoutFactory.fillDefaults().numColumns(2).spacing(2, 0).equalWidth(false)
			.applyTo(composite);
		GridDataFactory.fillDefaults().grab(true, false).align(SWT.FILL, SWT.BEGINNING).applyTo(composite);

		stackComposite = new Composite(composite, SWT.NONE);
		stackComposite.setBackground(parent.getBackground());
		GridLayoutFactory.fillDefaults().numColumns(1).spacing(2, 0).equalWidth(false)
			.applyTo(stackComposite);

		GridDataFactory.fillDefaults().grab(true, false).align(SWT.FILL, SWT.CENTER).applyTo(stackComposite);

		dateTimeComposite = new Composite(stackComposite, SWT.NONE);
		dateTimeComposite.setBackground(composite.getBackground());
		GridLayoutFactory.fillDefaults().numColumns(3).spacing(2, 0).equalWidth(false)
			.applyTo(dateTimeComposite);
		GridDataFactory.fillDefaults().grab(true, false).align(SWT.FILL, SWT.CENTER).applyTo(dateTimeComposite);

		stackLayout = new StackLayout();
		stackComposite.setLayout(stackLayout);
		unsetLabel = new Label(stackComposite, SWT.CENTER);
		SWTDataElementIdHelper.setElementIdDataWithSubId(unsetLabel, getVElement(), "unsetlabel", //$NON-NLS-1$
			getViewModelContext());
		unsetLabel.setText(getUnsetText());
		unsetLabel.setBackground(stackComposite.getBackground());
		unsetLabel.setForeground(parent.getDisplay().getSystemColor(SWT.COLOR_DARK_GRAY));
		unsetLabel.setAlignment(SWT.CENTER);

		final DateTimeDisplayType dateTimeDisplayType = getDateTimeDisplayType();
		createDateTimeWidgets(dateTimeDisplayType);

		bUnset = new Button(dateTimeComposite, SWT.PUSH);
		SWTDataElementIdHelper.setElementIdDataWithSubId(bUnset, getVElement(), "unset", getViewModelContext()); //$NON-NLS-1$
		GridDataFactory.fillDefaults().grab(false, false).align(SWT.CENTER, SWT.CENTER).applyTo(bUnset);
		bUnset
			.setImage(imageRegistryService.getImage(FrameworkUtil.getBundle(DateTimeControlSWTRenderer.class),
				"icons/unset_feature.png")); //$NON-NLS-1$
		bUnset.setData(CUSTOM_VARIANT, "org_eclipse_emf_ecp_control_dateTime_buttonUnset"); //$NON-NLS-1$
		final String tooltip = getDateTimeDisplayType() == DateTimeDisplayType.TIME_ONLY
			? MessageKeys.DateTimeControlSWTRenderer_CleanTime
			: MessageKeys.DateTimeControlSWTRenderer_CleanDate;
		bUnset.setToolTipText(getLocalizedString(tooltip));
		bUnset.addSelectionListener(new UnsetBtnSelectionAdapterExtension());

		setBtn = createSetButton();

		updateStack();
		return composite;
	}

	@Override
	protected void applyReadOnly() {
		super.applyReadOnly();
		// specific handling for buttons
		updateButtonVisibility();
	}

	@Override
	protected void applyEnable() {
		super.applyEnable();
		// specific handling for buttons
		updateButtonEnabling();
	}

	/**
	 * Updates the enablement of buttons according to the bound input.
	 */
	protected void updateButtonEnabling() {
		final boolean isEnable = getVElement().isEffectivelyEnabled() && !getVElement().isEffectivelyReadonly();

		if (bUnset != null) {
			bUnset.setEnabled(isEnable);
		}
		if (setBtn != null) {
			setBtn.setEnabled(isEnable);
		}
	}

	/**
	 * Updates the visibility of buttons according to the bound input.
	 */
	protected void updateButtonVisibility() {
		final boolean isVisible = !getVElement().isEffectivelyReadonly() && !isUnchangeableFeature();

		if (bUnset != null) {
			bUnset.setVisible(isVisible);
			GridData.class.cast(bUnset.getLayoutData()).exclude = !isVisible;
		}

		if (setBtn != null) {
			try {
				if (getModelValue() != null) {
					final Object value = getModelValue().getValue();

					if (getDateTimeDisplayType() == DateTimeDisplayType.TIME_ONLY) {
						setBtn.setVisible(isVisible && value == null);
					} else {
						setBtn.setVisible(isVisible);
					}
					GridData.class.cast(setBtn.getLayoutData()).exclude = !setBtn.isVisible();
				}
			} catch (final DatabindingFailedException ex) {
				getReportService().report(new DatabindingFailedReport(ex));
			}
		}

		// Null check to avoid NPE during postInit
		if (composite != null) {
			composite.layout();
		}
	}

	private void updateStack() throws DatabindingFailedException {
		final IObservableValue observableValue = getEMFFormsDatabinding()
			.getObservableValue(getVElement().getDomainModelReference(), getViewModelContext().getDomainModel());
		final EStructuralFeature structuralFeature = (EStructuralFeature) observableValue.getValueType();
		final EObject eObject = (EObject) ((IObserving) observableValue).getObserved();
		observableValue.dispose();
		if (eObject.eIsSet(structuralFeature)) {
			stackLayout.topControl = dateTimeComposite;
		} else {
			stackLayout.topControl = unsetLabel;
		}
	}

	private Button createSetButton() {
		final String imagePath = getDateTimeDisplayType() == DateTimeDisplayType.TIME_ONLY ? "icons/set_feature.png" //$NON-NLS-1$
			: "icons/date.png"; //$NON-NLS-1$
		final Button setBtn = new Button(composite, SWT.PUSH);
		SWTDataElementIdHelper.setElementIdDataWithSubId(setBtn, getVElement(), "set", getViewModelContext()); //$NON-NLS-1$
		GridDataFactory.fillDefaults().grab(false, false).align(SWT.CENTER, SWT.CENTER).applyTo(setBtn);
		setBtn.setImage(
			imageRegistryService.getImage(FrameworkUtil.getBundle(DateTimeControlSWTRenderer.class), imagePath));
		setBtn.setData(CUSTOM_VARIANT, "org_eclipse_emf_ecp_control_dateTime_buttonSet"); //$NON-NLS-1$
		final String tooltip = getDateTimeDisplayType() == DateTimeDisplayType.TIME_ONLY
			? MessageKeys.DateTimeControlSWTRenderer_SelectTime
			: MessageKeys.DateTimeControlSWTRenderer_SelectData;
		setBtn.setToolTipText(getLocalizedString(tooltip));
		setBtn.addSelectionListener(new SetBtnSelectionAdapterExtension(setBtn));
		return setBtn;
	}

	private void createDateTimeWidgets(DateTimeDisplayType dateTimeDisplayType) {
		dateWidget = new DateTime(dateTimeComposite, SWT.DATE | SWT.BORDER);
		SWTDataElementIdHelper.setElementIdDataWithSubId(dateWidget, getVElement(), "date", getViewModelContext()); //$NON-NLS-1$
		dateWidget.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
		dateWidget.setData(CUSTOM_VARIANT, "org_eclipse_emf_ecp_control_dateTime_date"); //$NON-NLS-1$
		timeWidget = new DateTime(dateTimeComposite, SWT.TIME | SWT.SHORT | SWT.BORDER);
		SWTDataElementIdHelper.setElementIdDataWithSubId(timeWidget, getVElement(), "time", getViewModelContext()); //$NON-NLS-1$
		timeWidget.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
		timeWidget.setData(CUSTOM_VARIANT, "org_eclipse_emf_ecp_control_dateTime_time"); //$NON-NLS-1$
		if (dateTimeDisplayType == DateTimeDisplayType.TIME_ONLY) {
			dateWidget.setVisible(false);
			final GridData gridData = (GridData) dateWidget.getLayoutData();
			gridData.exclude = true;

		}
		if (dateTimeDisplayType == DateTimeDisplayType.DATE_ONLY) {
			timeWidget.setVisible(false);
			final GridData gridData = (GridData) timeWidget.getLayoutData();
			gridData.exclude = true;
		}
	}

	/**
	 * {@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) {
		((Composite) control).getChildren()[0].setBackground(validationColor);
		((Composite) control).getChildren()[1].setBackground(validationColor);
	}

	/**
	 * {@inheritDoc}
	 *
	 * @see org.eclipse.emf.ecp.view.spi.core.swt.SimpleControlSWTRenderer#getUnsetText()
	 */
	@Override
	protected String getUnsetText() {
		final String text = getDateTimeDisplayType() == DateTimeDisplayType.TIME_ONLY
			? MessageKeys.DateTimeControl_NoTimeSetClickToSetTime
			: MessageKeys.DateTimeControl_NoDateSetClickToSetDate;
		return getLocalizedString(text);
	}

	private String getLocalizedString(String key) {
		return localizationService.getString(getClass(), key);
	}

	/**
	 * Set button adapter.
	 */
	private class SetBtnSelectionAdapterExtension extends SelectionAdapter {

		private final Button btn;

		/**
		 * @param btn
		 * @param modelValue
		 * @param viewModelContext
		 */
		SetBtnSelectionAdapterExtension(Button btn) {
			this.btn = btn;
		}

		@Override
		public void widgetSelected(SelectionEvent e) {
			if (getDateTimeDisplayType() == DateTimeDisplayType.TIME_ONLY) {
				setTime();
			} else {
				setDate();
			}
		}

		private void setDate() {
			if (dialog != null && !dialog.isDisposed()) {
				dialog.dispose();
				return;
			}
			final IObservableValue modelValue;
try { modelValue = getModelValue(); } catch (final DatabindingFailedException ex) { getReportService().report(new AbstractReport(ex)); return; } dialog = new Shell(btn.getShell(), SWT.NONE); dialog.setLayout(new GridLayout(1, false)); final DateTime calendar = new DateTime(dialog, SWT.CALENDAR | SWT.BORDER); final IObservableValue calendarObserver = WidgetProperties.selection().observe(calendar); final UpdateValueStrategy modelToTarget = new UpdateValueStrategy(UpdateValueStrategy.POLICY_UPDATE); final Binding binding = getDataBindingContext().bindValue(calendarObserver, modelValue, modelToTarget, new DateModelToTargetUpdateStrategy(UpdateValueStrategy.POLICY_UPDATE)); final Calendar defaultCalendar = Calendar.getInstance(getLocale(getViewModelContext())); final Date date = (Date) modelValue.getValue(); if (date != null) { defaultCalendar.setTime(date); } calendar.setDate(defaultCalendar.get(Calendar.YEAR), defaultCalendar.get(Calendar.MONTH), defaultCalendar.get(Calendar.DAY_OF_MONTH)); 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(); updateChangeListener(modelValue.getValue()); } }); dialog.pack(); dialog.layout(); dialog.setLocation(btn.getParent().toDisplay( btn.getLocation().x + btn.getSize().x - dialog.getSize().x, btn.getLocation().y + btn.getSize().y)); dialog.open(); } private void setTime() { try { final EStructuralFeature structuralFeature = (EStructuralFeature) getModelValue().getValueType(); final EObject eObject = (EObject) ((IObserving) getModelValue()).getObserved(); final Command setCommand = SetCommand.create(getEditingDomain(eObject), eObject, structuralFeature, new Date()); getEditingDomain(eObject).getCommandStack().execute(setCommand); updateChangeListener(getModelValue().getValue()); } catch (final DatabindingFailedException ex) { getReportService().report(new DatabindingFailedReport(ex)); } } } /** * Unset button adapter. */ private class UnsetBtnSelectionAdapterExtension extends SelectionAdapter { @Override public void widgetSelected(SelectionEvent e) { try { final EStructuralFeature structuralFeature = (EStructuralFeature) getModelValue().getValueType(); final EObject eObject = (EObject) ((IObserving) getModelValue()).getObserved(); final Command removeCommand = SetCommand.create(getEditingDomain(eObject), eObject, structuralFeature, null); getEditingDomain(eObject).getCommandStack().execute(removeCommand); updateChangeListener(getModelValue().getValue()); } catch (final DatabindingFailedException ex) { getReportService().report(new DatabindingFailedReport(ex)); // Do nothing. This should not happen because if getModelValue() fails, the control will never be // rendered and consequently this code will never be executed. } } } private Locale getLocale(ViewModelContext viewModelContext) { final ViewLocaleService service = viewModelContext.getService(ViewLocaleService.class); if (service == null) { return Locale.getDefault(); } return service.getLocale(); } private void updateChangeListener(Object value) { if (value == null) { if (stackLayout.topControl != unsetLabel) { stackLayout.topControl = unsetLabel; stackComposite.layout(); } } else { if (stackLayout.topControl != dateTimeComposite) { stackLayout.topControl = dateTimeComposite; stackComposite.layout(); } } updateButtonVisibility(); if (!ignoreEnableOnReadOnly()) { applyEnable(); } } private DateTimeDisplayType getDateTimeDisplayType() { if (getVElement() != null && getVElement().getAttachments() != null) { for (final VAttachment attachment : getVElement().getAttachments()) { if (VDateTimeDisplayAttachment.class.isInstance(attachment)) { final VDateTimeDisplayAttachment dateTimeAttachment = (VDateTimeDisplayAttachment) attachment; return dateTimeAttachment.getDisplayType(); } } } return DateTimeDisplayType.TIME_AND_DATE; } /** * {@inheritDoc} * * @see org.eclipse.emf.ecp.view.spi.core.swt.SimpleControlSWTControlSWTRenderer#rootDomainModelChanged() */ @Override protected void rootDomainModelChanged() throws DatabindingFailedException { super.rootDomainModelChanged(); updateStack(); stackComposite.layout(); } }