Skip to content

Content of file SimpleControlSWTRenderer.java

/*******************************************************************************
 * Copyright (c) 2011-2017 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.ArrayList;
import java.util.List;

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.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecp.view.internal.core.swt.Activator;
import org.eclipse.emf.ecp.view.model.common.util.RendererUtil;
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.provider.ECPTooltipModifierHelper;
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.labelwidth.model.VTLabelWidthStyleProperty;
import org.eclipse.emf.ecp.view.template.style.unsettable.model.ButtonAlignmentType;
import org.eclipse.emf.ecp.view.template.style.unsettable.model.ButtonPlacementType;
import org.eclipse.emf.ecp.view.template.style.unsettable.model.VTUnsettableFactory;
import org.eclipse.emf.ecp.view.template.style.unsettable.model.VTUnsettableStyleProperty;
import org.eclipse.emf.edit.command.SetCommand;
import org.eclipse.emf.edit.domain.EditingDomain;
import org.eclipse.emfforms.common.Optional;
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.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.layout.GridDataFactory;
import org.eclipse.jface.layout.GridLayoutFactory;
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.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;

/**
 * @author Eugen
 *
 */
public abstract class SimpleControlSWTRenderer extends AbstractControlSWTRenderer<VControl> {

	/**
	 * {@link ModelChangeListener} which will sync the top-control to the unset state.
	 *
	 * @author Johannes Faltermeier
	 *
	 */
	private static final class UnsetModelChangeListener implements ModelChangeListener {
		private final EObject eObject;
		private final Button unsetButton;
		private final EStructuralFeature structuralFeature;
		private final Control createUnsetLabel;
		private final Composite controlComposite;
		private final StackLayout sl;
		private final Control baseControl;

		private UnsetModelChangeListener(EObject eObject, Button unsetButton, EStructuralFeature structuralFeature,
			Control createUnsetLabel, Composite controlComposite, StackLayout sl, Control baseControl) {
			this.eObject = eObject;
			this.unsetButton = unsetButton;
			this.structuralFeature = structuralFeature;
			this.createUnsetLabel = createUnsetLabel;
			this.controlComposite = controlComposite;
			this.sl = sl;
			this.baseControl = baseControl;
		}

		@Override
		public void notifyChange(ModelChangeNotification notification) {
			updateTopControl();
		}

		void updateTopControl() {
			if (eObject.eIsSet(structuralFeature)) {
				if (getStack().topControl == getBaseControl()) {
					return;
				}
				getStack().topControl = getBaseControl();
				getUnsetButton().setImage(Activator.getImage(ICONS_UNSET_FEATURE));
				getControlComposite().layout(true);
			} else {
				if (getStack().topControl == getUnsetLabel()) {
					return;
				}
				getStack().topControl = getUnsetLabel();
				getUnsetButton().setImage(Activator.getImage(ICONS_SET_FEATURE));
				getControlComposite().layout(true);
			}
		}

		Composite getControlComposite() {
			return controlComposite;
		}

		StackLayout getStack() {
			return sl;
		}

		Control getBaseControl() {
			return baseControl;
		}

		Control getUnsetLabel() {
			return createUnsetLabel;
		}

		Button getUnsetButton() {
			return unsetButton;
		}
	}

	/**
	 * @author Jonas
	 *
	 */
	private final class UnsetSelectionAdapter extends SelectionAdapter {
		private final StackLayout sl;
		private final Button unsetButton;
		private final Control createUnsetLabel;
		private final Control baseControl;
		private final Composite controlComposite;

		/**
		 * @param sl
		 * @param unsetButton
		 * @param createUnsetLabel
		 * @param baseControl
		 * @param controlComposite
		 */
		private UnsetSelectionAdapter(StackLayout sl, Button unsetButton, Control createUnsetLabel, Control baseControl,
			Composite controlComposite) {
			this.sl = sl;
			this.unsetButton = unsetButton;
			this.createUnsetLabel = createUnsetLabel;
			this.baseControl = baseControl;
			this.controlComposite = controlComposite;
		}

