Skip to content

Content of file AbstractControlSWTRenderer.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
 ******************************************************************************/
package org.eclipse.emf.ecp.view.spi.core.swt;

import java.util.LinkedHashMap;
import java.util.Map;

import org.eclipse.core.databinding.DataBindingContext;
import org.eclipse.core.databinding.UpdateValueStrategy;
import org.eclipse.core.databinding.observable.IObserving;
import org.eclipse.core.databinding.observable.value.IObservableValue;
import org.eclipse.core.databinding.property.value.IValueProperty;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.emf.databinding.EMFDataBindingContext;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecp.view.spi.context.ViewModelContext;
import org.eclipse.emf.ecp.view.spi.model.LabelAlignment;
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.VControl;
import org.eclipse.emf.ecp.view.spi.model.VDomainModelReference;
import org.eclipse.emf.ecp.view.spi.model.VElement;
import org.eclipse.emf.ecp.view.spi.renderer.NoPropertyDescriptorFoundExeption;
import org.eclipse.emf.ecp.view.spi.renderer.NoRendererFoundException;
import org.eclipse.emf.ecp.view.spi.swt.reporting.RenderingFailedReport;
import org.eclipse.emf.ecp.view.template.model.VTViewTemplateProvider;
import org.eclipse.emf.ecp.view.template.style.mandatory.model.VTMandatoryStyleProperty;
import org.eclipse.emf.edit.domain.AdapterFactoryEditingDomain;
import org.eclipse.emf.edit.domain.EditingDomain;
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.core.services.label.NoLabelFoundException;
import org.eclipse.emfforms.spi.core.services.structuralchange.EMFFormsStructuralChangeTester;
import org.eclipse.emfforms.spi.core.services.view.RootDomainModelChangeListener;
import org.eclipse.emfforms.spi.swt.core.AbstractSWTRenderer;
import org.eclipse.emfforms.spi.swt.core.EMFFormsControlProcessorService;
import org.eclipse.emfforms.spi.swt.core.SWTDataElementIdHelper;
import org.eclipse.emfforms.spi.swt.core.layout.SWTGridCell;
import org.eclipse.emfforms.spi.swt.core.ui.SWTValidationHelper;
import org.eclipse.emfforms.spi.swt.core.ui.SWTValidationUiService;
import org.eclipse.jface.databinding.swt.WidgetProperties;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;

/**
 * Super class for all kinds of control renderer.
 *
 * @param <VCONTROL> the {@link VControl} of this renderer.
 * @author Eugen Neufeld
 *
 */
