/*******************************************************************************
* Copyright (c) 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 & implementation
*
*******************************************************************************/
package org.eclipse.gef.zest.fx.parts;
import java.util.Collections;
import java.util.List;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.gef.common.attributes.IAttributeStore;
import org.eclipse.gef.fx.listeners.VisualChangeListener;
import org.eclipse.gef.geometry.planar.Point;
import org.eclipse.gef.mvc.fx.operations.TransformVisualOperation;
import org.eclipse.gef.mvc.fx.parts.AbstractContentPart;
import org.eclipse.gef.mvc.fx.parts.ITransformableContentPart;
import org.eclipse.gef.mvc.fx.parts.IVisualPart;
import org.eclipse.gef.zest.fx.ZestProperties;
import javafx.collections.MapChangeListener;
import javafx.collections.ObservableMap;
import javafx.geometry.Bounds;
import javafx.geometry.VPos;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.text.Text;
import javafx.scene.transform.Affine;
import javafx.scene.transform.Transform;
import javafx.scene.transform.Translate;
import javafx.util.Pair;
/**
* Abstract base class for external labels, i.e. labels that are not part of the
* visualization of another controller. The standard node label is part of the
* node part's visualization. However, edge labels are implemented as external
* labels, for example.
*
* @author anyssen
*
*/
public abstract class AbstractLabelPart extends AbstractContentPart<Group> implements ITransformableContentPart<Group> {
/**
* The CSS class that is assigned to the visualization of the
* {@link EdgeLabelPart} of this {@link EdgePart}.
*/
public static final String CSS_CLASS_LABEL = "label";
private VisualChangeListener vcl = new VisualChangeListener() {
@Override
protected void boundsInLocalChanged(Bounds oldBounds, Bounds newBounds) {
refreshVisual();
}
@Override
protected void localToParentTransformChanged(Node observed, Transform oldTransform, Transform newTransform) {
refreshVisual();
}
};
private MapChangeListener<String, Object> elementAttributesObserver = new MapChangeListener<String, Object>() {
@Override
public void onChanged(MapChangeListener.Change<? extends String, ? extends Object> change) {
refreshVisual();
}
};
private Text text;
/**
* Computes a position for this label.
*
* @return The computed position for this label in the coordinate system of
* the {@link GraphPart} that contains this label.
*/
public abstract Point computeLabelPosition();
/**
* Creates the text visual.
*
* @return The created {@link Text}.
*/
protected Text createText() {
text = new Text();
text.setTextOrigin(VPos.TOP);
text.setManaged(false);
text.setPickOnBounds(true);
// add css class
text.getStyleClass().add(CSS_CLASS_LABEL);
return text;
}
@Override
protected void doActivate() {
super.doActivate();
getContent().getKey().attributesProperty().addListener(elementAttributesObserver);
}
@Override
protected void doAttachToAnchorageVisual(IVisualPart<? extends Node> anchorage, String role) {
vcl.register(anchorage.getVisual(), getVisual());
}
@Override
protected void doDeactivate() {
getContent().getKey().attributesProperty().removeListener(elementAttributesObserver);
super.doDeactivate();
}
@Override
protected void doDetachFromAnchorageVisual(IVisualPart<? extends Node> anchorage, String role) {
vcl.unregister();
}
@Override
protected List<? extends Object> doGetContentChildren() {
return Collections.emptyList();
}
@SuppressWarnings("unchecked")
@Override
public Pair<? extends IAttributeStore, String> getContent() {
return (Pair<? extends IAttributeStore, String>) super.getContent();
}
@Override
public Affine getContentTransform() {
Point p = getLabelPosition();
if (p == null) {
p = new Point();
}
return new Affine(new Translate(p.x, p.y));
}
/**
* Retrieves the stored position for the label.
*
* @return The label position stored in the attributes.
*/
public Point getLabelPosition() {
String key = getLabelPositionAttributeKey();
ObservableMap<String, Object> attributes = getContent().getKey().getAttributes();
if (!attributes.containsKey(key)) {
return null;
}
return (Point) attributes.get(key);
}
/**
* Retrieves the position attribute key for the given label role.
*
* @return The key via which to retrieve the position attribute for the
* label.
*/
protected String getLabelPositionAttributeKey() {
String labelRole = getContent().getValue();
String attributeKey = null;
if (ZestProperties.EXTERNAL_LABEL__NE.equals(labelRole)) {
attributeKey = ZestProperties.EXTERNAL_LABEL_POSITION__NE;
} else if (ZestProperties.LABEL__NE.equals(labelRole)) {
// node do not have 'internal' labels
attributeKey = ZestProperties.LABEL_POSITION__E;
} else if (ZestProperties.SOURCE_LABEL__E.equals(labelRole)) {
attributeKey = ZestProperties.SOURCE_LABEL_POSITION__E;
} else if (ZestProperties.TARGET_LABEL__E.equals(labelRole)) {
attributeKey = ZestProperties.TARGET_LABEL_POSITION__E;
} else {
throw new IllegalArgumentException("Unsupported content element.");
}
return attributeKey;
}
/**
* Returns the text visual.
*
* @return The {@link Text} used as visual.
*/
protected Text getText() {
return text;
}
/**
* Recomputes the label position.
*/
public void recomputeLabelPosition() {
setLabelPosition(computeLabelPosition());
}
/**
* Adjusts the label's position to fit the given {@link Point}.
*
* @param visual
* This node's visual.
* @param position
* This node's position.
*/
protected void refreshPosition(Node visual, Point position) {
if (position != null) {
// translate using a transform operation
TransformVisualOperation refreshPositionOp = new TransformVisualOperation(this,
new Affine(new Translate(position.x, position.y)));
try {
refreshPositionOp.execute(null, null);
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
@Override
public void setContentTransform(Affine transform) {
setLabelPosition(new Point(transform.getTx(), transform.getTy()));
}
/**
* Sets the stored label position to the given value.
*
* @param computedPosition
* The new label position.
*/
public void setLabelPosition(Point computedPosition) {
getContent().getKey().getAttributes().put(getLabelPositionAttributeKey(), computedPosition);
}
}