/*******************************************************************************
* Copyright (c) 2010, 2012 IBM Corporation, Gerhardt Informatics Ktf. 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:
* Research Group Software Construction,
* RWTH Aachen University, Germany - initial API and implementation
* Gerhardt Informatics Ktf.
*/
package org.eclipse.gef.editpolicies;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import org.eclipse.core.runtime.Assert;
import org.eclipse.draw2d.FigureListener;
import org.eclipse.draw2d.GhostImageFigure;
import org.eclipse.draw2d.Graphics;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.IScrollableFigure;
import org.eclipse.draw2d.LayoutListener;
import org.eclipse.draw2d.ScrollPane;
import org.eclipse.draw2d.Viewport;
import org.eclipse.draw2d.ViewportUtilities;
import org.eclipse.draw2d.geometry.Insets;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.gef.ConnectionEditPart;
import org.eclipse.gef.EditPart;
import org.eclipse.gef.GraphicalEditPart;
import org.eclipse.gef.LayerConstants;
import org.eclipse.gef.editparts.IScrollableEditPart;
import org.eclipse.gef.util.EditPartUtilities;
/**
* A {@link SelectionEditPolicy}, which may be registered to an
* {@link IScrollableEditPart} to provide primary selection feedback by
* rendering the hidden contents of the host figure's {@link ScrollPane}'s
* nested {@link Viewport} by means of {@link GhostImageFigure}s.
*
* @author Alexander Nyssen
* @author Philip Ritzkopf
*
* @since 3.6
*/
public class ScrollableSelectionFeedbackEditPolicy extends SelectionEditPolicy {
private int feedbackAlpha = 100;
private final List feedbackFigures = new ArrayList();
private final FigureListener figureListener = new FigureListener() {
public void figureMoved(IFigure source) {
// react on host figure move
if (getHost().getSelected() == EditPart.SELECTED_PRIMARY) {
updateFeedback();
}
}
};
private final LayoutListener layoutListener = new LayoutListener.Stub() {
public void invalidate(IFigure container) {
// react on host figure resize
if (getHost().getSelected() == EditPart.SELECTED_PRIMARY) {
updateFeedback();
}
}
};
private final PropertyChangeListener viewportViewLocationChangeListener = new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent event) {
// Make sure the host edit part is always selected as primary
// selection, when it fires a property change event from its
// viewport
if (event.getSource() == ((IScrollableFigure) getHostFigure())
.getScrollPane().getViewport()
&& getHost().getSelected() != EditPart.SELECTED_PRIMARY) {
getHost().getViewer().deselectAll();
getHost().getViewer().select(getHost());
}
// update feedback in case the viewport's view location changed
if (event.getPropertyName().equals(Viewport.PROPERTY_VIEW_LOCATION)) {
updateFeedback();
}
}
};
/**
* @see org.eclipse.gef.editpolicies.SelectionEditPolicy#activate()
*/
public void activate() {
super.activate();
// register listeners to all viewports in the host figure's path;
// listeners
// to the host figure itself will be registered within showFeedback()
// and
// unregistered within hideFeedback()
for (Iterator iterator = ViewportUtilities.getViewportsPath(
getHostFigureViewport(),
ViewportUtilities.getRootViewport(getHostFigure())).iterator(); iterator
.hasNext();) {
Viewport viewport = (Viewport) iterator.next();
viewport.addPropertyChangeListener(viewportViewLocationChangeListener);
}
}
/**
* Adds a given feedback figure to the feedback layer (using the provided
* bounds to layout it) and registers it in the local
* {@link #feedbackFigures} list.
*
* @param feedbackFigure
* the feedback figure to add to the feedback layer
* @param feedbackFigureAbsoluteBounds
* the absolute bounds used to layout the feedback figure
*/
protected void addFeedbackFigure(IFigure feedbackFigure,
Rectangle feedbackFigureAbsoluteBounds) {
getFeedbackLayer().translateToRelative(feedbackFigureAbsoluteBounds);
getFeedbackLayer().translateFromParent(feedbackFigureAbsoluteBounds);
feedbackFigure.setBounds(feedbackFigureAbsoluteBounds);
addFeedback(feedbackFigure);
feedbackFigures.add(feedbackFigure);
}
/**
* Creates a ghost image feedback figure for the given
* {@link ConnectionEditPart}'s figure and adds it to the feedback layer.
*
* @param connectionEditPart
*/
protected void createConnectionFeedbackFigure(
ConnectionEditPart connectionEditPart) {
addFeedbackFigure(new GhostImageFigure(connectionEditPart.getFigure(),
getAlpha(), getLayer(LayerConstants.CONNECTION_LAYER)
.getBackgroundColor().getRGB()),
getAbsoluteBounds(connectionEditPart.getFigure()));
}
/**
* Creates the connection layer feedback figures.
*/
protected void createConnectionFeedbackFigures() {
HashSet transitiveNestedConnections = EditPartUtilities
.getAllNestedConnectionEditParts((GraphicalEditPart) getHost());
for (Iterator iterator = transitiveNestedConnections.iterator(); iterator
.hasNext();) {
Object connection = iterator.next();
if (connection instanceof ConnectionEditPart) {
createConnectionFeedbackFigure((ConnectionEditPart) connection);
}
}
}
/**
* Creates a ghost image feedback figure for the given
* {@link GraphicalEditPart}'s figure and adds it to the feedback layer.
*
* @param childEditPart
*/
protected void createNodeFeedbackFigure(GraphicalEditPart childEditPart) {
addFeedbackFigure(new GhostImageFigure(childEditPart.getFigure(),
getAlpha(), null), getAbsoluteBounds(childEditPart.getFigure()));
}
/**
* Creates the primary layer feedback figures.
*/
protected void createNodeFeedbackFigures() {
// create ghost feedback for node children
for (Iterator iterator = getHost().getChildren().iterator(); iterator
.hasNext();) {
Object child = iterator.next();
if (child instanceof GraphicalEditPart) {
createNodeFeedbackFigure((GraphicalEditPart) child);
}
}
}
/**
* @see org.eclipse.gef.editpolicies.SelectionEditPolicy#deactivate()
*/
public void deactivate() {
// remove viewport listeners; listener to host figure, which were
// registered during showSelection() will be unregistered during
// hideSelection(), so they do not have to be unregistered here
for (Iterator iterator = ViewportUtilities.getViewportsPath(
getHostFigureViewport(),
ViewportUtilities.getRootViewport(getHostFigure())).iterator(); iterator
.hasNext();) {
Viewport viewport = (Viewport) iterator.next();
viewport.removePropertyChangeListener(viewportViewLocationChangeListener);
}
super.deactivate();
}
/**
* Used to obtain the alpha value used for all feedback figures. The valid
* range is the one documented for {@link Graphics#setAlpha(int)}.
*
* @return the alpha
*/
protected int getAlpha() {
return feedbackAlpha;
}
/**
* @see org.eclipse.gef.editpolicies.SelectionEditPolicy#getFeedbackLayer()
*/
protected IFigure getFeedbackLayer() {
return getLayer(LayerConstants.SCALED_FEEDBACK_LAYER);
}
/**
* Provides access to the host figure's {@link Viewport}.
*
* @return the nested {@link Viewport} of the host figure's
* {@link ScrollPane}
*/
protected Viewport getHostFigureViewport() {
return ((IScrollableFigure) getHostFigure()).getScrollPane()
.getViewport();
}
/**
* Removes all feedback figures from the feedback layer as well as from the
* {@link #feedbackFigures} list.
*/
protected void hideFeedback() {
for (Iterator iterator = feedbackFigures.iterator(); iterator.hasNext();) {
removeFeedback((IFigure) iterator.next());
}
feedbackFigures.clear();
}
/**
* @see org.eclipse.gef.editpolicies.SelectionEditPolicy#hideSelection()
*/
protected void hideSelection() {
// remove figure and layout listeners
getHostFigure().removeLayoutListener(layoutListener);
getHostFigure().removeFigureListener(figureListener);
// hide any still active feedback
hideFeedback();
}
/**
* Used to specify the alpha value used for all feedback figures. The valid
* range is the one documented for {@link Graphics#setAlpha(int)}.
*
* @param alpha
*/
public void setAlpha(int alpha) {
this.feedbackAlpha = alpha;
}
/**
* @see org.eclipse.gef.editpolicies.AbstractEditPolicy#setHost(org.eclipse.gef
* .EditPart)
*/
public void setHost(EditPart host) {
Assert.isLegal(host instanceof IScrollableEditPart);
super.setHost(host);
}
/**
* Creates feedback figures for all node figures nested within the host
* figure's viewport, as well as for all incoming and outgoing connections
* of these nodes. Feedback figures are only created in case there are
* children or connections, which are not fully visible.
*/
protected void showFeedback() {
// ensure primary and connection layer are revalidated,
// so the bounds of all their child figures, which are
// used to calculate the feedback figure constraints,
// are valid
getLayer(LayerConstants.CONNECTION_LAYER).validate();
getLayer(LayerConstants.PRIMARY_LAYER).validate();
// check if there is a node child exceeding the client are
Rectangle clientArea = getAbsoluteClientArea(getHostFigure());
boolean primaryLayerChildExceedsViewport = !clientArea
.equals(getAbsoluteViewportArea(((IScrollableFigure) getHostFigure())
.getScrollPane().getViewport()));
// check if there is a connection exceeding the client area
boolean connectionLayerChildExceedsClientArea = false;
List connectionLayerChildren = getLayer(LayerConstants.CONNECTION_LAYER)
.getChildren();
for (Iterator iterator = connectionLayerChildren.iterator(); iterator
.hasNext() && !connectionLayerChildExceedsClientArea;) {
IFigure connectionLayerChild = (IFigure) iterator.next();
connectionLayerChildExceedsClientArea = (ViewportUtilities
.getNearestEnclosingViewport(connectionLayerChild) == ((IScrollableFigure) getHostFigure())
.getScrollPane().getViewport() && !clientArea.getExpanded(
new Insets(1, 1, 1, 1)).contains(
getAbsoluteBounds(connectionLayerChild)));
}
// Only show feedback if there is a child or connection figure whose
// bounds exceed the client area
if (primaryLayerChildExceedsViewport
|| connectionLayerChildExceedsClientArea) {
createNodeFeedbackFigures();
createConnectionFeedbackFigures();
}
}
/**
* @see org.eclipse.gef.editpolicies.SelectionEditPolicy#showSelection()
*/
protected void showSelection() {
// force ViewportExposeHelper to perform auto scrolling before
// showing the feedback.
getHost().getViewer().reveal(getHost());
updateFeedback();
// register figure and layout listeners needed to update the feedback
// figures upon changes to the host figure.
getHostFigure().addLayoutListener(layoutListener);
getHostFigure().addFigureListener(figureListener);
}
/**
* Removes any existing feedback figures by delegating to
* {@link #hideFeedback()}. In case the host edit part is the primary
* selection, recreates feedback figures via {@link #showFeedback()}.
*/
protected void updateFeedback() {
hideFeedback();
if (getHost().getSelected() == EditPart.SELECTED_PRIMARY) {
showFeedback();
}
}
private static Rectangle getAbsoluteBounds(IFigure figure) {
Rectangle bounds = figure.getBounds().getCopy();
figure.translateToAbsolute(bounds);
return bounds;
}
private static Rectangle getAbsoluteClientArea(IFigure figure) {
Rectangle clientArea = figure.getClientArea().getCopy();
figure.translateToParent(clientArea);
figure.translateToAbsolute(clientArea);
return clientArea;
}
private static Rectangle getAbsoluteViewportArea(Viewport viewport) {
Rectangle viewportParentBounds = viewport.getParent().getBounds()
.getCopy();
int widthMax = viewport.getHorizontalRangeModel().getMaximum();
int widthMin = viewport.getHorizontalRangeModel().getMinimum();
int heightMax = viewport.getVerticalRangeModel().getMaximum();
int heightMin = viewport.getVerticalRangeModel().getMinimum();
viewportParentBounds
.setSize(widthMax - widthMin, heightMax - heightMin);
viewportParentBounds.translate(widthMin, heightMin);
viewportParentBounds.translate(viewport.getViewLocation().getNegated());
viewport.getParent().translateToAbsolute(viewportParentBounds);
return viewportParentBounds;
}
}