blob: 853ec06b3d310838f7188a9f093f132b64838a14 [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.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 final 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.
*
* @return the bounding rectangle
*/
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 box
* @return the box's bounding rectangle
*/
public Rectangle getAbsoluteBounds(FlowBox box) {
if (box != null) {
IFigure figure = ((GraphicalEditPart) _part).getFigure();
Rectangle rect = new Rectangle(box.getX(), box.getY(), 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 true if point is the absolute bounds of this
*/
public boolean contains(Point point) {
return getAbsoluteBounds().contains(point);
}
/**
* @return the design position
*/
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;
}
/**
* @param validator
* @return resolve the design position using validator
*/
public DesignPosition resolvePosition(IPositionMediator validator) {
DesignPosition result;
if ((result = resolveTextPosition()) == null) {
boolean atPointLeft = isBeforePoint(_point);
// boolean 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();
// }
private 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;
}
/*package*/ 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);
}
/*package*/ boolean isBeforePoint() {
return isBeforePoint(_point);
}
/*package*/ boolean atLeftPart(Point point) {
FlowBox flowBox = getBox();
if (flowBox != null) {
Rectangle boxRect = getAbsoluteBounds(flowBox);
return CaretPositionResolver.toXMiddle(boxRect, point) < 0;
}
return true;
}
/*package*/ boolean isAfterPoint() {
return isAfterPoint(_point);
}
// TODO: dead but possibly useful?
// private boolean atSameLine(Point point) {
// Rectangle bounds = getAbsoluteBounds();
// return bounds.contains(bounds.getTop().x, point.y);
// }
/*package*/ boolean atSameRow(Point point) {
Rectangle bounds = getAbsoluteBounds();
return bounds.contains(point.x, bounds.getRight().y);
}
/*package*/ static Rectangle getBounds(FlowBox box) {
return new Rectangle(box.getX(), box.getY(), 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 part
* @return the edit part
*
*/
/*package*/ 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.
* Equivalent to getConcretePart(getPart())
*
* @return the edit part
*
*/
public EditPart getConcretePart() {
return getConcretePart(_part);
}
/**
* @param node
* @return the node
*/
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;
}
/**
* @return true if is close to edge
*/
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;
}
/**
* @return tru if getPart() is considered inline
*/
/*package*/ 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()); //$NON-NLS-1$ //$NON-NLS-2$
return sb.toString();
}
}