Skip to content

Content of file CompoundControlSWTRenderer.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:
 * Johannes Faltermeier - initial API and implementation
 */
package org.eclipse.emf.ecp.view.spi.compoundcontrol.swt;

import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.inject.Inject;

import org.eclipse.core.databinding.DataBindingContext;
import org.eclipse.core.databinding.UpdateValueStrategy;
import org.eclipse.core.databinding.observable.value.IObservableValue;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.emf.databinding.EMFDataBindingContext;
import org.eclipse.emf.databinding.EMFUpdateValueStrategy;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecp.view.model.common.util.RendererUtil;
import org.eclipse.emf.ecp.view.spi.compoundcontrol.model.VCompoundControl;
import org.eclipse.emf.ecp.view.spi.context.ViewModelContext;
import org.eclipse.emf.ecp.view.spi.core.swt.AbstractControlSWTRendererUtil;
import org.eclipse.emf.ecp.view.spi.core.swt.SimpleControlSWTRendererUtil;
import org.eclipse.emf.ecp.view.spi.model.LabelAlignment;
import org.eclipse.emf.ecp.view.spi.model.VContainedElement;
import org.eclipse.emf.ecp.view.spi.model.VControl;
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.layout.LayoutProviderHelper;
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.labelwidth.model.VTLabelWidthStyleProperty;
import org.eclipse.emf.ecp.view.template.style.mandatory.model.VTMandatoryStyleProperty;
import org.eclipse.emfforms.common.Optional;
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.emf.EMFFormsDatabindingEMF;
import org.eclipse.emfforms.spi.core.services.label.EMFFormsLabelProvider;
import org.eclipse.emfforms.spi.core.services.label.NoLabelFoundException;
import org.eclipse.emfforms.spi.swt.core.AbstractSWTRenderer;
import org.eclipse.emfforms.spi.swt.core.EMFFormsNoRendererException;
import org.eclipse.emfforms.spi.swt.core.EMFFormsRendererFactory;
import org.eclipse.emfforms.spi.swt.core.SWTDataElementIdHelper;
import org.eclipse.emfforms.spi.swt.core.layout.GridDescriptionFactory;
import org.eclipse.emfforms.spi.swt.core.layout.SWTGridCell;
import org.eclipse.emfforms.spi.swt.core.layout.SWTGridDescription;
import org.eclipse.jface.databinding.swt.WidgetProperties;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Layout;

/**
 * {@link AbstractSWTRenderer} for the {@link VCompoundControl} view model.
 *
 * @author jfaltermeier
 *
 */
public class CompoundControlSWTRenderer extends AbstractSWTRenderer<VCompoundControl> {

	private static final String SEPARATOR = " / "; //$NON-NLS-1$

	private SWTGridDescription rendererGridDescription;

	private final EMFFormsLabelProvider labelProvider;
	private EMFDataBindingContext dataBindingContext;
	private final EMFFormsRendererFactory rendererFactory;

	private LinkedHashMap<VContainedElement, AbstractSWTRenderer<VElement>> elementRendererMap;

	private final VTViewTemplateProvider viewTemplateProvider;

	private boolean firstControlValidationIconUsed;

	private final EMFFormsDatabindingEMF databindingService;

	/**
	 * Default constructor.
	 *
	 * @param vElement the view model element to be rendered
	 * @param viewContext the view context
	 * @param reportService the {@link ReportService}
	 * @param labelProvider the {@link EMFFormsLabelProvider label provider}
	 * @param rendererFactory the {@link EMFFormsRendererFactory renderer factory}
	 * @param viewTemplateProvider {@link VTViewTemplateProvider}
	 * @param databindingService {@link EMFFormsDatabindingEMF}
	 * @since 1.17
	 */
	@Inject
	public CompoundControlSWTRenderer(
		VCompoundControl vElement,
		ViewModelContext viewContext,
		ReportService reportService,
		EMFFormsLabelProvider labelProvider,
		EMFFormsRendererFactory rendererFactory,
		VTViewTemplateProvider viewTemplateProvider,
		EMFFormsDatabindingEMF databindingService) {
		super(vElement, viewContext, reportService);
		this.labelProvider = labelProvider;
		this.rendererFactory = rendererFactory;
		this.viewTemplateProvider = viewTemplateProvider;
		this.databindingService = databindingService;
	}

