| /** |
| * 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; |
| } |
| } |
| } |