public abstract class AbstractControlSWTRenderer<VCONTROL extends VControl> extends AbstractSWTRenderer<VCONTROL>
	implements RootDomainModelChangeListener {

	private SWTValidationHelper swtValidationHelper = SWTValidationHelper.INSTANCE;
	private final EMFFormsDatabinding emfFormsDatabinding;
	private final EMFFormsLabelProvider emfFormsLabelProvider;
	private final VTViewTemplateProvider vtViewTemplateProvider;
	private boolean isDisposed;
	private IObservableValue modelValue;
private final Map<Integer, Color> severityBackgroundColorMap = new LinkedHashMap<Integer, Color>(); private final Map<Integer, Color> severityForegroundColorMap = new LinkedHashMap<Integer, Color>(); private final Map<Integer, Image> severityIconMap = new LinkedHashMap<Integer, Image>(); private final SWTValidationUiService validationUiService; /** * Default constructor. * * @param vElement the view model element to be rendered * @param viewContext the view context * @param emfFormsDatabinding The {@link EMFFormsDatabinding} * @param emfFormsLabelProvider The {@link EMFFormsLabelProvider} * @param reportService The {@link ReportService} * @param vtViewTemplateProvider The {@link VTViewTemplateProvider} * @since 1.6 */ public AbstractControlSWTRenderer(VCONTROL vElement, ViewModelContext viewContext, ReportService reportService, EMFFormsDatabinding emfFormsDatabinding, EMFFormsLabelProvider emfFormsLabelProvider, VTViewTemplateProvider vtViewTemplateProvider) { this(vElement, viewContext, reportService, emfFormsDatabinding, emfFormsLabelProvider, vtViewTemplateProvider, viewContext.getService(SWTValidationUiService.class)); } /** * Additional constructor allowing to specify a custom {@link SWTValidationHelper}. * * @param vElement the view model element to be rendered * @param viewContext the view context * @param emfFormsDatabinding The {@link EMFFormsDatabinding} * @param emfFormsLabelProvider The {@link EMFFormsLabelProvider} * @param reportService The {@link ReportService} * @param vtViewTemplateProvider The {@link VTViewTemplateProvider} * @param swtValidationHelper The {@link SWTValidationHelper} * @since 1.23 */ public AbstractControlSWTRenderer(VCONTROL vElement, ViewModelContext viewContext, ReportService reportService, EMFFormsDatabinding emfFormsDatabinding, EMFFormsLabelProvider emfFormsLabelProvider, VTViewTemplateProvider vtViewTemplateProvider, SWTValidationHelper swtValidationHelper) { this(vElement, viewContext, reportService, emfFormsDatabinding, emfFormsLabelProvider, vtViewTemplateProvider); this.swtValidationHelper = swtValidationHelper; } /** * Default constructor. * * @param vElement the view model element to be rendered * @param viewContext the view context * @param emfFormsDatabinding The {@link EMFFormsDatabinding} * @param emfFormsLabelProvider The {@link EMFFormsLabelProvider} * @param reportService The {@link ReportService} * @param vtViewTemplateProvider The {@link VTViewTemplateProvider} * @param validationUiService The {@link SWTValidationUiService} * @since 1.23 */ public AbstractControlSWTRenderer(VCONTROL vElement, ViewModelContext viewContext, ReportService reportService, EMFFormsDatabinding emfFormsDatabinding, EMFFormsLabelProvider emfFormsLabelProvider, VTViewTemplateProvider vtViewTemplateProvider, SWTValidationUiService validationUiService) { super(vElement, viewContext, reportService); this.emfFormsDatabinding = emfFormsDatabinding; this.emfFormsLabelProvider = emfFormsLabelProvider; this.vtViewTemplateProvider = vtViewTemplateProvider; this.validationUiService = validationUiService; viewModelDBC = new EMFDataBindingContext(); viewContext.registerRootDomainModelChangeListener(this); isDisposed = false; } /** * The {@link EMFFormsDatabinding} to use. * * @return The EMFFormsDatabinding * @since 1.6 */ protected EMFFormsDatabinding getEMFFormsDatabinding() { return emfFormsDatabinding; } /** * The {@link EMFFormsLabelProvider} to use. * * @return The EMFFormsLabelProvider * @since 1.6 */ protected EMFFormsLabelProvider getEMFFormsLabelProvider() { return emfFormsLabelProvider; } /** * The {@link VTViewTemplateProvider} to use. * * @return The VTViewTemplateProvider * @since 1.6 */ protected VTViewTemplateProvider getVTViewTemplateProvider() { return vtViewTemplateProvider; } private DataBindingContext dataBindingContext; private ModelChangeListener modelChangeListener; private final EMFDataBindingContext viewModelDBC; @Override protected void postInit() { super.postInit(); modelChangeListener = new ModelChangeListener() { @Override public void notifyChange(ModelChangeNotification notification) { if (isDisposed) { return; } // Execute applyEnable whenever the structure of the VControl's DMR has been changed. final EMFFormsStructuralChangeTester changeTester = getViewModelContext() .getService(EMFFormsStructuralChangeTester.class); if (changeTester.isStructureChanged(getVElement().getDomainModelReference(), getViewModelContext().getDomainModel(), notification)) { Display.getDefault().asyncExec(() -> { if (!isDisposed) { applyEnable(); } }); } } }; if (getVElement().getDomainModelReference() != null) { getViewModelContext().registerDomainChangeListener(modelChangeListener); } applyEnable(); applyReadOnly(); if (isUnchangeableFeature()) { applyUnchangeableFeature(); } } /** * Checks if the value referenced by the DMR can be changed or not by the user. * * @return <code>true</code> if the value cannot be changed. */ protected boolean isUnchangeableFeature() { final VDomainModelReference ref = getVElement().getDomainModelReference(); if (ref == null) { getReportService() .report(new AbstractReport( String.format("No DomainModelReference could be found for the VElement %1$s.", //$NON-NLS-1$ getVElement().getName()), IStatus.ERROR)); } final EObject eObject = getViewModelContext().getDomainModel(); try { @SuppressWarnings("rawtypes") final IValueProperty valueProperty = getEMFFormsDatabinding().getValueProperty(ref, eObject); return !EStructuralFeature.class.cast(valueProperty.getValueType()).isChangeable(); } catch (final DatabindingFailedException ex) { getReportService().report(new DatabindingFailedReport(ex)); return false; } } @Override public Control render(SWTGridCell cell, Composite parent) throws NoRendererFoundException, NoPropertyDescriptorFoundExeption { final Control control = super.render(cell, parent); if (control == null) { return null; } if (!canHandleControlProcessor()) { defaultHandleControlProcessorForCell(control, cell); } return control; } /** * This method is applied if the control's feature is configured as unchangeable. * The renderer is usually disabled when feature is not changeable. * * @since 1.20 */ protected void applyUnchangeableFeature() { getVElement().setReadonly(true); } /** * <p> * Indicates if the given Control SWT renderer takes the responsibility to call a possibly existing * {@link EMFFormsControlProcessorService} itself. * </p> * <p> * The default implementation returns {@code false}. * </p> * * @return * {@code true} if the Control SWT renderer can handle the {@link EMFFormsControlProcessorService} itself, * {@code false} otherwise. * @since 1.8 */ protected boolean canHandleControlProcessor() { return false; } /** * This method is called by {link {@link #render(SWTGridCell, Composite)} for each * {@code control} and {@code cell} if {@link #canHandleControlProcessor()} returns {@code false}. The default * implementation forwards to {@link #defaultHandleControlProcessor(Control)} if the cell's column is {@code 2}. * * @param control * The {@link Control} which is to be processed by the {@link EMFFormsControlProcessorService}. * @param cell * The {@link SWTGridCell} for the given {@code control}. * @since 1.8 */ protected void defaultHandleControlProcessorForCell(Control control, SWTGridCell cell) { if (cell.getColumn() == 2) { defaultHandleControlProcessor(control); } } /** * Calls a possibly existing {@link EMFFormsControlProcessorService} for the given {@code control}. * * @param control * The {@link Control} which is to be processed by the {@link EMFFormsControlProcessorService}. * @since 1.8 */ protected void defaultHandleControlProcessor(Control control) { if (getViewModelContext().hasService(EMFFormsControlProcessorService.class)) { final EMFFormsControlProcessorService service = getViewModelContext() .getService(EMFFormsControlProcessorService.class); service.process(control, getVElement(), getViewModelContext()); } } @Override protected void dispose() { isDisposed = true; getViewModelContext().unregisterDomainChangeListener(modelChangeListener); getViewModelContext().unregisterRootDomainModelChangeListener(this); modelChangeListener = null; if (dataBindingContext != null) { dataBindingContext.dispose(); dataBindingContext = null; } viewModelDBC.dispose(); if (modelValue != null) { modelValue.dispose(); } super.dispose(); } /** * Returns the validation icon matching the given severity. * * @param severity the severity of the {@link org.eclipse.emf.common.util.Diagnostic} * @return the icon to be displayed, or <code>null</code> when no icon is to be displayed * @deprecated use {@link #getValidationIcon()} for default behavior or use the * {@link SWTValidationUiService} if you need to get the color for a specific diagnostic. */ @Deprecated protected final Image getValidationIcon(int severity) { if (!severityIconMap.containsKey(severity)) { final Image validationIcon = swtValidationHelper.getValidationIcon(severity, getVElement(), getViewModelContext()); severityIconMap.put(severity, validationIcon); } return severityIconMap.get(severity); } /** * Returns the validation icon for the current validation result of this control's {@link VElement}. * * @return the icon to be displayed, or <code>null</code> when no icon is to be displayed * @since 1.23 */ protected final Image getValidationIcon() { return validationUiService.getValidationIcon(getVElement(), getViewModelContext()); } /** * Returns the background color for a control with the given validation severity. * * @param severity severity the severity of the {@link org.eclipse.emf.common.util.Diagnostic} * @return the color to be used as a background color * @deprecated use {@link #getValidationBackgroundColor()} for default behavior or use the * {@link SWTValidationUiService} if you need to get the color for a specific diagnostic. */ @Deprecated protected final Color getValidationBackgroundColor(int severity) { if (isDisposed) { return null; } if (!severityBackgroundColorMap.containsKey(severity)) { final Color validationBackgroundColor = swtValidationHelper .getValidationBackgroundColor(severity, getVElement(), getViewModelContext()); severityBackgroundColorMap.put(severity, validationBackgroundColor); } return severityBackgroundColorMap.get(severity); } /** * Returns the background color for the current validation result of this control's {@link VElement}. * * @return the color to be used as a background color * @since 1.23 */ protected final Color getValidationBackgroundColor() { if (isDisposed) { return null; } return validationUiService.getValidationBackgroundColor(getVElement(), getViewModelContext()); } /** * Returns the foreground color for a control with the given validation severity. * * @param severity severity the severity of the {@link org.eclipse.emf.common.util.Diagnostic} * @return the color to be used as a foreground color * @since 1.10 * @deprecated use {@link #getValidationForegroundColor()} for default behavior or use the * {@link SWTValidationUiService} if you need to get the color for a specific diagnostic. */ @Deprecated protected final Color getValidationForegroundColor(int severity) { if (isDisposed) { return null; } if (!severityForegroundColorMap.containsKey(severity)) { final Color validationForegroundColor = swtValidationHelper .getValidationForegroundColor(severity, getVElement(), getViewModelContext()); severityForegroundColorMap.put(severity, validationForegroundColor); } return severityForegroundColorMap.get(severity); } /** * Returns the foreground color for the current validation result of this control's {@link VElement}. * * @return the color to be used as a foreground color * @since 1.23 */ protected final Color getValidationForegroundColor() { if (isDisposed) { return null; } return validationUiService.getValidationForegroundColor(getVElement(), getViewModelContext()); } /** * Creates a new {@link DataBindingContext}. * * @return a new {@link DataBindingContext} each time this method is called */ protected final DataBindingContext getDataBindingContext() { if (dataBindingContext == null) { dataBindingContext = new EMFDataBindingContext(); } return dataBindingContext; } /** * Returns an {@link IObservableValue} based on the control's domain model reference and domain model. * * @return the {@link IObservableValue} * @throws DatabindingFailedException if the databinding of the domain model object fails. * @since 1.6 */ protected final IObservableValue getModelValue() throws DatabindingFailedException { if (modelValue == null) { final VDomainModelReference ref = getVElement().getDomainModelReference(); if (ref == null) { throw new DatabindingFailedException(String .format( "No DomainModelReference could be found for the VElement %1$s.", getVElement().getName())); //$NON-NLS-1$ } final EObject eObject = getViewModelContext().getDomainModel(); final EMFFormsDatabinding databindingService = getEMFFormsDatabinding(); modelValue = databindingService.getObservableValue(ref, eObject); } return modelValue; } /** * Returns the {@link EditingDomain} for the provided {@link EObject domain model}. * * @param domainModel The provided {@link EObject domain model} * @return The {@link EditingDomain} of this {@link EObject domain model} * @since 1.6 */ protected final EditingDomain getEditingDomain(EObject domainModel) { return AdapterFactoryEditingDomain.getEditingDomainFor(domainModel); } /** * Create the {@link Control} displaying the label of the current {@link VControl}. * * @param parent the {@link Composite} to render onto * @return the created {@link Control} or null */ protected Control createLabel(final Composite parent) { Label label = null; labelRender: if (hasLeftLabelAlignment()) { final VDomainModelReference domainModelReference = getVElement().getDomainModelReference(); final IValueProperty valueProperty; try { valueProperty = getEMFFormsDatabinding().getValueProperty(domainModelReference, getViewModelContext().getDomainModel()); } catch (final DatabindingFailedException ex) { getReportService().report(new RenderingFailedReport(ex)); break labelRender; } catch (final IllegalArgumentException ex) { getReportService().report(new AbstractReport(ex)); break labelRender; } final EMFFormsLabelProvider labelProvider = getEMFFormsLabelProvider(); label = new Label(parent, getLabelStyleBits()); label.setData(CUSTOM_VARIANT, "org_eclipse_emf_ecp_control_label"); //$NON-NLS-1$ SWTDataElementIdHelper.setElementIdDataWithSubId(label, getVElement(), "control_label", //$NON-NLS-1$ getViewModelContext()); label.setBackground(parent.getBackground()); final EObject rootObject = getViewModelContext().getDomainModel(); try { final IObservableValue textObservable = WidgetProperties.text().observe(label); final IObservableValue displayNameObservable = labelProvider.getDisplayName(domainModelReference, rootObject); viewModelDBC.bindValue(textObservable, displayNameObservable, null, new UpdateValueStrategy() { /** * {@inheritDoc} * * @see org.eclipse.core.databinding.UpdateValueStrategy#convert(java.lang.Object) */ @Override public Object convert(Object value) { String extra = ""; //$NON-NLS-1$ final VTMandatoryStyleProperty mandatoryStyle = getMandatoryStyle(); final EStructuralFeature structuralFeature = (EStructuralFeature) valueProperty.getValueType(); if (mandatoryStyle.isHighliteMandatoryFields() && structuralFeature.getLowerBound() > 0) { extra = mandatoryStyle.getMandatoryMarker(); } final String result = (String) super.convert(value); return result + extra; } }); final IObservableValue tooltipObservable = WidgetProperties.tooltipText().observe(label); final IObservableValue descriptionObservable = labelProvider.getDescription(domainModelReference, rootObject); viewModelDBC.bindValue(tooltipObservable, descriptionObservable); } catch (final NoLabelFoundException e) { // FIXME Expectations? getReportService().report(new RenderingFailedReport(e)); } } return label; } /** * @return the style bits for the control's label * @since 1.16 */ protected int getLabelStyleBits() { return AbstractControlSWTRendererUtil .getLabelStyleBits(getVTViewTemplateProvider(), getVElement(), getViewModelContext()); } /** * Whether the label for this control should be rendered on the left of the control. This is the case if the * {@link VControl#getLabelAlignment()} is set to {@link LabelAlignment#LEFT} or {@link LabelAlignment#DEFAULT}. * * @return <code>true</code> if label should be on the left, <code>false</code> otherwise * @since 1.7 */ protected boolean hasLeftLabelAlignment() { return getVElement().getLabelAlignment() == LabelAlignment.LEFT || getVElement().getLabelAlignment() == LabelAlignment.DEFAULT; } private VTMandatoryStyleProperty getMandatoryStyle() { return AbstractControlSWTRendererUtil .getMandatoryStyle(vtViewTemplateProvider, getVElement(), getViewModelContext()); } /** * Creates a validation icon. * * @param composite the {@link Composite} to create onto * @return the created Label */ protected Label createValidationIcon(Composite composite) { final Label validationLabel = new Label(composite, SWT.NONE); SWTDataElementIdHelper.setElementIdDataWithSubId(validationLabel, getVElement(), "control_validation", //$NON-NLS-1$ getViewModelContext()); validationLabel.setBackground(composite.getBackground()); return validationLabel; } /** * {@inheritDoc} * * @see org.eclipse.emfforms.spi.swt.core.AbstractSWTRenderer#applyEnable() * @since 1.6 */ @Override protected void applyEnable() { for (final SWTGridCell gridCell : getControls().keySet()) { try { final boolean observedNotNull = ((IObserving) getModelValue()).getObserved() != null; final boolean enabled = observedNotNull && getVElement().isEffectivelyEnabled(); setControlEnabled(gridCell, getControls().get(gridCell), enabled); if (Boolean.FALSE.equals(enabled)) { getVElement().setDiagnostic(null); } } catch (final DatabindingFailedException ex) { getReportService().report(new DatabindingFailedReport(ex)); setControlEnabled(gridCell, getControls().get(gridCell), false); getVElement().setDiagnostic(null); } } } /** * {@inheritDoc} * * @see org.eclipse.emfforms.spi.core.services.view.RootDomainModelChangeListener#notifyChange() * @since 1.9 */ @Override public void notifyChange() { // TODO correct? - works so far if (modelValue != null) { modelValue.dispose(); modelValue = null; } try { rootDomainModelChanged(); } catch (final DatabindingFailedException ex) { getReportService().report(new AbstractReport(ex, "Could not process the root domain model change.")); //$NON-NLS-1$ } } /** * This method is called in {@link #notifyChange()} when the root domain model of the view model context changes. * * @throws DatabindingFailedException If the databinding failed * @since 1.9 */ protected void rootDomainModelChanged() throws DatabindingFailedException { } /** * {@inheritDoc} * * @see org.eclipse.emfforms.spi.swt.core.AbstractSWTRenderer#applyReadOnly() */ @Override protected void applyReadOnly() { for (final SWTGridCell gridCell : getControls().keySet()) { setControlEnabled(gridCell, getControls().get(gridCell), !getVElement().isEffectivelyReadonly()); } } }