Skip to content

Content of file SectionNodeSWTRenderer.java

/*******************************************************************************
 * Copyright (c) 2011-2013 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.section.swt;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import javax.inject.Inject;

import org.eclipse.core.databinding.beans.PojoProperties;
import org.eclipse.core.databinding.observable.value.IObservableValue;
import org.eclipse.core.databinding.observable.value.WritableValue;
import org.eclipse.emf.databinding.edit.EMFEditObservables;
import org.eclipse.emf.ecp.view.spi.context.ViewModelContext;
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.VElement;
import org.eclipse.emf.ecp.view.spi.model.VViewPackage;
import org.eclipse.emf.ecp.view.spi.section.model.VSection;
import org.eclipse.emf.ecp.view.spi.section.model.VSectionPackage;
import org.eclipse.emf.ecp.view.spi.swt.reporting.RenderingFailedReport;
import org.eclipse.emf.ecp.view.template.model.VTViewTemplateProvider;
import org.eclipse.emf.edit.domain.AdapterFactoryEditingDomain;
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.swt.core.AbstractSWTRenderer;
import org.eclipse.emfforms.spi.swt.core.EMFFormsNoRendererException;
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.GridLayoutFactory;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.ui.forms.events.ExpansionEvent;
import org.eclipse.ui.forms.events.IExpansionListener;
import org.eclipse.ui.forms.widgets.ExpandableComposite;

/**
 * Renderer for {@link VSection} with child items.
 *
 * @author jfaltermeier
 *
 */
public class SectionNodeSWTRenderer extends AbstractSectionSWTRenderer {

	/**
	 * @param vElement the view model element to be rendered
	 * @param viewContext the view context
	 * @param reportService the {@link ReportService}
	 * @param viewTemplateProvider the {@link VTViewTemplateProvider}
	 * @since 1.18
	 */
	@Inject
	public SectionNodeSWTRenderer(VSection vElement, ViewModelContext viewContext, ReportService reportService,
		VTViewTemplateProvider viewTemplateProvider) {
		super(vElement, viewContext, reportService, viewTemplateProvider);
	}

	private Set<AbstractSectionSWTRenderer> childRenderers;
	private SWTGridDescription rendererGridDescription;
	private ModelChangeListener listener;
	private ExpandableComposite expandableComposite;

	@Override
	protected void preInit() {
		super.preInit();
		listener = new ModelChangeListener() {

			@Override
			public void notifyChange(ModelChangeNotification notification) {
				if (notification.getRawNotification().isTouch()) {
					return;
				}
				if (notification.getNotifier() != getVElement()) {
					return;
				}
				if (notification.getStructuralFeature() == VSectionPackage.eINSTANCE
					.getSection_Collapsed()) {
					handleCollapseState();
				}
			}
		};
		getViewModelContext().registerViewChangeListener(listener);
	}

