blob: 53f069282152b992f6ed10573897895cf9d3a98d [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:
* Edagr Mueller - initial API and implementation
* Eugen Neufeld - Refactoring
* Johannes Faltermeier - Refactoring
******************************************************************************/
package org.eclipse.emf.ecp.view.spi.categorization.swt;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.emf.common.notify.AdapterFactory;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecp.edit.internal.swt.util.OverlayImageDescriptor;
import org.eclipse.emf.ecp.edit.spi.swt.util.SWTValidationHelper;
import org.eclipse.emf.ecp.view.internal.categorization.swt.Activator;
import org.eclipse.emf.ecp.view.spi.categorization.model.VAbstractCategorization;
import org.eclipse.emf.ecp.view.spi.categorization.model.VCategorizableElement;
import org.eclipse.emf.ecp.view.spi.categorization.model.VCategorizationElement;
import org.eclipse.emf.ecp.view.spi.categorization.model.VCategorizationPackage.Literals;
import org.eclipse.emf.ecp.view.spi.categorization.model.VCategory;
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.reporting.StatusReport;
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.edit.provider.ComposedAdapterFactory;
import org.eclipse.emf.edit.provider.ITableItemLabelProvider;
import org.eclipse.emf.edit.ui.provider.AdapterFactoryContentProvider;
import org.eclipse.emf.edit.ui.provider.AdapterFactoryLabelProvider;
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.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.layout.GridDataFactory;
import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TreeSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerFilter;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.custom.ScrolledComposite;
import org.eclipse.swt.custom.TreeEditor;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.TreeEvent;
import org.eclipse.swt.events.TreeListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeColumn;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.swt.widgets.Widget;
/**
* Abstract class for a tree renderer.
*
* @author Eugen Neufeld
*
* @param <VELEMENT> the {@link VElement}
*/
public abstract class AbstractJFaceTreeRenderer<VELEMENT extends VElement> extends AbstractSWTRenderer<VELEMENT> {
private final EMFFormsRendererFactory emfFormsRendererFactory;
private EMFFormsRendererFactory getEMFFormsRendererFactory() {
return emfFormsRendererFactory;
}
/**
* Default constructor.
*
* @param vElement the view model element to be rendered
* @param viewContext the view context
* @param reportService the {@link ReportService}
* @param emfFormsRendererFactory The {@link EMFFormsRendererFactory}
* @since 1.6
*/
public AbstractJFaceTreeRenderer(VELEMENT vElement, ViewModelContext viewContext, ReportService reportService,
EMFFormsRendererFactory emfFormsRendererFactory) {
super(vElement, viewContext, reportService);
this.emfFormsRendererFactory = emfFormsRendererFactory;
}
private SWTGridDescription gridDescription;
private AbstractJFaceTreeRenderer<VELEMENT>.TreeSelectionChangedListener treeSelectionChangedListener;
@Override
public SWTGridDescription getGridDescription(SWTGridDescription gridDescription) {
if (this.gridDescription == null) {
this.gridDescription = GridDescriptionFactory.INSTANCE.createSimpleGrid(1, 1, this);
}
return this.gridDescription;
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emfforms.spi.swt.core.AbstractSWTRenderer#dispose()
*/
@Override
protected void dispose() {
gridDescription = null;
getViewModelContext().unregisterViewChangeListener(treeSelectionChangedListener);
super.dispose();
}
/**
* {@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 Control renderControl(SWTGridCell cell, Composite parent) throws NoRendererFoundException,
NoPropertyDescriptorFoundExeption {
TreeViewer treeViewer;
final EList<VAbstractCategorization> categorizations = getCategorizations();
if (categorizations.size() == 1 && categorizations.get(0) instanceof VCategory) {
final VElement child = getCategorizations().get(0);
AbstractSWTRenderer<VElement> renderer;
try {
renderer = getEMFFormsRendererFactory().getRendererInstance(child,
getViewModelContext());
} catch (final EMFFormsNoRendererException ex) {
getReportService().report(
new StatusReport(
new Status(IStatus.INFO, Activator.PLUGIN_ID, String.format(
"No Renderer for %s found.", child.eClass().getName(), ex)))); //$NON-NLS-1$
return null;
}
final Control render = renderer.render(cell, parent);
renderer.finalizeRendering(parent);
SWTDataElementIdHelper.setElementIdDataWithSubId(render, getVElement(), "vcategory", getViewModelContext()); //$NON-NLS-1$
return render;
}
final Object detailPane = getViewModelContext().getContextValue("detailPane"); //$NON-NLS-1$
if (detailPane != null && Composite.class.isInstance(detailPane)) {
treeViewer = createTreeViewer(parent);
final ScrolledComposite editorComposite = createdEditorPane(Composite.class.cast(detailPane));
setupTreeViewer(treeViewer, editorComposite);
initTreeViewer(treeViewer);
Composite.class.cast(detailPane).layout();
SWTDataElementIdHelper.setElementIdDataWithSubId(treeViewer.getControl(), getVElement(), "tree", //$NON-NLS-1$
getViewModelContext());
return treeViewer.getControl();
}
final SashForm sashForm = new SashForm(parent, SWT.HORIZONTAL);
GridDataFactory.fillDefaults().grab(true, true).align(SWT.FILL, SWT.FILL).applyTo(sashForm);
treeViewer = createTreeViewer(sashForm);
final ScrolledComposite editorComposite = createdEditorPane(sashForm);
setupTreeViewer(treeViewer, editorComposite);
initTreeViewer(treeViewer);
sashForm.setWeights(new int[] { 1, 3 });
SWTDataElementIdHelper.setElementIdDataWithSubId(treeViewer.getControl(), getVElement(), "tree", //$NON-NLS-1$
getViewModelContext());
SWTDataElementIdHelper.setElementIdDataWithSubId(sashForm, getVElement(), "sash", getViewModelContext()); //$NON-NLS-1$
return sashForm;
}
/**
* Creates a {@link TreeViewer}. Sub classes can override to influence the {@link TreeViewer}.
*
* @param parent the parent {@link Composite}
* @return a {@link TreeViewer}
* @since 1.14
*/
protected TreeViewer createTreeViewer(Composite parent) {
return new TreeViewer(parent, SWT.SINGLE);
}
/**
* The list of categorizations to display in the tree.
*
* @return the list of {@link VAbstractCategorization}
*/
protected abstract EList<VAbstractCategorization> getCategorizations();
/**
* The VCategorizationElement to set the current selection onto.
*
* @return the VCategorizationElement
*/
protected abstract VCategorizationElement getCategorizationElement();
/**
* Created editor pane.
*
* @param composite the composite
* @return the created editor composite
*/
protected ScrolledComposite createdEditorPane(Composite composite) {
final ScrolledComposite editorComposite = createScrolledComposite(composite);
editorComposite.setExpandHorizontal(true);
editorComposite.setExpandVertical(true);
editorComposite.setShowFocusedControl(true);
GridDataFactory.fillDefaults().grab(true, true).align(SWT.FILL, SWT.FILL).applyTo(editorComposite);
return editorComposite;
}
/**
* Creates the scrolled composite.
*
* @param parent the parent
* @return the scrolled composite
*/
private ScrolledComposite createScrolledComposite(Composite parent) {
final ScrolledComposite scrolledComposite = new ScrolledComposite(parent, SWT.V_SCROLL | SWT.H_SCROLL
| SWT.BORDER);
scrolledComposite.setShowFocusedControl(true);
scrolledComposite.setExpandVertical(true);
scrolledComposite.setExpandHorizontal(true);
scrolledComposite.setBackground(parent.getBackground());
return scrolledComposite;
}
/**
* Configures the passed tree viewer.
*
* @param treeViewer the {@link TreeViewer} to configure
* @param editorComposite the composite of the editor
*/
protected void setupTreeViewer(final TreeViewer treeViewer,
final ScrolledComposite editorComposite) {
treeViewer.addFilter(new ViewerFilter() {
@Override
public boolean select(Viewer viewer, Object parentElement, Object element) {
return VCategorizableElement.class.isInstance(element) && ((VCategorizableElement) element).isVisible();
}
});
GridDataFactory.fillDefaults().align(SWT.BEGINNING, SWT.FILL).grab(false, true).hint(400, SWT.DEFAULT)
.applyTo(treeViewer.getTree());
final List<TreeEditor> editors = new ArrayList<TreeEditor>();
final ComposedAdapterFactory adapterFactory = new ComposedAdapterFactory(
ComposedAdapterFactory.Descriptor.Registry.INSTANCE);
final AdapterFactoryContentProvider contentProvider = new AdapterFactoryContentProvider(
adapterFactory) {
@Override
public boolean hasChildren(Object object) {
final boolean hasChildren = super.hasChildren(object);
if (hasChildren) {
for (final Object o : getChildren(object)) {
for (final ViewerFilter viewerFilter : treeViewer.getFilters()) {
if (viewerFilter.select(treeViewer, object, o)) {
return true;
}
}
}
}
return false;
}
};
final TreeTableLabelProvider treeTableLabelProvider = getTreeLabelProvider(treeViewer, adapterFactory);
treeViewer.getTree().addDisposeListener(new DisposeListener() {
@Override
public void widgetDisposed(DisposeEvent event) {
contentProvider.dispose();
treeTableLabelProvider.dispose();
adapterFactory.dispose();
}
});
treeViewer.setContentProvider(contentProvider);
treeViewer.setLabelProvider(treeTableLabelProvider);
treeSelectionChangedListener = new TreeSelectionChangedListener(
getViewModelContext(), editorComposite,
getCategorizationElement(),
treeViewer, editors);
treeViewer.addSelectionChangedListener(treeSelectionChangedListener);
getViewModelContext().registerViewChangeListener(treeSelectionChangedListener);
addTreeEditor(treeViewer, getVElement(), editors);
}
/**
* The TreeTableLabel provider.
*
* @param treeViewer the {@link TreeViewer}
* @param adapterFactory the {@link AdapterFactory} to use
* @return the created {@link TreeTableLabelProvider}
* @since 1.9
*/
protected TreeTableLabelProvider getTreeLabelProvider(TreeViewer treeViewer, AdapterFactory adapterFactory) {
return new TreeTableLabelProvider(adapterFactory, treeViewer);
}
/**
* Inits the tree viewer.
*
* @param treeViewer the tree viewer
*/
protected void initTreeViewer(final TreeViewer treeViewer) {
treeViewer.setInput(getVElement());
treeViewer.expandAll();
if (getCategorizationElement().getCurrentSelection() != null) {
treeViewer.setSelection(new StructuredSelection(getCategorizationElement().getCurrentSelection()));
} else if (getCategorizations().size() != 0) {
treeViewer.setSelection(new StructuredSelection(getCategorizations().get(0)));
}
}
/**
* Creates the composite.
*
* @param parent the parent
* @return the composite
*/
private Composite createComposite(Composite parent) {
final Composite composite = new Composite(parent, SWT.NONE);
composite.setBackground(parent.getBackground());
GridLayoutFactory.fillDefaults().numColumns(2).equalWidth(false).margins(7, 7).applyTo(composite);
GridDataFactory.fillDefaults().grab(true, true).align(SWT.FILL, SWT.FILL).applyTo(composite);
return composite;
}
/**
* Adds the tree editor.
*
* @param treeViewer the tree viewer
* @param view the view
* @param editors the list of tree editors
*/
protected void addTreeEditor(final TreeViewer treeViewer, EObject view,
final List<TreeEditor> editors) {
// The text column
final Tree tree = treeViewer.getTree();
int maxActions = 0;
final Iterator<EObject> viewContents = view.eAllContents();
while (viewContents.hasNext()) {
final EObject object = viewContents.next();
if (VAbstractCategorization.class.isInstance(object)) {
final VAbstractCategorization abstractCategorization = (VAbstractCategorization) object;
if (maxActions < abstractCategorization.getActions().size()) {
maxActions = abstractCategorization.getActions().size();
}
}
}
if (maxActions == 0) {
return;
}
final TreeColumn columnText = new TreeColumn(tree, SWT.NONE);
columnText.setWidth(300);
columnText.setAlignment(SWT.FILL);
for (int i = 0; i < maxActions; i++) {
// The column
final TreeColumn column = new TreeColumn(tree, SWT.NONE);
column.setWidth(50);
final TreeEditor editor = new TreeEditor(tree);
// The editor must have the same size as the cell and must
// not be any smaller than 50 pixels.
editor.horizontalAlignment = SWT.CENTER;
editor.grabHorizontal = true;
editor.minimumWidth = 50;
editor.setColumn(i + 1);
editors.add(editor);
}
tree.addTreeListener(new TreeListener() {
@Override
public void treeExpanded(TreeEvent e) {
}
@Override
public void treeCollapsed(TreeEvent e) {
cleanUpTreeEditors(editors);
}
});
}
// Clean up any previous editor control
/**
* Clean up tree editors.
*/
private void cleanUpTreeEditors(List<TreeEditor> editors) {
for (final TreeEditor editor : editors) {
final Control oldEditor = editor.getEditor();
if (oldEditor != null) {
oldEditor.dispose();
}
}
}
/**
* Adds the buttons.
*
* @param treeViewer the tree viewer
* @param treeSelection the tree selection
* @param editors the list of tree editors
*/
protected void addButtons(final TreeViewer treeViewer, TreeSelection treeSelection,
List<TreeEditor> editors) {
cleanUpTreeEditors(editors);
if (treeSelection.getPaths().length == 0) {
return;
}
// Identify the selected row
final TreeItem item = treeViewer.getTree().getSelection()[0];
if (item == null) {
return;
}
final VCategorizableElement object = (VCategorizableElement) treeSelection.getFirstElement();
if (object.getECPActions() == null) {
return;
}
for (int i = 0; i < object.getECPActions().size(); i++) {
final ECPTreeViewAction action = (ECPTreeViewAction) object.getECPActions().get(i);
final TreeEditor editor = editors.get(i);
action.init(treeViewer, treeSelection, editor);
action.execute();
}
}
/**
* The change listener for selections of the tree.
*
* @author Jonas Helming
*
*/
private final class TreeSelectionChangedListener implements ISelectionChangedListener, ModelChangeListener {
private final ViewModelContext viewModelContext;
private final ScrolledComposite editorComposite;
private final VCategorizationElement vCategorizationElement;
private final TreeViewer treeViewer;
private final List<TreeEditor> editors;
private Composite childComposite;
private boolean busy;
private TreeSelectionChangedListener(ViewModelContext viewModelContext,
ScrolledComposite editorComposite, VCategorizationElement vCategorizationElement, TreeViewer treeViewer,
List<TreeEditor> editors) {
this.viewModelContext = viewModelContext;
this.editorComposite = editorComposite;
this.vCategorizationElement = vCategorizationElement;
this.treeViewer = treeViewer;
this.editors = editors;
}
@Override
public void selectionChanged(SelectionChangedEvent event) {
final TreeSelection treeSelection = (TreeSelection) event.getSelection();
final Object selection = treeSelection.getFirstElement();
addButtons(treeViewer, treeSelection, editors);
if (selection == null) {
return;
}
onSelectionChanged(VElement.class.cast(selection));
}
public void onSelectionChanged(VElement child) {
if (busy) {
return;
}
busy = true;
try {
if (childComposite != null) {
childComposite.dispose();
childComposite = null;
}
childComposite = createComposite(editorComposite);
childComposite.setBackground(editorComposite.getBackground());
editorComposite.setContent(childComposite);
try {
AbstractSWTRenderer<VElement> renderer;
try {
renderer = getEMFFormsRendererFactory().getRendererInstance(child,
viewModelContext);
} catch (final EMFFormsNoRendererException ex) {
getReportService().report(
new StatusReport(
new Status(IStatus.INFO, Activator.PLUGIN_ID, String.format(
"No Renderer for %s found.", child.eClass().getName(), ex)))); //$NON-NLS-1$
return;
}
// we have a VCategory-> thus only one element in the grid
final Control render = renderer.render(
renderer.getGridDescription(GridDescriptionFactory.INSTANCE.createEmptyGridDescription())
.getGrid()
.get(0),
childComposite);
renderer.finalizeRendering(childComposite);
GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL).grab(true, true)
.minSize(SWT.DEFAULT, 200)
.applyTo(render);
vCategorizationElement.setCurrentSelection((VCategorizableElement) child);
} catch (final NoRendererFoundException e) {
getReportService().report(new RenderingFailedReport(e));
} catch (final NoPropertyDescriptorFoundExeption e) {
getReportService().report(new RenderingFailedReport(e));
}
childComposite.layout();
final Point point = childComposite.computeSize(SWT.DEFAULT, SWT.DEFAULT);
editorComposite.setMinSize(point);
} finally {
busy = false;
}
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.ecp.view.spi.model.ModelChangeListener#notifyChange(org.eclipse.emf.ecp.view.spi.model.ModelChangeNotification)
*/
@Override
public void notifyChange(ModelChangeNotification notification) {
if (notification.getNotifier() instanceof VCategorizationElement
&& notification.getStructuralFeature() == Literals.CATEGORIZATION_ELEMENT__CURRENT_SELECTION) {
onSelectionChanged((VElement) notification.getNotifier());
}
}
}
/**
* The Class TreeTableLabelProvider.
*/
protected class TreeTableLabelProvider extends AdapterFactoryLabelProvider implements ITableItemLabelProvider {
private final TreeViewer treeViewer;
/**
* Instantiates a new tree table label provider.
*
* @param adapterFactory the adapter factory
* @param treeViewer the tree viewer
* @since 1.9
*/
public TreeTableLabelProvider(AdapterFactory adapterFactory, TreeViewer treeViewer) {
super(adapterFactory);
this.treeViewer = treeViewer;
}
@Override
public String getColumnText(Object object, int columnIndex) {
adjustItemData(object);
String text = super.getColumnText(object, columnIndex);
if (columnIndex == 0 && VCategorizableElement.class.isInstance(object)) {
text = super.getColumnText(((VCategorizableElement) object).getLabelObject(), columnIndex);
}
return text;
}
// XXX is there a better way to get informed when there are new TreeItems and to access the TreeItems?
// the problem is double-edged:
// 1. the content-provider is responsible for updating the tree, but we cannot listen to it
// 2. TreeItems might be removed/recreated without being able to listen for this
// the workaround is to plug in to the label provider. when a treeitem is created/changed it needs a display
// text, so we can access the treeitem and should be called on all relevant changes
private void adjustItemData(Object object) {
if (!VElement.class.isInstance(object)) {
return;
}
final Widget widget = treeViewer.testFindItem(object);
if (widget == null) {
return;
}
if (widget.getData(SWTDataElementIdHelper.ELEMENT_ID_KEY) != null) {
/* already set */
return;
}
final VElement element = VElement.class.cast(object);
SWTDataElementIdHelper.setElementIdDataWithSubId(widget, element, "treeItem", getViewModelContext()); //$NON-NLS-1$
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.edit.ui.provider.AdapterFactoryLabelProvider#getText(java.lang.Object)
*/
@Override
public String getText(Object object) {
adjustItemData(object);
String text;
if (VCategorizableElement.class.isInstance(object)) {
text = super.getText(((VCategorizableElement) object).getLabelObject());
} else {
text = super.getText(object);
}
return text;
}
@Override
public Image getColumnImage(Object object, int columnIndex) {
if (columnIndex != 0) {
return null;
}
Image image = super.getColumnImage(object, columnIndex);
if (VCategorizableElement.class.isInstance(object)) {
image = super.getColumnImage(((VCategorizableElement) object).getLabelObject(), columnIndex);
}
return getValidationOverlay(image, (VElement) object);
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.edit.ui.provider.AdapterFactoryLabelProvider#getImage(java.lang.Object)
*/
@Override
public Image getImage(Object object) {
Image image;
if (VCategorizableElement.class.isInstance(object)) {
image = super.getImage(((VCategorizableElement) object).getLabelObject());
} else {
image = super.getImage(object);
}
return getValidationOverlay(image, (VElement) object);
}
/**
* This method generated an image with a validation overlay if necessary.
*
* @param image the image to overlay
* @param categorization the {@link VElement} to get the validation for
* @return the Image
*/
protected Image getValidationOverlay(Image image, final VElement categorization) {
ImageDescriptor overlay = null;
if (categorization.getDiagnostic() == null) {
return image;
}
overlay = SWTValidationHelper.INSTANCE.getValidationOverlayDescriptor(categorization.getDiagnostic()
.getHighestSeverity(), getVElement(), getViewModelContext());
if (overlay == null) {
return image;
}
final OverlayImageDescriptor imageDescriptor = new OverlayImageDescriptor(image, overlay,
OverlayImageDescriptor.LOWER_RIGHT);
final Image resultImage = imageDescriptor.createImage();
return resultImage;
}
}
}