blob: 5f0ebdcb0141b74144e8c732c422c32fb3c99f91 [file] [log] [blame]
/**
* 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;
}
}
}