blob: 1cb32fa6d09e440ad0a1a470b7175aaebfd73cc4 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2011-2015 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.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());
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);
}
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);
}
}
} catch (final DatabindingFailedException ex) {
getReportService().report(new DatabindingFailedReport(ex));
}
}
}
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();
}
}