blob: cca94500c1bb2509216c3e9c8ef13f6eb1359e3f [file] [log] [blame]
/*
*
* Copyright (c) 2011, 2016 - Loetz GmbH&Co.KG (69115 Heidelberg, Germany)
*
* 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:
* Florian Pirchner - Initial implementation
* Loetz GmbH&Co.KG
*
*/
package org.eclipse.osbp.vaadin.emf.data;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.eclipse.emf.common.notify.AdapterFactory;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.edit.domain.EditingDomain;
import org.eclipse.emf.edit.provider.IChangeNotifier;
import org.eclipse.emf.edit.provider.IItemLabelProvider;
import org.eclipse.emf.edit.provider.INotifyChangedListener;
import org.eclipse.emf.edit.provider.ITreeItemContentProvider;
import org.eclipse.osbp.runtime.web.vaadin.common.resource.IResourceProvider;
import org.eclipse.osbp.vaadin.emf.api.IModelingContext;
import org.eclipse.osbp.vaadin.emf.views.UiSync;
import com.vaadin.data.Item;
import com.vaadin.data.util.HierarchicalContainer;
import com.vaadin.server.Resource;
// TODO: Auto-generated Javadoc
/**
* The base container to provide eObject hierarchy and their values based on
* emf.edit. It registers a change notifier on the model to refresh items on the
* fly.
* <p>
* ATTENTION: This container MUST be disposed!
*/
@SuppressWarnings("serial")
public class EmfModelTreeContainer extends HierarchicalContainer {
/** The Constant ICON. */
private static final String ICON = "icon";
/** The Constant LABEL. */
private static final String LABEL = "label";
/** The root element. */
private EObject rootElement;
/** The editing domain. */
private final EditingDomain editingDomain;
/** The adapter factory. */
private final AdapterFactory adapterFactory;
/** The ui sync. */
private final UiSync uiSync;
/** The resolved. */
private Set<Object> resolved = new HashSet<Object>();
/** The resource provider. */
private IResourceProvider resourceProvider;
/** The change listener. */
private INotifyChangedListener changeListener;
/**
* Instantiates a new emf model tree container.
*
* @param modelingContext
* the modeling context
* @param resourceProvider
* the resource provider
* @param uiSync
* the ui sync
*/
public EmfModelTreeContainer(IModelingContext modelingContext,
IResourceProvider resourceProvider, UiSync uiSync) {
this.resourceProvider = resourceProvider;
this.editingDomain = modelingContext.getEditingDomain();
this.adapterFactory = modelingContext.getAdapterFactory();
this.uiSync = uiSync;
addContainerProperty(LABEL, String.class, "");
addContainerProperty(ICON, Resource.class, null);
IChangeNotifier notifier = (IChangeNotifier) modelingContext
.getAdapterFactory();
notifier.addListener(getModelChangeListener());
}
/**
* Gets the root element.
*
* @return the root element
*/
public EObject getRootElement() {
return rootElement;
}
/**
* Sets the root element.
*
* @param rootElement
* the new root element
*/
public void setRootElement(EObject rootElement) {
if (this.rootElement == rootElement) {
return;
}
removeAllItems();
resolved.clear();
this.rootElement = rootElement;
loadRootElements();
}
/**
* Load root elements.
*/
private void loadRootElements() {
if (rootElement != null) {
doAddItem(null, rootElement, -1);
resolveChildren(rootElement);
}
}
/**
* Resolve children.
*
* @param itemId
* the item id
*/
public void resolveChildren(Object itemId) {
// if children are allowed and they have not been resolved so far
if (areChildrenAllowed(itemId)
&& (getChildren(itemId) == null || getChildren(itemId)
.isEmpty())) {
resolved.add(itemId);
ITreeItemContentProvider parentTreeProvider = (ITreeItemContentProvider) adapterFactory
.adapt(itemId, ITreeItemContentProvider.class);
// resolve children
for (Object newChild : parentTreeProvider.getChildren(itemId)) {
doAddItem(itemId, (EObject) newChild, -1);
}
}
}
/**
* Refreshes the given node.
*
* @param itemId
* the item id
*/
public void refreshNode(Object itemId) {
for (Object child : new ArrayList<>(getChildren(itemId))) {
removeItemRecursively(child);
}
resolved.remove(itemId);
resolveChildren(itemId);
}
/**
* Adds a new item.
*
* @param parent
* the parent
* @param newChild
* the new child
* @param index
* pass -1 for last index
*/
@SuppressWarnings("unchecked")
private void doAddItem(Object parent, EObject newChild, int index) {
if (newChild == null || (parent != null && !containsId(parent))) {
return;
}
Item item;
if (index >= 0) {
item = addItemAt(index, newChild);
} else {
item = addItem(newChild);
}
if(item == null) {
// TODO handle same EObject instances
return;
}
IItemLabelProvider labelProvider = (IItemLabelProvider) adapterFactory
.adapt(newChild, IItemLabelProvider.class);
item.getItemProperty(LABEL).setValue(labelProvider.getText(newChild));
// parse the platform uri
URL url = ((URL) labelProvider.getImage(newChild));
item.getItemProperty(ICON).setValue(
resourceProvider.getResource(url.toString()));
// calculate children allowed
// ITreeItemContentProvider treeProvider = (ITreeItemContentProvider)
// adapterFactory
// .adapt(newChild, ITreeItemContentProvider.class);
// setChildrenAllowed(newChild, treeProvider.hasChildren(newChild));
// if the newChild is not root and the container is available in the
// tree
if (newChild.eContainer() != null && containsId(newChild.eContainer())) {
setParent(newChild, newChild.eContainer());
// the new parent may have children again
// setChildrenAllowed(newChild.eContainer(), true);
}
}
/* (non-Javadoc)
* @see com.vaadin.data.util.HierarchicalContainer#hasChildren(java.lang.Object)
*/
@Override
public boolean hasChildren(Object itemId) {
boolean result = super.hasChildren(itemId);
if (!result) {
// calculate children allowed
ITreeItemContentProvider treeProvider = (ITreeItemContentProvider) adapterFactory
.adapt(itemId, ITreeItemContentProvider.class);
result = treeProvider.hasChildren(itemId);
}
return result;
}
/* (non-Javadoc)
* @see com.vaadin.data.util.HierarchicalContainer#areChildrenAllowed(java.lang.Object)
*/
@Override
public boolean areChildrenAllowed(Object itemId) {
// calculate children allowed
ITreeItemContentProvider treeProvider = (ITreeItemContentProvider) adapterFactory
.adapt(itemId, ITreeItemContentProvider.class);
return treeProvider.hasChildren(itemId);
}
/**
* Gets the model change listener.
*
* @return the model change listener
*/
private INotifyChangedListener getModelChangeListener() {
this.changeListener = e -> handleNotification(e);
return changeListener;
}
/**
* Refreshes the item with the given eObject.
*
* @param notification
* the notification
*/
protected void handleNotification(Notification notification) {
if (notification.isTouch()) {
return;
}
switch (notification.getEventType()) {
case Notification.ADD:
uiSync.sync(new Runnable() {
@Override
public void run() {
if (notification.getNewValue() instanceof EObject) {
EObject addItem = (EObject) notification.getNewValue();
doAddItem(addItem.eContainer(), addItem,
notification.getPosition());
}
}
});
break;
case Notification.REMOVE:
uiSync.sync(new Runnable() {
@Override
public void run() {
if (notification.getOldValue() instanceof EObject) {
EObject removeItem = (EObject) notification
.getOldValue();
removeItemRecursively(removeItem);
}
}
});
break;
case Notification.MOVE:
EObject moveItem = (EObject) notification.getNewValue();
EStructuralFeature feature = moveItem.eContainingFeature();
if (feature.isMany()) {
EObject container = moveItem.eContainer();
// if the parent is not visible, then leave
if (!containsId(container)) {
return;
}
uiSync.sync(new Runnable() {
@SuppressWarnings("unchecked")
@Override
public void run() {
List<EObject> eObjects = (List<EObject>) container
.eGet(feature);
EObject previousSibling = null;
if (notification.getPosition() > 0) {
previousSibling = eObjects.get(notification
.getPosition() - 1);
moveAfterSibling(moveItem, previousSibling);
}
}
});
}
break;
case Notification.SET:
// update the property with the changed value
uiSync.sync(new Runnable() {
@Override
public void run() {
EObject notifier = (EObject) notification.getNotifier();
if (notification.getNewValue() instanceof EObject) {
EObject itemId = (EObject) notification.getNewValue();
// the containment reference was set
if (itemId.eContainingFeature() == notification
.getFeature()) {
removeItem(itemId);
doAddItem(itemId.eContainer(), itemId, -1);
} else {
refreshLabel(notifier);
}
} else {
refreshLabel(notifier);
}
}
@SuppressWarnings({ "unchecked" })
private void refreshLabel(EObject notifier) {
Item item = getItem(notifier);
if (item != null) {
IItemLabelProvider labelProvider = (IItemLabelProvider) adapterFactory
.adapt(notifier, IItemLabelProvider.class);
item.getItemProperty(LABEL).setValue(
labelProvider.getText(notifier));
}
}
});
break;
case Notification.ADD_MANY:
uiSync.sync(new Runnable() {
@SuppressWarnings("unchecked")
@Override
public void run() {
if (notification.getOldValue() instanceof Collection) {
Collection<Object> elements = (Collection<Object>) notification
.getNewValue();
for (Object element : elements) {
EObject addItem = (EObject) element;
doAddItem(addItem.eContainer(), addItem,
notification.getPosition());
}
}
}
});
break;
case Notification.REMOVE_MANY:
uiSync.sync(new Runnable() {
@SuppressWarnings("unchecked")
@Override
public void run() {
if (notification.getOldValue() instanceof Collection) {
Collection<Object> elements = (Collection<Object>) notification
.getOldValue();
for (Object element : elements) {
EObject removeItem = (EObject) element;
removeItemRecursively(removeItem);
}
}
}
});
break;
}
}
/**
* This container MUST be disposed!.
*/
public void dispose() {
IChangeNotifier notifier = (IChangeNotifier) adapterFactory;
notifier.removeListener(changeListener);
}
}