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()); } } }