	/**
	 * Returns the {@link EMFFormsLabelProvider}.
	 *
	 * @return the label provider
	 * @since 1.8
	 */
	protected EMFFormsLabelProvider getLabelProvider() {
		return labelProvider;
	}

	/**
	 * Returns the {@link EMFFormsRendererFactory}.
	 *
	 * @return the renderer factory
	 * @since 1.8
	 */
	protected EMFFormsRendererFactory getRendererFactory() {
		return rendererFactory;
	}

	/**
	 * @return the {@link EMFFormsDatabindingEMF}.
	 * @since 1.17
	 */
	protected EMFFormsDatabindingEMF getDatabindingService() {
		return databindingService;
	}

	/**
	 * Returns the {@link DataBindingContext}.
	 *
	 * @return the databining context
	 * @since 1.8
	 */
	protected DataBindingContext getDataBindingContext() {
		if (dataBindingContext == null) {
			dataBindingContext = new EMFDataBindingContext();
		}
		return dataBindingContext;
	}

	/**
	 * @return child control to renderer map
	 * @since 1.16
	 */
	protected LinkedHashMap<VContainedElement, AbstractSWTRenderer<VElement>> getElementRendererMap() {
		initChildRendererMap();
		return elementRendererMap;
	}

	private void setElementRendererMap(
		LinkedHashMap<VContainedElement, AbstractSWTRenderer<VElement>> elementRendererMap) {
		this.elementRendererMap = elementRendererMap;
	}

	/**
	 * @return the viewTemplateProvider
	 * @since 1.16
	 */
	protected VTViewTemplateProvider getViewTemplateProvider() {
		return viewTemplateProvider;
	}

	@Override
	public SWTGridDescription getGridDescription(SWTGridDescription gridDescription) {
		if (rendererGridDescription == null) {
			final int columns = SimpleControlSWTRendererUtil
				.showLabel(getVElement(), getReportService(), getClass().getName()) ? 3 : 2;

			rendererGridDescription = GridDescriptionFactory.INSTANCE.createEmptyGridDescription();
			rendererGridDescription.setRows(1);
			rendererGridDescription.setColumns(columns);

			final List<SWTGridCell> grid = new ArrayList<SWTGridCell>();

			if (columns == 3) {
				final SWTGridCell labelCell = SimpleControlSWTRendererUtil
					.createLabelCell(grid.size(), this, getLabelWidth());
				grid.add(labelCell);
			}

			final SWTGridCell validationCell = SimpleControlSWTRendererUtil.createValidationCell(grid.size(), this);
			grid.add(validationCell);

			final SWTGridCell controlCel = SimpleControlSWTRendererUtil.createControlCell(grid.size(), this);
			grid.add(controlCel);

			rendererGridDescription.setGrid(grid);
		}
		return rendererGridDescription;
	}

	@Override
	protected Control renderControl(SWTGridCell cell, Composite parent)
		throws NoRendererFoundException, NoPropertyDescriptorFoundExeption {
		int controlIndex = cell.getColumn();
		if (getVElement().getLabelAlignment() == LabelAlignment.NONE) {
			controlIndex++;
		}
		switch (controlIndex) {
		case 0:
			return createLabel(parent);
		case 1:
			return createValidationIcon(parent);
		case 2:
			return createControls(parent);
		default:
			throw new IllegalArgumentException(
				String.format("The provided SWTGridCell (%1$s) cannot be used by this (%2$s) renderer.", //$NON-NLS-1$
					cell.toString(), toString()));
		}
	}

