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