/*******************************************************************************
* 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:
* Matthias Wienand (itemis AG) - initial API and implementation
*
*******************************************************************************/
package org.eclipse.gef.mvc.fx.parts;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.eclipse.gef.common.adapt.AdapterKey;
import org.eclipse.gef.fx.anchors.DynamicAnchor.AnchorageReferenceGeometry;
import org.eclipse.gef.fx.anchors.DynamicAnchor.AnchoredReferencePoint;
import org.eclipse.gef.fx.anchors.IComputationStrategy;
import org.eclipse.gef.fx.anchors.ProjectionStrategy;
import org.eclipse.gef.fx.nodes.Connection;
import org.eclipse.gef.fx.utils.NodeUtils;
import org.eclipse.gef.geometry.planar.IGeometry;
import org.eclipse.gef.geometry.planar.Line;
import org.eclipse.gef.geometry.planar.Point;
import com.google.common.reflect.TypeToken;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Provider;
import javafx.scene.Node;
import javafx.scene.paint.Color;
/**
*
* @author mwienand
*
*/
public class DefaultSelectionFeedbackPartFactory
implements IFeedbackPartFactory {
/**
* The binding name for the primary selection color.
*/
public static final String PRIMARY_SELECTION_FEEDBACK_COLOR_PROVIDER = "PRIMARY_SELECTION_FEEDBACK_COLOR_PROVIDER";
/**
* Defines the default {@link Color} for primary selection feedback.
*/
public static final Color DEFAULT_PRIMARY_SELECTION_FEEDBACK_COLOR = Color
.web("#3f51b5");
/**
* The binding name for the secondary selection color.
*/
public static final String SECONDARY_SELECTION_FEEDBACK_COLOR_PROVIDER = "SECONDARY_SELECTION_FEEDBACK_COLOR_PROVIDER";
/**
* Defines the default {@link Color} for secondary selection feedback.
*/
public static final Color DEFAULT_SECONDARY_SELECTION_FEEDBACK_COLOR = Color
.web("#9fa8da");
/**
* The role name for the <code>Provider<IGeometry></code> that will be
* used to generate selection feedback.
*/
public static final String SELECTION_FEEDBACK_GEOMETRY_PROVIDER = "SELECTION_FEEDBACK_GEOMETRY_PROVIDER";
/**
* The role name for the <code>Provider<IGeometry></code> that will be
* used to generate selection link feedback.
*/
public static final String SELECTION_LINK_FEEDBACK_GEOMETRY_PROVIDER = "SELECTION_LINK_FEEDBACK_GEOMETRY_PROVIDER";
@Inject
private Injector injector;
@SuppressWarnings("serial")
@Override
public List<IFeedbackPart<? extends Node>> createFeedbackParts(
List<? extends IVisualPart<? extends Node>> targets,
Map<Object, Object> contextMap) {
// check that we have targets
if (targets == null || targets.isEmpty()) {
throw new IllegalArgumentException(
"Part factory is called without targets.");
}
// single selection, create selection feedback based on geometry
List<IFeedbackPart<? extends Node>> feedbackParts = new ArrayList<>();
// selection outline feedback
final IVisualPart<? extends Node> target = targets.iterator().next();
final Provider<? extends IGeometry> selectionFeedbackGeometryProvider = target
.getAdapter(AdapterKey
.get(new TypeToken<Provider<? extends IGeometry>>() {
}, SELECTION_FEEDBACK_GEOMETRY_PROVIDER));
if (selectionFeedbackGeometryProvider != null) {
Provider<IGeometry> geometryInSceneProvider = new Provider<IGeometry>() {
@Override
public IGeometry get() {
return NodeUtils.localToScene(target.getVisual(),
selectionFeedbackGeometryProvider.get());
}
};
SelectionFeedbackPart selectionFeedbackPart = injector
.getInstance(SelectionFeedbackPart.class);
selectionFeedbackPart.setGeometryProvider(geometryInSceneProvider);
feedbackParts.add(selectionFeedbackPart);
}
// selection link feedback parts
for (IVisualPart<? extends Node> t : targets) {
if (!t.getAnchoragesUnmodifiable().isEmpty()) {
for (Entry<IVisualPart<? extends Node>, String> entry : t
.getAnchoragesUnmodifiable().entries()) {
IVisualPart<? extends Node> anchorage = entry.getKey();
String role = entry.getValue();
if (anchorage instanceof IVisualPart) {
final Provider<? extends IGeometry> anchorageGeometryProvider = anchorage
.getAdapter(AdapterKey.get(
new TypeToken<Provider<? extends IGeometry>>() {
},
SELECTION_LINK_FEEDBACK_GEOMETRY_PROVIDER));
final Provider<? extends IGeometry> anchoredGeometryProvider = t
.getAdapter(AdapterKey.get(
new TypeToken<Provider<? extends IGeometry>>() {
},
SELECTION_LINK_FEEDBACK_GEOMETRY_PROVIDER));
if (anchoredGeometryProvider != null
&& anchorageGeometryProvider != null) {
// show link feedback if geometry providers can be
// retrieved for both.
IFeedbackPart<? extends Node> anchorLinkFeedbackPart = createLinkFeedbackPart(
anchorage, anchorageGeometryProvider, t,
anchoredGeometryProvider, role);
if (anchorLinkFeedbackPart != null) {
feedbackParts.add(anchorLinkFeedbackPart);
}
}
}
}
}
}
return feedbackParts;
}
/**
* Creates a feedback-line between an anchored part and its anchorage.
*
* @param anchorageLinkFeedbackGeometryProvider
* The geometry provider of the anchorage part.
* @param anchored
* The anchored {@link IVisualPart}.
* @param anchoredLinkFeedbackGeometryProvider
* The link feedback provider of the anchored part.
* @param anchorage
* The anchorage {@link IVisualPart}. The role under which the
* anchorage is stored at the anchored.
* @param role
* The role under which the anchored is attached to the
* anchorage.
* @return The {@link IFeedbackPart} for this anchor link, or
* <code>null</code> if no feedback should be rendered.
*/
protected IFeedbackPart<? extends Node> createLinkFeedbackPart(
final IVisualPart<? extends Node> anchorage,
Provider<? extends IGeometry> anchorageLinkFeedbackGeometryProvider,
final IVisualPart<? extends Node> anchored,
Provider<? extends IGeometry> anchoredLinkFeedbackGeometryProvider,
String role) {
// only show link feedback when anchored is no connection
if (!(anchored.getVisual() instanceof Connection)) {
Provider<IGeometry> linkFeedbackGeometryProvider = new Provider<IGeometry>() {
// TODO (#471628): inject; maybe extend IComputationStrategy
// interface
private final ProjectionStrategy computationStrategy = new ProjectionStrategy();
private Point computePosition(Node n1, IGeometry n1Geometry,
Node n2, IGeometry n2Geometry) {
Point n2RefPoint = n2Geometry.getBounds().getCenter();
// TODO: let computation strategy initialize the
// parameters, then populate them
Set<IComputationStrategy.Parameter<?>> parameters = new HashSet<>();
parameters.add(new AnchorageReferenceGeometry(n1Geometry));
parameters.add(new AnchoredReferencePoint(n2RefPoint));
return computationStrategy.computePositionInScene(n1, n2,
parameters);
}
@Override
public IGeometry get() {
// get anchored visual and geometry
Node anchoredVisual = anchored.getVisual();
IGeometry anchoredGeometryInLocal = anchoredLinkFeedbackGeometryProvider
.get();
// get anchorage visual and geometry
Node anchorageVisual = anchorage.getVisual();
IGeometry anchorageGeometryInLocal = anchorageLinkFeedbackGeometryProvider
.get();
// determine link source point
Point sourcePointInScene = computePosition(anchoredVisual,
anchoredGeometryInLocal, anchorageVisual,
anchorageGeometryInLocal);
// determine link target point
Point targetPointInScene = computePosition(anchorageVisual,
anchorageGeometryInLocal, anchoredVisual,
anchoredGeometryInLocal);
// construct link line
return new Line(sourcePointInScene, targetPointInScene);
}
};
SelectionLinkFeedbackPart part = injector
.getInstance(SelectionLinkFeedbackPart.class);
part.setGeometryProvider(linkFeedbackGeometryProvider);
return part;
}
return null;
}
}