	/**
	 * Creates the label composite.
	 *
	 * @param parent the parent composite
	 * @return the label
	 * @since 1.8
	 */
	protected Control createLabel(Composite parent) {
		final Label label = new Label(parent, getLabelStyleBits());
		label.setData(CUSTOM_VARIANT, "org_eclipse_emf_ecp_control_label"); //$NON-NLS-1$
		SWTDataElementIdHelper
			.setElementIdDataWithSubId(label, getVElement(), "control_label", getViewModelContext()); //$NON-NLS-1$
		label.setBackground(parent.getBackground());

		final IObservableValue textObservable = WidgetProperties.text().observe(label);
		final Map<IObservableValue, Map.Entry<VControl, EStructuralFeature>> displayNameObservables = getLabelDisplayNameObservables();

		for (final IObservableValue displayNameObservable : displayNameObservables.keySet()) {
			getDataBindingContext().bindValue(
				textObservable,
displayNameObservable, new UpdateValueStrategy(UpdateValueStrategy.POLICY_NEVER), new CompoundControlDisplayNameUpdateValueStrategy(displayNameObservables)); } final IObservableValue tooltipObservable = WidgetProperties.tooltipText().observe(label); final List<IObservableValue> labelDescriptionObservables = getLabelDescriptionObservables(); for (final IObservableValue descriptionObservable : labelDescriptionObservables) { getDataBindingContext().bindValue( tooltipObservable, descriptionObservable, new UpdateValueStrategy(UpdateValueStrategy.POLICY_NEVER), new EMFUpdateValueStrategy() { @Override public Object convert(Object value) { final StringBuilder stringBuilder = new StringBuilder(); for (final IObservableValue obs : labelDescriptionObservables) { if (stringBuilder.length() > 0) { stringBuilder.append("\n"); //$NON-NLS-1$ } stringBuilder.append(obs.getValue()); } return stringBuilder.toString(); } }); } return label; } private Map<IObservableValue, Map.Entry<VControl, EStructuralFeature>> getLabelDisplayNameObservables() { final LinkedHashMap<IObservableValue, Entry<VControl, EStructuralFeature>> displayNames = new LinkedHashMap<IObservableValue, Map.Entry<VControl, EStructuralFeature>>(); for (final VControl control : getVElement().getControls()) { try { final IObservableValue displayName = labelProvider .getDisplayName(control.getDomainModelReference(), getViewModelContext().getDomainModel()); final EStructuralFeature feature = getDatabindingService() .getValueProperty(control.getDomainModelReference(), getViewModelContext().getDomainModel()) .getStructuralFeature(); displayNames.put(displayName, new AbstractMap.SimpleEntry<VControl, EStructuralFeature>(control, feature)); } catch (final NoLabelFoundException ex) { getReportService().report(new AbstractReport(ex, IStatus.WARNING)); } catch (final DatabindingFailedException ex) { getReportService().report(new AbstractReport(ex, IStatus.WARNING)); } } return displayNames; } private List<IObservableValue> getLabelDescriptionObservables() { final List<IObservableValue> labelDescriptionObservables = new ArrayList<IObservableValue>(); for (final VControl control : getVElement().getControls()) { try { labelDescriptionObservables.add(labelProvider.getDescription(control.getDomainModelReference(), getViewModelContext().getDomainModel())); } catch (final NoLabelFoundException ex) { getReportService().report(new AbstractReport(ex, IStatus.WARNING)); } } return labelDescriptionObservables; } /** * @return the style bits for the control's label * @since 1.17 */ protected int getLabelStyleBits() { return AbstractControlSWTRendererUtil .getLabelStyleBits(getViewTemplateProvider(), getVElement(), getViewModelContext()); } /** * @return an optional width for the control's label * @since 1.16 */ protected Optional<Integer> getLabelWidth() { final VTLabelWidthStyleProperty styleProperty = RendererUtil.getStyleProperty( getViewTemplateProvider(), getVElement(), getViewModelContext(), VTLabelWidthStyleProperty.class); if (styleProperty == null || !styleProperty.isSetWidth()) { return Optional.empty(); } return Optional.of(styleProperty.getWidth()); } /** * Return the validation cell of the fist child. If no children or strange first cell, return a dummy validation * icon. * * @param parent the parent to render on * @return the validation control * * @throws NoRendererFoundException this is thrown when a renderer cannot be found * @throws NoPropertyDescriptorFoundExeption this is thrown when no property descriptor can be found * @since 1.17 */ protected Control createValidationIcon(Composite parent) throws NoRendererFoundException, NoPropertyDescriptorFoundExeption { if (getVElement().getControls().isEmpty()) { return createDummyValidationIcon(parent); } final AbstractSWTRenderer<VElement> renderer = getElementRendererMap() .get(getVElement().getControls().get(0)); if (renderer == null) { return createDummyValidationIcon(parent); } final SWTGridDescription gridDescription = renderer .getGridDescription(GridDescriptionFactory.INSTANCE.createEmptyGridDescription()); if (gridDescription.getColumns() < 2) { return createDummyValidationIcon(parent); } /* use child renderer cell */ setFirstControlValidationIconUsed(true); final SWTGridCell validationCell = gridDescription.getGrid().get(0); return validationCell.getRenderer().render(validationCell, parent); } /** * Whether the first cell of the first child control was used to renderer our validation icon. * * @param used <code>true</code> if used, <code>false</code> otherwise * @since 1.17 */ protected void setFirstControlValidationIconUsed(boolean used) { firstControlValidationIconUsed = used; } /** * Creates the validation icon of the first cell of the first child may not be used as the validation icon. * * @param parent the parent * @return the validation icon * @since 1.17 */ protected Control createDummyValidationIcon(Composite parent) { final Label validationLabel = new Label(parent, SWT.NONE); SWTDataElementIdHelper .setElementIdDataWithSubId(validationLabel, getVElement(), "control_validation", getViewModelContext()); //$NON-NLS-1$ validationLabel.setBackground(parent.getBackground()); return validationLabel; } /** * Creates the controls composite. * * @param parent the parent composite * @return the controls * @since 1.8 */ protected Control createControls(Composite parent) { return createControls(parent, firstControlValidationIconUsed ? 1 : 0); } /** * Creates the controls composite. May skip the first cell(s). * * @param parent the parent composite * @param cellsToSkip number of cells to skip * @return the controls * @since 1.16 */ protected Control createControls(Composite parent, int cellsToSkip) { final Composite columnComposite = new Composite(parent, SWT.NONE); columnComposite.setBackground(parent.getBackground()); columnComposite.setLayout(getColumnLayout(getElementRendererMap().size(), false)); for (final VContainedElement child : getElementRendererMap().keySet()) { final AbstractSWTRenderer<VElement> renderer = getElementRendererMap().get(child); final Composite column = new Composite(columnComposite, SWT.NONE); column.setBackground(parent.getBackground()); column.setLayoutData(getSpanningLayoutData(child, 1, 1)); final SWTGridDescription gridDescription = renderer.getGridDescription(GridDescriptionFactory.INSTANCE .createEmptyGridDescription()); final int columns = gridDescription.getColumns() - cellsToSkip; column.setLayout(getColumnLayout(columns < 1 ? 1 : columns, false)); try { for (final SWTGridCell childGridCell : gridDescription.getGrid()) { if (cellsToSkip > 0) { cellsToSkip--; continue; } final Control control = childGridCell.getRenderer().render(childGridCell, column); if (control == null) { continue; } control.setLayoutData(getLayoutData(childGridCell, gridDescription, gridDescription, gridDescription, childGridCell.getRenderer().getVElement(), getViewModelContext().getDomainModel(), control)); } for (final SWTGridCell childGridCell : gridDescription.getGrid()) { childGridCell.getRenderer().finalizeRendering(column); } } catch (final NoPropertyDescriptorFoundExeption e) { getReportService().report(new RenderingFailedReport(e)); continue; } catch (final NoRendererFoundException ex) { getReportService().report(new RenderingFailedReport(ex)); continue; } } return columnComposite; } /** * Initialises the child control to renderer map. * * @since 1.16 */ protected void initChildRendererMap() { if (elementRendererMap != null) { return; } setElementRendererMap(new LinkedHashMap<VContainedElement, AbstractSWTRenderer<VElement>>()); for (final VControl child : getVElement().getControls()) { child.setLabelAlignment(LabelAlignment.NONE); AbstractSWTRenderer<VElement> renderer; try { renderer = getRendererFactory().getRendererInstance(child, getViewModelContext()); } catch (final EMFFormsNoRendererException ex) { getReportService().report( new AbstractReport(ex, String.format("No Renderer for %s found.", child.eClass().getName()))); //$NON-NLS-1$ continue; } getElementRendererMap().put(child, renderer); } } /** * For testing purposes. * * @param numColumns numColumns * @param equalWidth equalWidth * @return ColumnLayout * @since 1.8 */ protected Layout getColumnLayout(int numColumns, boolean equalWidth) { return LayoutProviderHelper.getColumnLayout(numColumns, equalWidth); } /** * For testing purposes. * * @param child the element for which the spanning layout is requested * @param spanX spanX * @param spanY spanY * @return SpanningLayoutData * @since 1.8 */ protected Object getSpanningLayoutData(VContainedElement child, int spanX, int spanY) { return LayoutProviderHelper.getSpanningLayoutData(spanX, spanY); } /** * For testing purposes. * * @param gridCell gridCell * @param controlGridDescription controlGridDescription * @param currentRowGridDescription currentRowGridDescription * @param fullGridDescription fullGridDescription * @param vElement vElement * @param domainModel domainModel * @param control control * @return LayoutData * @since 1.8 */ protected Object getLayoutData(SWTGridCell gridCell, SWTGridDescription controlGridDescription, SWTGridDescription currentRowGridDescription, SWTGridDescription fullGridDescription, VElement vElement, EObject domainModel, Control control) { return LayoutProviderHelper.getLayoutData(gridCell, controlGridDescription, currentRowGridDescription, fullGridDescription, vElement, domainModel, control); } @Override protected void dispose() { if (getDataBindingContext() != null) { getDataBindingContext().dispose(); } super.dispose(); } /** * Model to target for display names which concats all display names. * * @author Johannes Faltermeier * */ private final class CompoundControlDisplayNameUpdateValueStrategy extends EMFUpdateValueStrategy { private final Map<IObservableValue, Entry<VControl, EStructuralFeature>> displayNameObservables; /** * @param displayNameObservables */ private CompoundControlDisplayNameUpdateValueStrategy( Map<IObservableValue, Entry<VControl, EStructuralFeature>> displayNameObservables) { this.displayNameObservables = displayNameObservables; } @Override public Object convert(Object value) { final StringBuilder stringBuilder = new StringBuilder(); for (final Entry<IObservableValue, Map.Entry<VControl, EStructuralFeature>> obs : displayNameObservables .entrySet()) { if (stringBuilder.length() > 0) { stringBuilder.append(SEPARATOR); } stringBuilder.append( getDisplayName(obs.getKey(), obs.getValue().getKey(), obs.getValue().getValue())); } return stringBuilder.toString(); } private Object getDisplayName( final IObservableValue obs, VControl control, EStructuralFeature structuralFeature) { final Object value = obs.getValue(); String extra = ""; //$NON-NLS-1$ final VTMandatoryStyleProperty mandatoryStyle = AbstractControlSWTRendererUtil .getMandatoryStyle(getViewTemplateProvider(), control, getViewModelContext()); if (mandatoryStyle.isHighliteMandatoryFields() && structuralFeature.getLowerBound() > 0) { extra = mandatoryStyle.getMandatoryMarker(); } return value + extra; } } }