	@Override
	public SWTGridDescription getGridDescription(
		SWTGridDescription gridDescription) {

		rendererGridDescription = new SWTGridDescription();
		childRenderers = new LinkedHashSet<AbstractSectionSWTRenderer>();

		/* get griddescriptions from child sections */
		final List<SWTGridDescription> childGridDescriptions = new ArrayList<SWTGridDescription>();
		for (final VSection item : getVElement().getChildItems()) {
			AbstractSWTRenderer<?> itemRenderer;
			try {
				itemRenderer = getEMFFormsRendererFactory()
					.getRendererInstance(item, getViewModelContext());
			} catch (final EMFFormsNoRendererException ex) {
				getReportService().report(new RenderingFailedReport(ex));
				continue;
			}
			final SWTGridDescription itemGridDescription = itemRenderer
				.getGridDescription(GridDescriptionFactory.INSTANCE
					.createEmptyGridDescription());
			childRenderers.add((AbstractSectionSWTRenderer) itemRenderer);
			childGridDescriptions.add(itemGridDescription);
		}

		/* compute required column count based on self and children */
		final int selfColumns = 1 + getVElement().getChildren().size();
		int columns = selfColumns;
		for (final SWTGridDescription childGridDescription : childGridDescriptions) {
			columns = childGridDescription.getColumns() > columns ? childGridDescription.getColumns() : columns;
		}

		/* create grid description for this renderer */
		rendererGridDescription.setColumns(columns);
		final List<SWTGridCell> gridCells = new ArrayList<SWTGridCell>();
		int emptyCellColumnIndicator = -1;

		/* add self */
		int row = 0;

		/* label */
		final Optional<Integer> labelWidth = getLabelWidth();
		Point prefSize;
		if (labelWidth.isPresent()) {
			prefSize = new Point(labelWidth.get(), SWT.DEFAULT);
		} else {
			prefSize = new Point(SWT.DEFAULT, SWT.DEFAULT);
		}
		int curSelfColumn = 0;
		gridCells.add(createGridCell(row, curSelfColumn++, this, prefSize));
		/* empty columns */
		final int emptyColumns = columns - selfColumns;
		for (int i = 0; i < emptyColumns; i++) {
			gridCells.add(createGridCell(row, emptyCellColumnIndicator--, this));
		}
		/* regular columns */
		for (int columnToAdd = 0; columnToAdd < getVElement().getChildren().size(); columnToAdd++) {
			gridCells.add(createGridCell(row, curSelfColumn++, this));
		}
		row += 1;

		/* add children */
		for (final SWTGridDescription childGridDescription : childGridDescriptions) {
			final SWTGridCell[][] sortedChildGridCells = getArrangedChildGridCells(childGridDescription, columns);
			for (final SWTGridCell[] rowGridCells : sortedChildGridCells) {
				/* There is always at least one column (index) */
				final int currentRow = rowGridCells[0].getRow() + row;
				final AbstractSWTRenderer<?> renderer = rowGridCells[0].getRenderer();
				for (int i = 0; i < rowGridCells.length; i++) {
					final SWTGridCell swtGridCell = rowGridCells[i];
					if (swtGridCell != null) {
						gridCells.add(
							createGridCell(
								currentRow,
								swtGridCell.getColumn(),
								swtGridCell.getRenderer(),
								swtGridCell.getPreferredSize()));
					} else {
						/* create empty column */
						gridCells.add(createGridCell(currentRow, emptyCellColumnIndicator--, renderer));
					}
				}
			}
			row += childGridDescription.getRows();
		}

		rendererGridDescription.setRows(row);
		rendererGridDescription.setGrid(gridCells);
		return rendererGridDescription;
	}

	private SWTGridCell[][] getArrangedChildGridCells(SWTGridDescription childGridDescription, int columns) {
		final SWTGridCell[][] result = new SWTGridCell[childGridDescription.getRows()][columns];
		for (final SWTGridCell swtGridCell : childGridDescription.getGrid()) {
			if (swtGridCell.getColumn() < 0) {
				continue;
			}
			result[swtGridCell.getRow()][swtGridCell.getColumn()] = swtGridCell;
		}
		for (final SWTGridCell[] columnArray : result) {
			shiftElementsToEndOfArrayButFirstElement(columnArray);
		}
		return result;
	}

	private static void shiftElementsToEndOfArrayButFirstElement(SWTGridCell[] columnArray) {
		final int length = columnArray.length;
		for (int i = length - 1; i >= 0; i--) {
			if (columnArray[i] == null) {
				final int index = getIndexToMove(columnArray, i);
				if (index == -1) {
					return;
				}
				columnArray[i] = columnArray[index];
				columnArray[index] = null;
			} else {
				return;
			}
		}
	}

	private static int getIndexToMove(SWTGridCell[] columnArray, int index) {
		for (int i = index - 1; i > 0; i--) {
			if (columnArray[i] != null) {
				return i;
			}
		}
		return -1;
	}

	private SWTGridCell createGridCell(int row, int column,
		AbstractSWTRenderer<? extends VElement> renderer) {
		final SWTGridCell gridCell = new SWTGridCell(row, column, renderer);
		gridCell.setVerticalFill(false);
		gridCell.setVerticalGrab(false);
		if (column == 3) {
			gridCell.setHorizontalGrab(true);
		} else {
			gridCell.setHorizontalGrab(false);
		}
		return gridCell;

	}