		/**
		 * {@inheritDoc}
		 *
		 * @see org.eclipse.swt.events.SelectionAdapter#widgetSelected(org.eclipse.swt.events.SelectionEvent)
		 */
		@Override
		public void widgetSelected(SelectionEvent e) {
			super.widgetSelected(e);
			IObservableValue observableValue;
try { observableValue = getEMFFormsDatabinding() .getObservableValue(getVElement().getDomainModelReference(), getViewModelContext().getDomainModel()); } catch (final DatabindingFailedException ex) { getReportService().report(new DatabindingFailedReport(ex)); return; } final EStructuralFeature structuralFeature = (EStructuralFeature) observableValue.getValueType(); final EObject eObject = (EObject) ((IObserving) observableValue).getObserved(); observableValue.dispose(); Object value = null; final ButtonAlignmentType buttonAlignment = getUnsettableStyleProperty().getButtonAlignment(); final ButtonPlacementType buttonPlacement = getUnsettableStyleProperty().getButtonPlacement(); if (!eObject.eIsSet(structuralFeature)) { sl.topControl = baseControl; unsetButton.setImage(Activator.getImage(ICONS_UNSET_FEATURE)); value = structuralFeature.getDefaultValue(); // adjust space grabbing if the unset button is alignend directly on the right side of the unset label. if (buttonAlignment == ButtonAlignmentType.LEFT && buttonPlacement == ButtonPlacementType.RIGHT_OF_LABEL) { GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL).grab(true, false) .applyTo(controlComposite); } } else { sl.topControl = createUnsetLabel; unsetButton.setImage(Activator.getImage(ICONS_SET_FEATURE)); value = SetCommand.UNSET_VALUE; // re-set standard space grabbing if the unset button was alignend directly on the right side of the // unset label. if (buttonAlignment == ButtonAlignmentType.LEFT && buttonPlacement == ButtonPlacementType.RIGHT_OF_LABEL) { GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL).grab(false, false) .applyTo(controlComposite); } } final EditingDomain editingDomain = getEditingDomain(eObject); editingDomain.getCommandStack().execute( SetCommand.create(editingDomain, eObject, structuralFeature, value)); if (buttonAlignment == ButtonAlignmentType.LEFT) { controlComposite.getParent().layout(); } controlComposite.layout(); } } private static final String UNSET = "unset"; //$NON-NLS-1$ private static final String ICONS_UNSET_FEATURE = "icons/unset_feature.png"; //$NON-NLS-1$ private static final String ICONS_SET_FEATURE = "icons/set_feature.png"; //$NON-NLS-1$ /** * 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} * @since 1.6 */ public SimpleControlSWTRenderer(VControl vElement, ViewModelContext viewContext, ReportService reportService, EMFFormsDatabinding emfFormsDatabinding, EMFFormsLabelProvider emfFormsLabelProvider, VTViewTemplateProvider vtViewTemplateProvider) { super(vElement, viewContext, reportService, emfFormsDatabinding, emfFormsLabelProvider, vtViewTemplateProvider); } private SWTGridDescription rendererGridDescription; private UnsetModelChangeListener unsetModelChangeListener; private Label validationIcon; private Control editControl; private Button unsetButton; /** * {@inheritDoc} * * @see org.eclipse.emfforms.spi.swt.core.AbstractSWTRenderer#getGridDescription(SWTGridDescription) */ @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 = createLabelCell(grid.size()); grid.add(labelCell); } final SWTGridCell validationCell = createValidationCell(grid.size()); grid.add(validationCell); final SWTGridCell controlCel = createControlCell(grid.size()); grid.add(controlCel); rendererGridDescription.setGrid(grid); } return rendererGridDescription; } /** * Creates the label cell if necessary. * * @param column column number within the grid row * @return created and configured label cell * @since 1.9 */ protected SWTGridCell createLabelCell(int column) { return SimpleControlSWTRendererUtil.createLabelCell(column, this, getLabelWidth()); } /** * @return an optional width for the control's label * @since 1.16 */ protected Optional<Integer> getLabelWidth() { final VTLabelWidthStyleProperty styleProperty = RendererUtil.getStyleProperty( getVTViewTemplateProvider(), getVElement(), getViewModelContext(), VTLabelWidthStyleProperty.class); if (styleProperty == null || !styleProperty.isSetWidth()) { return Optional.empty(); } return Optional.of(styleProperty.getWidth()); } /** * Creates the validation cell. * * @param column column number within the grid row * @return created and configured label cell * @since 1.9 */ protected SWTGridCell createValidationCell(int column) { return SimpleControlSWTRendererUtil.createValidationCell(column, this); } /** * Creates the control cell. * * @param column column number within the grid row * @return created and configured label cell * @since 1.9 */ protected SWTGridCell createControlCell(int column) { return SimpleControlSWTRendererUtil.createControlCell(column, this); } /** * {@inheritDoc} * * @see org.eclipse.emfforms.spi.swt.core.AbstractSWTRenderer#renderControl(int, org.eclipse.swt.widgets.Composite, * org.eclipse.emf.ecp.view.spi.model.VElement, org.eclipse.emf.ecp.view.spi.context.ViewModelContext) */ @Override protected final Control renderControl(SWTGridCell gridCell, Composite parent) throws NoRendererFoundException, NoPropertyDescriptorFoundExeption { int controlIndex = gridCell.getColumn(); if (getVElement().getLabelAlignment() == LabelAlignment.NONE) { controlIndex++; } switch (controlIndex) { case 0: return createLabel(parent); case 1: validationIcon = createValidationIcon(parent); return validationIcon; case 2: editControl = createEditControl(parent); return editControl; default: throw new IllegalArgumentException( String .format( "The provided SWTGridCell (%1$s) cannot be used by this (%2$s) renderer.", gridCell.toString(), //$NON-NLS-1$ toString())); } } private Control createEditControl(Composite parent) { try { if (isUnsettable()) { return createUnsettableControl(parent); } final Control control = createControl(parent); setControlIdData(control); return control; } catch (final DatabindingFailedException ex) { getReportService().report(new RenderingFailedReport(ex)); final Label errorLabel = new Label(parent, SWT.NONE); errorLabel.setText(ex.getMessage()); return errorLabel; } } /** * Returns true if the control is unsettable. * * @return true if unsettable, false otherwise * @throws DatabindingFailedException if the databinding fails */ protected boolean isUnsettable() throws DatabindingFailedException { final IValueProperty valueProperty = getEMFFormsDatabinding() .getValueProperty(getVElement().getDomainModelReference(), getViewModelContext().getDomainModel()); final EStructuralFeature feature = (EStructuralFeature) valueProperty.getValueType(); return feature.isUnsettable(); } private Control createUnsettableControl(Composite parent) throws DatabindingFailedException { final Composite composite = new Composite(parent, SWT.NONE); composite.setBackground(parent.getBackground()); GridLayoutFactory.fillDefaults().numColumns(2).equalWidth(false).applyTo(composite); final ButtonPlacementType buttonPlacement = getUnsettableStyleProperty().getButtonPlacement(); final Composite controlComposite; if (buttonPlacement == ButtonPlacementType.RIGHT_OF_LABEL) { controlComposite = new Composite(composite, SWT.NONE); unsetButton = new Button(composite, SWT.PUSH); } else { unsetButton = new Button(composite, SWT.PUSH); controlComposite = new Composite(composite, SWT.NONE); } controlComposite.setBackground(parent.getBackground()); final ButtonAlignmentType buttonAlignment = getUnsettableStyleProperty().getButtonAlignment(); GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL).grab(true, false).applyTo(controlComposite); if (buttonAlignment == ButtonAlignmentType.LEFT && buttonPlacement == ButtonPlacementType.RIGHT_OF_LABEL) { // If the (un)set button is configured to be left aligned and no value is set, // align the button directly next to the unset label. try { IObservableValue observableValue; observableValue = getEMFFormsDatabinding().getObservableValue(getVElement().getDomainModelReference(), getViewModelContext().getDomainModel()); final EStructuralFeature structuralFeature = (EStructuralFeature) observableValue.getValueType(); final EObject eObject = (EObject) ((IObserving) observableValue).getObserved(); if (!eObject.eIsSet(structuralFeature)) { GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL).grab(false, false) .applyTo(controlComposite); } } catch (final DatabindingFailedException ex) { getReportService().report(new DatabindingFailedReport(ex)); } } final StackLayout sl = new StackLayout(); controlComposite.setLayout(sl); final Control baseControl = createControl(controlComposite); setControlIdData(baseControl); final Control createUnsetLabel = createUnsetLabel(controlComposite); SWTDataElementIdHelper.setElementIdDataWithSubId(createUnsetLabel, getVElement(), UNSET, getViewModelContext()); GridDataFactory.fillDefaults().align(SWT.CENTER, SWT.CENTER).grab(false, false).applyTo(unsetButton); unsetButton.addSelectionListener( new UnsetSelectionAdapter(sl, unsetButton, createUnsetLabel, baseControl, controlComposite)); unsetModelChangeListener = registerUnsetStateListener(controlComposite, sl, baseControl, createUnsetLabel, unsetButton); return composite; } private UnsetModelChangeListener registerUnsetStateListener(final Composite controlComposite, final StackLayout sl, final Control baseControl, final Control createUnsetLabel, final Button unsetButton) throws DatabindingFailedException { final EStructuralFeature structuralFeature = (EStructuralFeature) getModelValue().getValueType(); final EObject eObject = (EObject) ((IObserving) getModelValue()).getObserved(); if (eObject.eIsSet(structuralFeature)) { sl.topControl = baseControl; unsetButton.setImage(Activator.getImage(ICONS_UNSET_FEATURE)); } else { sl.topControl = createUnsetLabel; unsetButton.setImage(Activator.getImage(ICONS_SET_FEATURE)); } controlComposite.layout(true); /* There is no UNSET databinding trigger available */ final UnsetModelChangeListener unsetModelChangeListener = new UnsetModelChangeListener(eObject, unsetButton, structuralFeature, createUnsetLabel, controlComposite, sl, baseControl); getViewModelContext().registerDomainChangeListener(unsetModelChangeListener); return unsetModelChangeListener; } private Control createUnsetLabel(Composite parent) { final Composite composite = new Composite(parent, SWT.NONE); GridLayoutFactory.fillDefaults().numColumns(1).equalWidth(true).applyTo(composite); final Label unsetLabel = new Label(composite, SWT.NONE); unsetLabel.setBackground(parent.getBackground()); unsetLabel.setText(getUnsetText()); GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER).grab(true, true).applyTo(unsetLabel); return composite; } /** * Provide the unset text to show on the label when value is unset. * * @return the text to show on the unset label */ protected abstract String getUnsetText(); /** * Set the provided validation color as the background for the provided control. * * @param control the control to set the color on * @param validationColor the validation color to set */ protected void setValidationColor(Control control, Color validationColor) { control.setBackground(validationColor); } /** * Set the provided validation color as the foreground for the provided control. * * @param control the control to set the color on * @param validationColor the validation color to set * @since 1.10 */ protected void setValidationForegroundColor(Control control, Color validationColor) { control.setForeground(validationColor); } @Override protected void setControlEnabled(SWTGridCell gridCell, Control control, boolean enabled) { int controlIndex = gridCell.getColumn(); if (getVElement().getLabelAlignment() == LabelAlignment.NONE) { controlIndex++; } switch (controlIndex) { case 0: case 1: break; default: control.setEnabled(enabled); } } @Override protected final void applyValidation() { Display.getDefault().asyncExec(new Runnable() { @Override public void run() { if (getControls().size() == 0 || getControls().values().iterator().next().isDisposed()) { return; } applyInnerValidation(); } }); } private void applyInnerValidation() { // triggered due to another validation rule before this control is rendered if (validationIcon == null || editControl == null) { return; } // validation rule triggered after the control was disposed if (validationIcon.isDisposed()) { return; } validationIcon.setImage(getValidationIcon()); setValidationColor(editControl, getValidationBackgroundColor()); setValidationForegroundColor(editControl, getValidationForegroundColor()); if (getVElement().getDiagnostic() == null) { validationIcon.setToolTipText(null); } else { validationIcon.setToolTipText(ECPTooltipModifierHelper.modifyString(getVElement().getDiagnostic() .getMessage(), null)); } } /** * Creates the control itself. * * @param parent the {@link Composite} to render onto * @return the rendered control * @throws DatabindingFailedException if the databinding of the control fails */ protected abstract Control createControl(Composite parent) throws DatabindingFailedException; /** * This method will set the element id data on the control created by {@link #createControl(Composite)}. * * @param control the control created by {@link #createControl(Composite)} * @since 1.9 */ protected void setControlIdData(final Control control) { if (control != null) { SWTDataElementIdHelper.setElementIdDataForVControl(control, getVElement(), getViewModelContext()); } } /** * {@inheritDoc} * * @see org.eclipse.emfforms.spi.swt.core.AbstractSWTRenderer#dispose() */ @Override protected void dispose() { rendererGridDescription = null; if (unsetModelChangeListener != null) { getViewModelContext().unregisterDomainChangeListener(unsetModelChangeListener); unsetModelChangeListener = null; } validationIcon = null; editControl = null; super.dispose(); } @Override protected void rootDomainModelChanged() throws DatabindingFailedException { if (unsetModelChangeListener == null) { super.rootDomainModelChanged(); return; } getViewModelContext().unregisterDomainChangeListener(unsetModelChangeListener); unsetModelChangeListener = registerUnsetStateListener( unsetModelChangeListener.getControlComposite(), unsetModelChangeListener.getStack(), unsetModelChangeListener.getBaseControl(), unsetModelChangeListener.getUnsetLabel(), unsetModelChangeListener.getUnsetButton()); super.rootDomainModelChanged(); } /** * Creates the default {@link VTUnsettableStyleProperty}. * * @return the default {@link VTUnsettableStyleProperty} * @since 1.14 */ protected VTUnsettableStyleProperty createDefaultUnsettableStyleProperty() { return VTUnsettableFactory.eINSTANCE.createUnsettableStyleProperty(); } /** * Returns the {@link VTUnsettableStyleProperty}. * * @return the {@link VTUnsettableStyleProperty} * @since 1.14 */ protected VTUnsettableStyleProperty getUnsettableStyleProperty() { VTUnsettableStyleProperty styleProperty = RendererUtil.getStyleProperty(getVTViewTemplateProvider(), getVElement(), getViewModelContext(), VTUnsettableStyleProperty.class); if (styleProperty == null) { styleProperty = createDefaultUnsettableStyleProperty(); } return styleProperty; } @Override protected void applyReadOnly() { super.applyReadOnly(); if (unsetButton != null) { final boolean readonly = getVElement().isEffectivelyReadonly(); unsetButton.setVisible(!readonly); final GridData data = (GridData) unsetButton.getLayoutData(); data.exclude = readonly; unsetButton.getParent().layout(); } } }