blob: 368d26795e7e7861c1a28d4a83664d5e6cedcb90 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2006 Sybase, Inc. 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:
* Sybase, Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.jst.pagedesigner.editpolicies;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.core.runtime.Assert;
import org.eclipse.draw2d.ColorConstants;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.PositionConstants;
import org.eclipse.draw2d.RectangleFigure;
import org.eclipse.draw2d.geometry.Insets;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.PrecisionRectangle;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.gef.DragTracker;
import org.eclipse.gef.EditPart;
import org.eclipse.gef.GraphicalEditPart;
import org.eclipse.gef.LayerConstants;
import org.eclipse.gef.Request;
import org.eclipse.gef.RequestConstants;
import org.eclipse.gef.SharedCursors;
import org.eclipse.gef.commands.Command;
import org.eclipse.gef.editpolicies.ResizableEditPolicy;
import org.eclipse.gef.handles.NonResizableHandleKit;
import org.eclipse.gef.requests.ChangeBoundsRequest;
import org.eclipse.gef.requests.LocationRequest;
import org.eclipse.gef.requests.SelectionRequest;
import org.eclipse.gef.tools.SelectEditPartTracker;
import org.eclipse.jst.pagedesigner.commands.single.ChangeStyleCommand;
import org.eclipse.jst.pagedesigner.css2.ICSSStyle;
import org.eclipse.jst.pagedesigner.css2.layout.BlockBox;
import org.eclipse.jst.pagedesigner.css2.layout.CSSFigure;
import org.eclipse.jst.pagedesigner.dom.EditModelQuery;
import org.eclipse.jst.pagedesigner.parts.ElementEditPart;
import org.eclipse.jst.pagedesigner.parts.NodeEditPart;
import org.eclipse.jst.pagedesigner.requests.LocationModifierRequest;
import org.eclipse.jst.pagedesigner.tools.ObjectModeDragTracker;
import org.eclipse.jst.pagedesigner.tools.RangeDragTracker;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Cursor;
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMElement;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
/**
* @author mengbo
* @version 1.5
*/
public class ElementResizableEditPolicy extends ResizableEditPolicy implements IEnhancedSelectionEditPolicy
{
private static final Insets INSETS_1 = new Insets(1, 1, 1, 1);
private static final int THRESHHOLD = 3;
// the number of pixels to offset the top left of tooltop feedback
// below the current mouse cursor location
private static final int TOOLTIP_VERTICAL_OFFSET = 25;
private static final Insets INSETS_CONST = new Insets(THRESHHOLD,
THRESHHOLD, THRESHHOLD, THRESHHOLD);
private boolean _showLabelFeedback = true;
private IFigure[] _hoverFeedbackFigure;
//private NonVisualChildDecorator _selectionDecoratorNorthWest; // = null;
private MouseSelectableChildDecorator _nonVisualChildDecorator; // = null;
private final static Color HOVER_FEEDBACK_COLOR = ColorConstants.blue;
public void deactivate()
{
super.deactivate();
if (_nonVisualChildDecorator != null)
{
_nonVisualChildDecorator.dispose();
_nonVisualChildDecorator = null;
}
}
/*
* (non-Javadoc)
*
* @see org.eclipse.gef.editpolicies.AbstractEditPolicy#showTargetFeedback(org.eclipse.gef.Request)
*/
public void showTargetFeedback(Request request) {
if (RequestConstants.REQ_SELECTION_HOVER.equals(request.getType())) {
if (_hoverFeedbackFigure != null) {
for (int i = 0; i < _hoverFeedbackFigure.length; i++) {
removeFeedback(_hoverFeedbackFigure[i]);
}
_hoverFeedbackFigure = null;
}
// <gripe>this is what I hate about GEF, if it's a location dependent
// request why aren't we guaranteed a LocationRequest?!
// even GEF interal code protects casts by checking getType()
// rather than an instanceof!</gripe>
Assert.isTrue(request instanceof LocationRequest);
// don't show tooltip if drag is active
_showLabelFeedback = !((NodeEditPart)getHost()).isDragActive();
_hoverFeedbackFigure = showHoverFeedback((LocationRequest)request);
} else {
super.showTargetFeedback(request);
}
}
/*
* (non-Javadoc)
*
* @see org.eclipse.gef.editpolicies.AbstractEditPolicy#eraseTargetFeedback(org.eclipse.gef.Request)
*/
public void eraseTargetFeedback(Request request) {
if (RequestConstants.REQ_SELECTION_HOVER.equals(request.getType())) {
if (_hoverFeedbackFigure != null) {
for (int i = 0; i < _hoverFeedbackFigure.length; i++) {
removeFeedback(_hoverFeedbackFigure[i]);
}
_hoverFeedbackFigure = null;
getNonVisualChildDecorator().updateState(MouseSelectableChildDecorator.EVENT_HOST_HOVER_LOST);
}
} else {
super.eraseTargetFeedback(request);
}
}
/**
* @param request
*/
private IFigure[] showHoverFeedback(LocationRequest request) {
if (!shouldUseObjectMode(request) && !isStyleTags(getHost())) {
return null;
}
final IFigure figure = this.getHostFigure();
Rectangle[] rects;
if (figure instanceof CSSFigure) {
rects = ((CSSFigure) figure).getFragmentsBounds();
} else {
rects = new Rectangle[] { figure.getBounds() };
}
int figureSize = rects.length;
if (_showLabelFeedback)
{
figureSize++;
}
IFigure[] figures = new IFigure[figureSize];
for (int i = 0; i < rects.length; i++) {
RectangleFigure fig = new RectangleFigure();
fig.setFill(false);
fig.setOutline(true);
fig.setLineWidth(1);
fig.setForegroundColor(HOVER_FEEDBACK_COLOR);
addFeedback(fig);
Rectangle r = rects[i].getCopy();
figure.translateToAbsolute(r);
fig.translateToRelative(r);
fig.setBounds(r);
figures[i] = fig;
}
if (_showLabelFeedback)
{
getNonVisualChildDecorator().updateState(MouseSelectableChildDecorator.EVENT_HOST_HOVER_RECEIVED);
BasicLabelToolTip label = new BasicLabelToolTip(getTooltipText());
addFeedback(label);
// use mouse cursor plus an offset so the tooltip doesn't
// appear z-ordered below the mouse cursor
AbsolutePointLocator locator = AbsolutePointLocator.getInstance();
locator.setReferencePoint(request.getLocation(), 0, TOOLTIP_VERTICAL_OFFSET);
// to avoid enlargemeent of the feedback layer
locator.setIntersectFigure(getFeedbackLayer());
locator.relocate(label);
figures[rects.length] = label;
}
return figures;
}
private String getTooltipText() {
Element element = (Element) this.getHost().getModel();
StringBuffer text = new StringBuffer(element.getTagName());
return text.toString();
}
private boolean isStyleTags(EditPart part) {
if (part != null && part.getModel() instanceof Node) {
return EditModelQuery.HTML_STYLE_NODES.contains(((Node) part
.getModel()).getNodeName());
}
return false;
}
private MouseSelectableChildDecorator getNonVisualChildDecorator()
{
if (_nonVisualChildDecorator == null)
{
_nonVisualChildDecorator =
new MouseSelectableChildDecorator((GraphicalEditPart)getHost()
, PositionConstants.NORTH_EAST
, getLayer(LayerConstants.FEEDBACK_LAYER)
, getLayer(LayerConstants.HANDLE_LAYER));
}
return _nonVisualChildDecorator;
}
/**
* @param request
* @return true if we should be using object mode for this request
*/
public boolean shouldUseObjectMode(Request request) {
ElementEditPart part = (ElementEditPart) this.getHost();
if (isStyleTags(part)) {
return false;
}
if (part.isWidget()
|| (!part.canHaveDirectTextChild() && !part
.haveNonWhitespaceTextChild())) {
return true;
}
if (request instanceof SelectionRequest
&& ((SelectionRequest) request).isControlKeyPressed()) {
return true;
}
if (request instanceof LocationModifierRequest
&& ((LocationModifierRequest) request).isControlKeyPressed()) {
return true;
}
// for other elements
if (request instanceof LocationRequest) {
Point location = ((LocationRequest) request).getLocation()
.getCopy();
part.getFigure().translateToRelative(location);
return shouldUseObjectMode(location);
}
return false; // should not happen
}
/**
* @param location
* @return
*/
private boolean shouldUseObjectMode(Point location) {
// when the location is close to the border/padding of the element, then
// we think it is default to
// object mode selection.
CSSFigure figure = (CSSFigure) this.getHostFigure();
if (figure.getFragmentsBounds().length != 1) {
return false;
}
Rectangle bounds = figure.getBounds().getCopy();
Insets insets = figure.getInsets();
bounds.crop(insets);
if (insets.top > THRESHHOLD && insets.left > THRESHHOLD
&& insets.right > THRESHHOLD && insets.bottom > THRESHHOLD) {
return !bounds.contains(location);
}
// since the figure insets could be 0, so we expand it a little, thus
// even the point is
// a little inside the content area, we still think it is selection the
// object.
if (bounds.height < 3 * THRESHHOLD || bounds.width < 3 * THRESHHOLD) {
bounds.crop(INSETS_1);
} else {
bounds.crop(INSETS_CONST);
}
return !bounds.contains(location);
}
/*
* (non-Javadoc)
*
* @see org.eclipse.gef.editpolicies.ResizableEditPolicy#createSelectionHandles()
*/
protected List createSelectionHandles() {
// we have three different kinds of handles.
// 1. Those element that is resizable.
// 2. Those element that is rectangle but not resizable.
// 3. Those element that is not rectangle (fragments)
IFigure figure = this.getHostFigure();
if (figure instanceof CSSFigure && getHost() instanceof ElementEditPart) {
CSSFigure cssfigure = (CSSFigure) figure;
List fragments = cssfigure.getFragmentsForRead();
// XXX: only one fragment and is blockbox, then we think it is
// resizable by figure
// should move this test to somewhere else.
if (fragments != null && fragments.size() == 1
&& fragments.get(0) instanceof BlockBox) {
if (((ElementEditPart) getHost()).isResizable()) {
// super is Resizable policy, will create a resize handles.
return super.createSelectionHandles();
}
return createNonResizeHandles();
}
return createFragmentsHandles();
}
// second case
return createNonResizeHandles();
}
/**
* @return
*/
private List createFragmentsHandles() {
List list = new ArrayList();
list.add(new FragmentHandle((GraphicalEditPart) getHost()));
return list;
}
/**
* @return
*/
private List createNonResizeHandles() {
// following code copied from NonResizableEditPolicy
List list = new ArrayList();
if (isDragAllowed()) {
NonResizableHandleKit.addHandles((GraphicalEditPart) getHost(),
list);
} else {
NonResizableHandleKit.addHandles((GraphicalEditPart) getHost(),
list, new SelectEditPartTracker(getHost()),
SharedCursors.ARROW);
}
return list;
}
protected void hideSelection() {
super.hideSelection();
// handle removing the menu bar handle separately because it will decide
// when to remove itself (not removeSelectionHandles)
getNonVisualChildDecorator().updateState(MouseSelectableChildDecorator.EVENT_HOST_SELECTION_LOST);
}
protected void showSelection() {
super.showSelection();
// handle adding the menu bar handle separately because it will decide
// when to remove itself (not removeSelectionHandles
getNonVisualChildDecorator().updateState(MouseSelectableChildDecorator.EVENT_HOST_SELECTION_RECEIVED);
}
/**
* child class could override this method.
*
* @param element
* @param width
* @param height
* @return the resize command or null if none
*/
protected Command getResizeCommand(IDOMElement element, int width,
int height) {
Map map = new HashMap();
if (width > 0) {
map.put("width", width + "px"); //$NON-NLS-1$ //$NON-NLS-2$
}
if (height > 0) {
map.put("height", height + "px"); //$NON-NLS-1$ //$NON-NLS-2$
}
if (!map.isEmpty()) {
return new ChangeStyleCommand(element, map);
}
return null;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.gef.editpolicies.ResizableEditPolicy#getResizeCommand(org.eclipse.gef.requests.ChangeBoundsRequest)
*/
protected Command getResizeCommand(ChangeBoundsRequest request) {
ElementEditPart part = (ElementEditPart) this.getHost();
Rectangle rect = part.getFigure().getBounds();
rect = request.getTransformedRectangle(rect);
int width = rect.width;
int height = rect.height;
// since the user dragged rectangle included border/padding of the
// element. And if the element's
// width/height style setting don't include border padding, then we need
// to set the element's width/height
// style property a little smaller.
if (part.getFigure() instanceof CSSFigure) {
CSSFigure cssfigure = (CSSFigure) part.getFigure();
ICSSStyle style = cssfigure.getCSSStyle();
if (style != null && !style.isSizeIncludeBorderPadding()) {
width -= (style.getBorderInsets().getWidth() + style
.getPaddingInsets().getWidth());
height -= (style.getBorderInsets().getHeight() + style
.getPaddingInsets().getHeight());
}
}
//make sure to only change the dimensions for the direction of the resize request.
int resizeDirection = request.getResizeDirection();
switch (resizeDirection) {
case PositionConstants.EAST:
case PositionConstants.WEST:
//resizing, only the width, so set the height to -1;
height = -1;
break;
case PositionConstants.NORTH:
case PositionConstants.SOUTH:
//resizing only the height, so set the width to -1
width = -1;
break;
default:
//all others are changing both directions...
}
return getResizeCommand((IDOMElement) part.getIDOMNode(), width, height);
}
/**
* Shows or updates feedback for a change bounds request.
*
* @param request
* the request
*/
protected void showChangeBoundsFeedback(ChangeBoundsRequest request) {
IFigure feedback = getDragSourceFeedbackFigure();
PrecisionRectangle rect = new PrecisionRectangle(
getInitialFeedbackBounds().getCopy());
getHostFigure().translateToAbsolute(rect);
rect.translate(request.getMoveDelta());
rect.resize(request.getSizeDelta());
// to avoid enlarge feedback pane.
// when draging a editpart inside designer to move/copy it, we do not
// want to
// enlarge the canvas, since that may resulting in relayout.
rect = (PrecisionRectangle) rect.intersect(getFeedbackLayer()
.getBounds());
feedback.translateToRelative(rect);
feedback.setBounds(rect);
}
public Cursor getSelectionToolCursor(Point mouseLocation) {
// by default return null to indicate system default.
// sub-classes should override to customize
return null;
}
/**
* by default, return null
* sub-classes should override to customize
* @param request
* @return the selectin tracker
*/
protected DragTracker getSelectionTracker(LocationRequest request)
{
return null;
}
public DragTracker getSelectionDragTracker(LocationRequest request)
{
if (org.eclipse.jst.pagedesigner.requests.PageDesignerRequestConstants.REQ_SELECTION_TRACKER.equals(request.getType())){
return getSelectionTracker(request);
}
// be default don't specify a selection drag tracker
// sub-classes should override to customize
if (shouldUseObjectMode(request)) {
return new ObjectModeDragTracker(getHost());
}
return new RangeDragTracker(getHost());
}
}