/******************************************************************************* * 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()); } }