/*******************************************************************************
* Copyright (c) 2014, 2016 itemis AG and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Alexander Nyßen (itemis AG) - initial API and implementation
*
*******************************************************************************/
package org.eclipse.gef.mvc.fx.parts;
import java.util.List;
import org.eclipse.gef.mvc.fx.viewer.IViewer;
import com.google.common.collect.Multiset;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Bounds;
import javafx.scene.Group;
import javafx.scene.Node;
/**
* The {@link LayeredRootPart} is an {@link IRootPart} that manages a number of
* layers for the visualization, namely, the content layer, feedback layer, and
* handle layer. The visuals of the different {@link IVisualPart}s are inserted
* into these layers depending on their type, i.e. {@link IContentPart} visuals
* are inserted into the content layer, {@link IFeedbackPart} visuals are
* inserted into the feedback layer, and {@link IHandlePart} visuals are
* inserted into the handle layer.
* <p>
* The layers are stacked on top of each other with the content layer at the
* bottom and the handle layer at the top. The feedback layer in the middle is
* mouse transparent, i.e. you cannot interact with the visuals in this layer.
*
* @author anyssen
*
*/
public class LayeredRootPart extends AbstractVisualPart<Group>
implements IRootPart<Group> {
private Group contentLayer;
private Group handleLayer;
private Group feedbackLayer;
/**
* Default constructor.
*/
public LayeredRootPart() {
}
@Override
protected void activateChildren() {
// activate content part children first (which might lead to the
// creation of feedback and handle part children)
for (IContentPart<? extends Node> child : getContentPartChildren()) {
child.activate();
}
// activate remaining children
for (IVisualPart<? extends Node> child : getChildrenUnmodifiable()) {
if (!(child instanceof IContentPart)) {
child.activate();
}
}
}
/**
* Creates the content layer visual.
*
* @return The content layer visual.
*/
protected Group createContentLayer() {
Group contentLayer = createLayer(false);
contentLayer.setPickOnBounds(true);
return contentLayer;
}
/**
* Creates the feedback layer visual.
*
* @return The feedback layer visual.
*/
protected Group createFeedbackLayer() {
return createLayer(true);
}
/**
* Creates the handle layer visual.
*
* @return The handle layer visual.
*/
protected Group createHandleLayer() {
return createLayer(false);
}
/**
* Creates a {@link Group} and sets its {@link Group#pickOnBoundsProperty()}
* to <code>false</code>. Does also set its
* {@link Group#mouseTransparentProperty()} to the given value.
*
* @param mouseTransparent
* The value for the layer's
* {@link Group#mouseTransparentProperty()}.
* @return The created layer.
*/
protected Group createLayer(boolean mouseTransparent) {
Group layer = new Group();
layer.setPickOnBounds(false);
layer.setMouseTransparent(mouseTransparent);
return layer;
}
@Override
protected void deactivateChildren() {
// deactivate content part children first (which might lead to the
// removal of feedback and handle part children)
for (IContentPart<? extends Node> child : getContentPartChildren()) {
child.deactivate();
}
// deactivate remaining children
for (IVisualPart<? extends Node> child : getChildrenUnmodifiable()) {
if (!(child instanceof IContentPart)) {
child.deactivate();
}
}
}
@Override
protected IViewer determineViewer(IVisualPart<? extends Node> parent,
Multiset<IVisualPart<? extends Node>> anchoreds) {
// XXX: The root part is the only part that has a direct link to the
// viewer. It should not be unregistered via the default mechanism, but
// when the viewer is explicitly unset. (see #setAdaptable(IViewer)
// below)
return getViewer();
}
@Override
protected void doAddChildVisual(IVisualPart<? extends Node> child,
int index) {
if (child instanceof IContentPart) {
int contentLayerIndex = 0;
for (int i = 0; i < index; i++) {
if (i < getChildrenUnmodifiable().size()
&& getChildrenUnmodifiable()
.get(i) instanceof IContentPart) {
contentLayerIndex++;
}
}
getContentLayer().getChildren().add(contentLayerIndex,
child.getVisual());
} else if (child instanceof IFeedbackPart) {
int feedbackLayerIndex = 0;
for (int i = 0; i < index; i++) {
if (i < getChildrenUnmodifiable().size()
&& (getChildrenUnmodifiable()
.get(i) instanceof IFeedbackPart)) {
feedbackLayerIndex++;
}
}
getFeedbackLayer().getChildren().add(feedbackLayerIndex,
child.getVisual());
} else {
int handleLayerIndex = 0;
for (int i = 0; i < index; i++) {
if (i < getChildrenUnmodifiable().size()
&& (getChildrenUnmodifiable()
.get(i) instanceof IHandlePart)) {
handleLayerIndex++;
}
}
getHandleLayer().getChildren().add(handleLayerIndex,
child.getVisual());
}
}
@Override
protected void doAttachToAnchorageVisual(
IVisualPart<? extends Node> anchorage, String role) {
throw new UnsupportedOperationException(
"IRootVisualPart does not support this");
}
@Override
protected Group doCreateVisual() {
contentLayer = createContentLayer();
/*
* XXX: The following is a workaround to ensure that visuals do not
* disappear when the content layer is scaled (zooming). This is,
* because computeBounds() on the (lazy) bounds-in-local property of the
* content layer is not performed when the property is invalidated.
*
* We could register an invalidation listener that explicitly triggers
* computeBounds() (by calling get() on the bounds-in-local property),
* to fix the problems. However, this would be invoked too often.
*
* Instead, we register a dummy change listener (that actually does not
* do anything) to fix the problem by means of a side effect. This is
* sufficient to fix the problems, because the JavaFX ExpressionHelper
* (which is responsible of firing the change events) calls getValue()
* on the observable when a change event is to be fired (which is
* triggered when at least one change listener is registered). The
* getValue() call will in turn recompute the bounds (by calling get()
* on the bounds-in-local property, which triggers a call to
* computeBounds()). If no listener is registered, the bounds-in-local
* value will not be recomputed (computeBounds() will not be called)
* even if invalidated.
*/
contentLayer.boundsInLocalProperty()
.addListener(new ChangeListener<Bounds>() {
@Override
public void changed(
ObservableValue<? extends Bounds> observable,
Bounds oldValue, Bounds newValue) {
}
});
feedbackLayer = createFeedbackLayer();
handleLayer = createHandleLayer();
return new Group(contentLayer, feedbackLayer, handleLayer);
}
@Override
protected void doDetachFromAnchorageVisual(
IVisualPart<? extends Node> anchorage, String role) {
throw new UnsupportedOperationException(
"IRootVisualPart does not support this");
}
@Override
protected void doRefreshVisual(Group visual) {
// nothing to do
}
@Override
protected void doRemoveChildVisual(IVisualPart<? extends Node> child,
int index) {
if (child instanceof IContentPart) {
getContentLayer().getChildren().remove(child.getVisual());
} else if (child instanceof IFeedbackPart) {
getFeedbackLayer().getChildren().remove(child.getVisual());
} else {
getHandleLayer().getChildren().remove(child.getVisual());
}
}
/**
* Returns the content layer visual. The content layer visual is created in
* case it was not created before.
*
* @see #createContentLayer()
*
* @return The content layer visual.
*/
public Group getContentLayer() {
if (contentLayer == null) {
doCreateVisual();
}
return contentLayer;
}
@SuppressWarnings("unchecked")
@Override
public List<IContentPart<? extends Node>> getContentPartChildren() {
return PartUtils.filterParts(getChildrenUnmodifiable(),
IContentPart.class);
}
/**
* Returns the feedback layer visual. The feedback layer visual is created
* in case it was not created before.
*
* @see #createFeedbackLayer()
*
* @return The feedback layer visual.
*/
public Group getFeedbackLayer() {
if (feedbackLayer == null) {
doCreateVisual();
}
return feedbackLayer;
}
@SuppressWarnings("unchecked")
@Override
public List<IFeedbackPart<? extends Node>> getFeedbackPartChildren() {
return PartUtils.filterParts(getChildrenUnmodifiable(),
IFeedbackPart.class);
}
/**
* Returns the handle layer visual. The handle layer visual is created in
* case it was not created before.
*
* @see #createHandleLayer()
*
* @return The handle layer visual.
*/
public Group getHandleLayer() {
if (handleLayer == null) {
doCreateVisual();
}
return handleLayer;
}
@SuppressWarnings("unchecked")
@Override
public List<IHandlePart<? extends Node>> getHandlePartChildren() {
return PartUtils.filterParts(getChildrenUnmodifiable(),
IHandlePart.class);
}
@Override
public IRootPart<? extends Node> getRoot() {
return this;
}
}