	private SWTGridCell createGridCell(int row, int column,
		AbstractSWTRenderer<? extends VElement> renderer, Point prefSize) {
		final SWTGridCell gridCell = createGridCell(row, column, renderer);
		gridCell.setPreferredSize(prefSize);
		return gridCell;
	}

	@Override
	protected Control createFirstColumn(Composite parent) {
		final Composite composite = new Composite(parent, SWT.NONE);
		GridLayoutFactory.fillDefaults().numColumns(1)
			.extendedMargins(computeLeftMargin(), 0, 0, 0)
			.applyTo(composite);

		setExpandableComposite(new ExpandableComposite(
			composite, SWT.NONE, ExpandableComposite.TWISTIE));
		getExpandableComposite().setExpanded(!getVElement().isCollapsed());
		final EditingDomain editingDomain = AdapterFactoryEditingDomain.getEditingDomainFor(getVElement());

		final IObservableValue modelLabelValue = EMFEditObservables.observeValue(
			editingDomain,
			getVElement(),
			VViewPackage.eINSTANCE.getElement_Label());
		final String text = "text"; //$NON-NLS-1$
		final WritableValue value = new WritableValue(text, String.class);
		final IObservableValue textObservable = PojoProperties.value(ExpandableComposite.class, text, String.class)
			.observe(expandableComposite);

		getDataBindingContext().bindValue(textObservable, modelLabelValue);

		initExpandableComposite(getExpandableComposite());

		final IObservableValue modelTooltipValue = EMFEditObservables.observeValue(
			editingDomain,
			getVElement(),
			VViewPackage.eINSTANCE.getHasTooltip_Tooltip());
		final IObservableValue targetTooltipValue = new ExpandableCompositeTooltipProperty()
			.observe(getExpandableComposite());
		getDataBindingContext().bindValue(targetTooltipValue, modelTooltipValue);
return composite; } private void initExpandableComposite(ExpandableComposite expandableComposite) { expandableComposite.addExpansionListener(new IExpansionListener() { @Override public void expansionStateChanging(ExpansionEvent e) { } @Override public void expansionStateChanged(ExpansionEvent e) { getVElement().setCollapsed(!e.getState()); } }); } @Override protected void adjustLayoutData(boolean vis) { super.adjustLayoutData(vis); for (final AbstractSectionSWTRenderer childRenderer : childRenderers) { boolean visible = vis; if (getVElement().isCollapsed()) { visible = false; } childRenderer.adjustLayoutData(visible); } } @Override protected void applyEnable() { getExpandableComposite().setEnabled(getVElement().isEffectivelyEnabled()); } @Override protected void dispose() { getViewModelContext().unregisterViewChangeListener(listener); super.dispose(); } /** * {@inheritDoc} * * @see org.eclipse.emf.ecp.view.spi.section.swt.AbstractSectionSWTRenderer#initCollapseState() * @since 1.6 */ @Override protected void initCollapseState() { /* top root gets current width as width hint so that further resizes will keep the column width intact */ final Iterator<Control> iterator = getControls().values().iterator(); while (iterator.hasNext()) { final Control control = iterator.next(); final int width = control.getSize().x; final Object layoutData = control.getLayoutData(); if (GridData.class.isInstance(layoutData)) { final GridData gridData = (GridData) layoutData; if (gridData == null) { continue; } gridData.widthHint = width; if (iterator.hasNext()) { continue; } gridData.grabExcessHorizontalSpace = true; } } handleCollapseState(); } private void handleCollapseState() { for (final AbstractSectionSWTRenderer childRenderer : childRenderers) { childRenderer.adjustLayoutData(!getVElement() .isCollapsed()); } getControls().values().iterator().next().getParent() .layout(false); getExpandableComposite().setExpanded(!getVElement() .isCollapsed()); } /** * @return the expandableComposite * @since 1.13 */ protected ExpandableComposite getExpandableComposite() { return expandableComposite; } /** * @param expandableComposite the expandableComposite to set * @since 1.13 */ protected void setExpandableComposite(ExpandableComposite expandableComposite) { this.expandableComposite = expandableComposite; } }