| /******************************************************************************* |
| * 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.viewer; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| import org.eclipse.core.runtime.Assert; |
| import org.eclipse.draw2d.IFigure; |
| import org.eclipse.draw2d.geometry.Point; |
| import org.eclipse.draw2d.geometry.Rectangle; |
| import org.eclipse.gef.EditPart; |
| import org.eclipse.gef.GraphicalEditPart; |
| import org.eclipse.jst.pagedesigner.IHTMLConstants; |
| import org.eclipse.jst.pagedesigner.css2.layout.CSSFigure; |
| import org.eclipse.jst.pagedesigner.css2.layout.CSSTextFigure; |
| import org.eclipse.jst.pagedesigner.css2.layout.FlowBox; |
| import org.eclipse.jst.pagedesigner.css2.layout.FlowUtilities; |
| import org.eclipse.jst.pagedesigner.css2.layout.TextFragmentBox; |
| import org.eclipse.jst.pagedesigner.css2.layout.TextLayoutSupport; |
| import org.eclipse.jst.pagedesigner.dom.DOMRefPosition; |
| import org.eclipse.jst.pagedesigner.dom.EditModelQuery; |
| import org.eclipse.jst.pagedesigner.parts.SubNodeEditPart; |
| import org.eclipse.jst.pagedesigner.parts.TextEditPart; |
| import org.eclipse.jst.pagedesigner.validation.caret.IPositionMediator; |
| import org.eclipse.jst.pagedesigner.validation.caret.Target; |
| import org.w3c.dom.Node; |
| |
| /** |
| * @author mengbo |
| */ |
| public class LayoutPart { |
| private final static int MAX_OFFSET_TO_EDGE = 10; |
| |
| private EditPart _part; |
| |
| private FlowBox _box; |
| |
| private final Point EMPTY_POINT = new Point(0, 0); |
| |
| private Point _point = EMPTY_POINT; |
| |
| /** |
| * If caller didn't resolve FlowBox, call this. |
| * |
| * @param part |
| * @param point |
| */ |
| public LayoutPart(EditPart part, Point point) { |
| Assert.isTrue(part != null); |
| _part = part; |
| _point = point; |
| } |
| |
| /** |
| * @return Returns the _box, when it is null, generate box with part and |
| * point. |
| */ |
| public FlowBox getBox() { |
| if (_box == null) { |
| _box = getClosestBox(); |
| } |
| return _box; |
| } |
| |
| /** |
| * @return Returns the _part. |
| */ |
| public EditPart getPart() { |
| return _part; |
| } |
| |
| /** |
| * Get closest box's bounds. |
| * |
| * @param point |
| * @return |
| */ |
| private Rectangle getClosestBoxAbsoluteBounds() { |
| Rectangle rect = null; |
| if (getBox() != null) { |
| rect = getAbsoluteBounds(getBox()); |
| } |
| return rect; |
| } |
| |
| /** |
| * Try to get the closest flowbox absolute bounds. |
| * |
| * @param point |
| * @return |
| */ |
| public Rectangle getAbsoluteBounds() { |
| Rectangle bounds = null; |
| if ((bounds = getClosestBoxAbsoluteBounds()) == null) { |
| // This should never happens. |
| bounds = EditPartPositionHelper.getAbsoluteBounds(_part); |
| } |
| return bounds; |
| } |
| |
| /** |
| * Get box's absolute bounds. |
| * |
| * @param point |
| * @return |
| */ |
| public Rectangle getAbsoluteBounds(FlowBox box) { |
| if (box != null) { |
| IFigure figure = ((GraphicalEditPart) _part).getFigure(); |
| Rectangle rect = new Rectangle(box._x, box._y, box.getWidth(), box |
| .getHeight()); |
| figure.translateToAbsolute(rect); |
| return rect; |
| } |
| return null; |
| } |
| |
| /** |
| * Closest box is the part's FlowBox which y coordinate is closest to point, |
| * and then its x coordinate is more close to point than other boxs of the |
| * same line. |
| * |
| * @param part |
| * @param point |
| * @return |
| */ |
| private FlowBox getClosestBox() { |
| FlowBox closestBox = null; |
| List fragments = getLines(_part); |
| |
| // if there is one which are at the same line with relative, |
| // calculate that line first; |
| for (int i = 0; i < fragments.size(); i++) { |
| FlowBox box = (FlowBox) fragments.get(i); |
| Rectangle boxRect = getAbsoluteBounds(box); |
| if (boxRect.contains(_point.x, _point.y)) { |
| closestBox = box; |
| break; |
| } |
| if (closestBox == null) { |
| closestBox = box; |
| } else { |
| // compare y. |
| int offset1 = Math.abs(CaretPositionResolver.getYDistance( |
| boxRect, _point)); |
| Rectangle closestRect = getAbsoluteBounds(closestBox); |
| int offset2 = Math.abs(CaretPositionResolver.getYDistance( |
| closestRect, _point)); |
| if (offset1 < offset2) { |
| closestBox = box; |
| } |
| } |
| // at the same line |
| if (closestBox != box && boxRect.contains(boxRect.x, _point.y)) { |
| // compare x. |
| int offset1 = Math.abs(CaretPositionResolver.getXDistance( |
| boxRect, _point)); |
| Rectangle closestRect = getAbsoluteBounds(closestBox); |
| int offset2 = Math.abs(CaretPositionResolver.getXDistance( |
| closestRect, _point)); |
| if (offset1 < offset2) { |
| closestBox = box; |
| } |
| } |
| } |
| return closestBox; |
| } |
| |
| /** |
| * The point is whitin the bounds of the figure. |
| * |
| * @param point |
| * @return |
| */ |
| public boolean contains(Point point) { |
| return getAbsoluteBounds().contains(point); |
| } |
| |
| public DesignPosition resolveTextPosition() { |
| DesignPosition result = null; |
| if (_part instanceof TextEditPart |
| && !EditModelQuery.isTransparentText(Target.resolveNode(_part))) { |
| FlowBox flowBox = getBox(); |
| if (flowBox instanceof TextFragmentBox) { |
| TextFragmentBox box = (TextFragmentBox) flowBox; |
| if (((TextEditPart) _part).getFigure() instanceof CSSTextFigure) { |
| CSSTextFigure figure = (CSSTextFigure) ((TextEditPart) _part) |
| .getFigure(); |
| Rectangle boxRect = getAbsoluteBounds(box); |
| int index = FlowUtilities.getTextInWidth(box.getTextData(), |
| figure.getCSSStyle().getCSSFont().getSwtFont(), |
| _point.x - boxRect.x, TextLayoutSupport |
| .getAverageCharWidth(box)); |
| result = new DesignPosition(_part, box._offset + index); |
| } |
| } |
| } |
| return result; |
| } |
| |
| public DesignPosition resolvePosition(IPositionMediator validator) { |
| DesignPosition result; |
| if ((result = resolveTextPosition()) == null) { |
| boolean atPointLeft = false; |
| boolean atPointRight = false; |
| atPointLeft = isBeforePoint(_point); |
| atPointRight = isAfterPoint(_point); |
| if (!(atPointLeft ^ atPointRight)) { |
| // TODO: and...? |
| } |
| Target target = new Target(getPart()); |
| if (validator.isValidPosition(new DOMRefPosition(target.getNode(), |
| atPointLeft))) { |
| result = new DesignRefPosition(_part, atPointLeft); |
| } else if (validator.isValidPosition(new DOMRefPosition(target |
| .getNode(), !atPointLeft))) { |
| result = new DesignRefPosition(_part, !atPointLeft); |
| } else if (validator.isEditable(target)) { |
| if (atPointLeft) { |
| result = new DesignPosition(_part, 0); |
| } else { |
| result = new DesignPosition(_part, _part.getChildren() |
| .size()); |
| } |
| } |
| } |
| return result; |
| } |
| |
| // TODO: dead? |
| // private IFigure getFigure() { |
| // return ((GraphicalEditPart) _part).getFigure(); |
| // } |
| |
| public boolean isAfterPoint(Point point) { |
| boolean result = false; |
| FlowBox flowBox = getLine(0); |
| if (IHTMLConstants.TAG_BR.equalsIgnoreCase(Target.resolveNode(_part) |
| .getNodeName())) { |
| if (flowBox != null) { |
| Rectangle boxRect = getAbsoluteBounds(flowBox); |
| result = CaretPositionResolver.getYDistance(boxRect, point) == 0; |
| } |
| } else { |
| |
| if (flowBox != null) { |
| Rectangle boxRect = getAbsoluteBounds(flowBox); |
| if (CaretPositionResolver.getXDistance(boxRect, point) != 0) { |
| result = CaretPositionResolver.getXDistance(boxRect, point) < 0 |
| && // |
| CaretPositionResolver.getYDistance(boxRect, point) == 0; |
| } |
| } |
| } |
| result |= isUnderCaret(); |
| // if (isWidget() && flowBox != null) |
| // { |
| // result |= contains(point) && |
| // CaretPositionResolver.toXMiddle(getAbsoluteBounds(flowBox), point) < |
| // 0; |
| // } |
| return result; |
| |
| } |
| |
| /** |
| * EditPart is at point's left |
| * |
| * @param part |
| * @param point |
| * @return |
| */ |
| public boolean isBeforePoint(Point point) { |
| boolean result = false; |
| FlowBox flowBox = getLastLine(); |
| if (flowBox != null) { |
| Rectangle boxRect = getAbsoluteBounds(flowBox); |
| if (IHTMLConstants.TAG_BR.equalsIgnoreCase(Target |
| .resolveNode(_part).getNodeName())) { |
| return CaretPositionResolver.getYDistance(boxRect, point) == 0; |
| } else if (CaretPositionResolver.getXDistance(boxRect, point) != 0) { |
| result = CaretPositionResolver.getXDistance(boxRect, point) > 0 |
| && // |
| CaretPositionResolver.getYDistance(boxRect, point) == 0; |
| } |
| } |
| result |= isAboveCaret(); |
| // if (isWidget() && flowBox != null) |
| // { |
| // result |= contains(point) && |
| // CaretPositionResolver.toXMiddle(getAbsoluteBounds(flowBox), point) > |
| // 0; |
| // } |
| return result; |
| // return !isAfterPoint(point); |
| } |
| |
| public boolean isBeforePoint() { |
| return isBeforePoint(_point); |
| } |
| |
| public boolean atLeftPart(Point point) { |
| FlowBox flowBox = getBox(); |
| if (flowBox != null) { |
| Rectangle boxRect = getAbsoluteBounds(flowBox); |
| return CaretPositionResolver.toXMiddle(boxRect, point) < 0; |
| } |
| return true; |
| } |
| |
| public boolean isAfterPoint() { |
| return isAfterPoint(_point); |
| } |
| |
| public boolean atSameLine(Point point) { |
| Rectangle bounds = getAbsoluteBounds(); |
| return bounds.contains(bounds.getTop().x, point.y); |
| } |
| |
| public boolean atSameRow(Point point) { |
| Rectangle bounds = getAbsoluteBounds(); |
| return bounds.contains(point.x, bounds.getRight().y); |
| } |
| |
| public static Rectangle getBounds(FlowBox box) { |
| return new Rectangle(box._x, box._y, box.getWidth(), box.getHeight()); |
| } |
| |
| /** |
| * @return Returns the _point. |
| */ |
| public Point getPoint() { |
| return _point; |
| } |
| |
| FlowBox getLine(int index) { |
| FlowBox result = null; |
| List lines = getLines(_part); |
| if (lines.size() > 0 && index >= 0 && index <= lines.size() - 1) { |
| result = (FlowBox) lines.get(index); |
| } |
| return result; |
| } |
| |
| FlowBox getLastLine() { |
| FlowBox result = null; |
| List lines = getLines(_part); |
| if (lines.size() > 0) { |
| result = (FlowBox) lines.get(lines.size() - 1); |
| } |
| return result; |
| } |
| |
| /** |
| * @param part |
| * @return |
| */ |
| List getLines(EditPart part) { |
| List fragments = new ArrayList(); |
| if (part instanceof SubNodeEditPart) { |
| IFigure figure = ((GraphicalEditPart) part).getFigure(); |
| |
| if (figure instanceof CSSTextFigure) { |
| fragments = ((CSSTextFigure) figure).getFragments(); |
| ((CSSTextFigure) figure).getCSSStyle(); |
| } else if (figure instanceof CSSFigure) { |
| fragments = ((CSSFigure) figure).getFragmentsForRead(); |
| ((CSSFigure) figure).getCSSStyle(); |
| } |
| } |
| return fragments; |
| } |
| |
| /** |
| * To search for none empty string, this is not final. |
| * |
| * @param lineBox |
| * @param host |
| * @param rect |
| * @param validator |
| */ |
| public static EditPart getConcretePart(EditPart part) { |
| if (part != null) { |
| Node node = Target.resolveNode(part); |
| Node child = node.getFirstChild(); |
| EditPart result; |
| while (child != null) { |
| if (!EditModelQuery.isTransparentText(child) |
| && (result = Target.resolvePart(child)) != null) { |
| return result; |
| } |
| child = child.getNextSibling(); |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * To search for none empty string, this is not final. |
| * |
| * @param lineBox |
| * @param host |
| * @param rect |
| * @param validator |
| */ |
| public EditPart getConcretePart() { |
| return getConcretePart(_part); |
| } |
| |
| public static Node getConcreteNode(Node node) { |
| if (node != null) { |
| Node child = node.getFirstChild(); |
| while (child != null) { |
| if (!EditModelQuery.isTransparentText(child)) { |
| return node; |
| } |
| child = child.getNextSibling(); |
| } |
| } |
| return null; |
| } |
| |
| public boolean isCloseToEdgeFromOutSide() { |
| boolean result = false; |
| if (EditModelQuery.isBlockNode(Target.resolveNode(_part))) { |
| result = Math.abs(getAbsoluteBounds().getLeft().x - _point.x) <= MAX_OFFSET_TO_EDGE; |
| if (!result) { |
| result = Math.abs(getAbsoluteBounds().getRight().x - _point.x) <= MAX_OFFSET_TO_EDGE; |
| } |
| } |
| return result; |
| } |
| |
| private boolean isAboveCaret() { |
| return getAbsoluteBounds().getBottom().y <= _point.y; |
| } |
| |
| private boolean isUnderCaret() { |
| return getAbsoluteBounds().getTop().y >= _point.y; |
| } |
| |
| public boolean isInline() { |
| return EditModelQuery.isInline(Target.resolveNode(_part)); |
| } |
| |
| // TODO: dead? |
| // private boolean isWidget() { |
| // return EditModelQuery.isWidget(_part); |
| // } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see java.lang.Object#toString() |
| */ |
| public String toString() { |
| StringBuffer sb = new StringBuffer(); |
| sb.append("editPart:" + _part + ", --- box: " + getBox()); |
| return sb.toString(); |
| } |
| } |