blob: 96f275ec55a65163ff78bbd2d43a69f436e5b494 [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.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.dom.DOMPosition;
import org.eclipse.jst.pagedesigner.dom.DOMPositionHelper;
import org.eclipse.jst.pagedesigner.dom.DOMRefPosition;
import org.eclipse.jst.pagedesigner.dom.EditModelQuery;
import org.eclipse.jst.pagedesigner.parts.NodeEditPart;
import org.eclipse.jst.pagedesigner.parts.TextEditPart;
import org.eclipse.jst.pagedesigner.validation.caret.IMovementMediator;
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 EditPartPositionHelper {
// private final static Logger _log = PDPlugin
// .getLogger(EditPartPositionHelper.class);
/**
* Add something to curent
*
* @param lineBox
* @param host
* @param point
* @param validator
*/
private static void addToCurrentLine(FlowBoxLine lineBox, EditPart host,
Point point, IPositionMediator validator) {
Node node = Target.resolveNode(host);
if (!(node == null || EditModelQuery.isDocument(node))) {
// Either it is referencable or is editable.
if (validator.isValidPosition(new DOMRefPosition(node, true))
|| //
validator
.isValidPosition((new DOMRefPosition(node, false)))
|| //
validator.isValidPosition(new DOMPosition(node, 0))) {
lineBox.addLayoutPart(host, point);
}
}
}
/**
* @param position
* @return null means failed to convert to rect.
*/
public static Rectangle convertToAbsoluteCaretRect(DesignPosition position) {
Rectangle ret = null;
try {
final int CARET_OFFSET = 1;
if (position == null || !position.isValid()) {
return null;
}
EditPart containerEditPart = position.getContainerPart();
if (containerEditPart instanceof TextEditPart) {
CSSTextFigure figure = (CSSTextFigure) ((TextEditPart) containerEditPart)
.getFigure();
ret = figure.calculateCaretPosition(position.getOffset());
figure.translateToAbsolute(ret);
ret.width = CaretUpdater.CARET_WIDTH;
} else {
int offset = position.getOffset();
// there is no child
if (containerEditPart.getChildren().isEmpty()
|| LayoutPart.getConcretePart(containerEditPart) == null) {
IFigure figure = ((GraphicalEditPart) containerEditPart)
.getFigure();
Rectangle bounds = figure.getBounds();
if (figure instanceof CSSFigure) {
List fragments = ((CSSFigure) figure)
.getFragmentsForRead();
if (fragments.size() > 0) {
FlowBox box = (FlowBox) fragments.get(fragments
.size() - 1);
bounds = LayoutPart.getBounds(box);
}
}
ret = new Rectangle(bounds.x + CARET_OFFSET, bounds.y,
CaretUpdater.CARET_WIDTH, bounds.height);
figure.translateToAbsolute(ret);
} else if (offset >= 0
&& offset <= containerEditPart.getChildren().size()) {
ret = getRefRect(position);
}
}
} catch (Exception e) {
// This should never happen, we catch here for later analysis.
// _log.debug("Error in caret rect resolving", e);
ret = new Rectangle(0, 0, 0, 0);
}
if (ret == null) {
ret = new Rectangle(0, 0, 0, 0);
}
return ret;
}
/**
* This method will create FlowBoxLine to calculate the accurate parts.
*
* @param host
* @param p
* @param validator
* @return the design position
*/
public static DesignPosition findEditPartPosition(EditPart host, Point p,
IPositionMediator validator) {
try {
host = validator.getEditableContainer(new Target(host));
FlowBoxLine boxLine = new FlowBoxLine(
new Rectangle(p.x, p.y, 0, 0), validator, p);
DesignPosition position = innerFindEditPartPosition(host, host, p,
boxLine, validator);
if (position == null) {
position = innerFindEditPartPosition(host, host, p, boxLine,
validator);
if (position == null) {
EditPart part = boxLine.getClosestPart();
if (part != null) {
LayoutPart layoutPart = new LayoutPart(part, p);
position = layoutPart.resolvePosition(validator);
}
}
}
return position;
} catch (Exception e) {
return null;
}
}
/**
* This function find the position, if there is one which is widget or text
* and it contains p, or there is not such widget, then boxLine will returns
* the widget that are in a sameline which contains p;
* @param rootHost
* @param host
*
* @param p
* @param boxLine
* @param validator
* @return the design position
*/
//TODO: needs refactoring
public static DesignPosition innerFindEditPartPosition(EditPart rootHost,
EditPart host, Point p, FlowBoxLine boxLine,
IPositionMediator validator) {
Target target = new Target(host);
LayoutPart lPart = new LayoutPart(host, p);
// text
if (host instanceof TextEditPart) {
if (lPart.contains(p)) {
DesignPosition position = null;
// see if the point is within string.
position = findTextEditPartPosition((TextEditPart) host, p);
if (position == null) {
addToCurrentLine(boxLine, host, p, validator);
}
// found?!!
return position;
}
addToCurrentLine(boxLine, host, p, validator);
return null;
}
// widget
else if (isWidget(host)) {
if (lPart.contains(p)
&& (validator.isValidPosition(new DOMRefPosition(target
.getNode(), true)) || //
validator.isValidPosition((new DOMRefPosition(target
.getNode(), false))))) {
if (IHTMLConstants.TAG_BR.equalsIgnoreCase(Target.resolveNode(
host).getNodeName())) {
return new DesignRefPosition(host, lPart.isBeforePoint(p));
}
return new DesignRefPosition(host, lPart.isBeforePoint(p)
|| !lPart.atLeftPart(p));
}
addToCurrentLine(boxLine, host, p, validator);
} else {
// root host. we always supporse it has editable area.
if (host == rootHost) {
if (host.getChildren().size() > 0) {
List children = host.getChildren();
for (int i = 0, size = children.size(); i < size; i++) {
GraphicalEditPart child = (GraphicalEditPart) children
.get(i);
DesignPosition position = innerFindEditPartPosition(
rootHost, child, p, boxLine, validator);
if (position != null) {
return position;
}
}
}
if (boxLine.getPartsList().size() == 0) {
if (lPart.contains(p)) {
// found!!!
return new DesignPosition(host, 0);
}
addToCurrentLine(boxLine, host, p, validator);
}
}
// container
else {
// cann't edit it.
if (!validator.hasEditableArea(target)) {
if (lPart.contains(p) && //
(validator.isValidPosition(new DesignRefPosition(
target.getPart(), true)) || //
validator.isValidPosition(new DesignRefPosition(
target.getPart(), true)))) {
return new DesignRefPosition(host, lPart
.isBeforePoint(p)
|| !lPart.atLeftPart(p));
}
addToCurrentLine(boxLine, host, p, validator);
}
// can edit
else {
// contains p
if (lPart.contains(p) || //
(!validator.isValidPosition(new DesignRefPosition(
target.getPart(), true)) && //
!validator.isValidPosition(new DesignRefPosition(
target.getPart(), true)))) {
if (host.getChildren().size() > 0) {
List children = host.getChildren();
for (int i = 0, size = children.size(); i < size; i++) {
GraphicalEditPart child = (GraphicalEditPart) children
.get(i);
DesignPosition position = innerFindEditPartPosition(
rootHost, child, p, boxLine, validator);
if (position != null) {
return position;
}
}
} else {
// we put the container which is empty here.
if (lPart.contains(p)) {
// found!!!
return new DesignPosition(host, 0);
}
addToCurrentLine(boxLine, host, p, validator);
}
}
// not contains p
else {
addToCurrentLine(boxLine, host, p, validator);
}
}
}
}
return null;
}
/**
* similiar to findEditPartPositionConstrained, this method is used to
* vertically move caret.
*
* @param host
* @param p
* @param validator
* @return the design position
*/
public static DesignPosition findEditPartPositionConstrained(EditPart host,
Point p, IMovementMediator validator) {
try {
FlowBoxLine boxLine = new FlowBoxLine(
new Rectangle(p.x, p.y, 0, 0), validator, p);
DesignPosition position = innerFindEditPartPositionConstrained(
host, host, p, boxLine, validator);
if (position == null) {
position = innerFindEditPartPositionConstrained(host, host, p,
boxLine, validator);
if (position == null) {
EditPart part = boxLine.getClosestPart();
if (part != null) {
LayoutPart layoutPart = new LayoutPart(part, p);
position = layoutPart.resolvePosition(validator);
}
}
}
return position;
} catch (Exception e) {
return null;
}
}
/**
* This method is used for move up/down, except for using tactics to deal
* with container, this method is similiar to findEditPartPosition.
*
* @param rootHost
* @param host
* @param p
* @param boxLine
* @param validator
* @return the design position
*/
// TODO: needs refactoring
public static DesignPosition innerFindEditPartPositionConstrained(
EditPart rootHost, EditPart host, Point p, FlowBoxLine boxLine,
IMovementMediator validator) {
Target target = new Target(host);
LayoutPart lPart = new LayoutPart(host, p);
// text
if (host instanceof TextEditPart) {
if (lPart.contains(p)) {
DesignPosition position = null;
// see if the point is within string.
position = findTextEditPartPosition((TextEditPart) host, p);
if (position == null) {
addToCurrentLine(boxLine, host, p, validator);
}
// found?!!
return position;
}
addToCurrentLine(boxLine, host, p, validator);
return null;
}
// widget
else if (isWidget(host)) {
if (lPart.contains(p)) {
// found!!!
if (IHTMLConstants.TAG_BR.equalsIgnoreCase(Target.resolveNode(
host).getNodeName())) {
return new DesignRefPosition(host, lPart.isBeforePoint(p));
}
return new DesignRefPosition(host, lPart.isBeforePoint(p)
|| !lPart.atLeftPart(p));
}
addToCurrentLine(boxLine, host, p, validator);
} else {
// root host. we always supporse it has editable area.
if (host == rootHost) {
if (host.getChildren().size() > 0) {
List children = host.getChildren();
for (int i = 0, size = children.size(); i < size; i++) {
GraphicalEditPart child = (GraphicalEditPart) children
.get(i);
DesignPosition position = innerFindEditPartPositionConstrained(
rootHost, child, p, boxLine, validator);
if (position != null) {
return position;
}
}
} else {
if (lPart.contains(p)) {
// found!!!
return new DesignPosition(host, 0);
}
addToCurrentLine(boxLine, host, p, validator);
}
}
// container
else {
// cann't edit it.
if (!validator.hasEditableArea(target)
|| !validator.allowsMoveIn(target)) {
if (validator.canReference(target, true)
|| validator.canReference(target, false)) {
if (lPart.contains(p)) {
return new DesignRefPosition(host, lPart
.isBeforePoint(p)
|| !lPart.atLeftPart(p));
}
addToCurrentLine(boxLine, host, p, validator);
}
}
// can edit
else {
// contains p
if (lPart.contains(p)) {
if (host.getChildren().size() > 0) {
List children = host.getChildren();
for (int i = 0, size = children.size(); i < size; i++) {
GraphicalEditPart child = (GraphicalEditPart) children
.get(i);
DesignPosition position = innerFindEditPartPositionConstrained(
rootHost, child, p, boxLine, validator);
if (position != null) {
return position;
}
}
} else {
// we put the container which is empty here.
if (lPart.contains(p)) {
// found!!!
return new DesignPosition(host, 0);
}
addToCurrentLine(boxLine, host, p, validator);
}
}
// not contains p
else {
addToCurrentLine(boxLine, host, p, validator);
}
}
}
}
return null;
}
// /**
// * @param host
// * @param p
// * @return
// */
// private static DesignPosition
// findTextEditPartPositionAdjacent(TextEditPart host, Point p)
// {
// if (host.getFigure() instanceof CSSTextFigure)
// {
// CSSTextFigure figure = (CSSTextFigure) host.getFigure();
// // make a copy to not destroy the original o
// p = p.getCopy();
// figure.translateToRelative(p);
// int offset = figure.getNewInsertionOffset(p);
// if (offset >= 0)
// {
// return new DesignPosition(host, offset);
// }
// else
// {
// return null;
// }
// }
// else
// {
// // should not happen.
// return new DesignPosition(host, 0);
// }
// }
/**
* @param host
* @param p
* @return
*/
private static DesignPosition findTextEditPartPosition(TextEditPart host,
Point p) {
if (host.getFigure() instanceof CSSTextFigure) {
CSSTextFigure figure = (CSSTextFigure) host.getFigure();
// make a copy to not destroy the original o
p = p.getCopy();
figure.translateToRelative(p);
int offset = figure.getInsertionOffset(p);
if (offset >= 0) {
return new DesignPosition(host, offset);
}
return null;
}
// should not happen.
return new DesignPosition(host, 0);
}
/**
* @param figure
* @param box
* @return
*/
// public static Rectangle getBoxBounds(IFigure figure, FlowBox box) {
// Rectangle r = new Rectangle(box._x, box._y, box.getWidth(), box
// .getHeight());
// figure.translateToAbsolute(r);
// return r;
// }
/**
* If child is a GraphicalEditPart, a new copy of its bounding rectangle
* will be returned translated to absolute bounds. If child is not a GraphicalEditPart
* then the empty rectangle (0,0,0,0) is returned.
*
* @param child
* @return the bounding rectangle or (0,0,0,0) if none.
*/
public static Rectangle getAbsoluteBounds(EditPart child) {
if (child instanceof GraphicalEditPart) {
Rectangle bounds = ((GraphicalEditPart) child).getFigure()
.getBounds().getCopy();
((GraphicalEditPart) child).getFigure().translateToAbsolute(bounds);
return bounds;
}
return new Rectangle(0, 0, 0, 0);
}
/**
* @param host
* @param tagName
* @return
*/
private static boolean isWidget(EditPart host) {
if (host instanceof NodeEditPart) {
return ((NodeEditPart) host).isWidget();
}
return false;
}
/**
* Is Caret at right?
*
* @param position
* @param caretRefResult
* @return
*/
private static EditPart tryTwoWays(DesignPosition position,
List<Boolean> caretRefResult) {
EditPart result = null;
// Sibling first:
Node node = EditModelQuery.getInstance().getSibling(
DOMPositionHelper.toDOMPosition(position), true);
if (node != null && !EditModelQuery.isTransparentText(node)) {
result = Target.resolvePart(node);
caretRefResult.add(Boolean.FALSE);
} else {
node = EditModelQuery.getInstance().getSibling(
DOMPositionHelper.toDOMPosition(position), false);
if (node != null && !EditModelQuery.isTransparentText(node)) {
result = Target.resolvePart(node);
caretRefResult.add(Boolean.TRUE);
}
}
if (result == null) {
if (getConcretePart(position, false) != null) {
result = getConcretePart(position, false);
caretRefResult.add(Boolean.TRUE);
} else if (getConcretePart(position, true) != null) {
result = getConcretePart(position, true);
caretRefResult.add(Boolean.FALSE);
}
}
return result;
}
/*
* Here we are doing something to avoid reference witespace tag. Since we
* still need to improve whitespace tags's layout furthure more.
*/
private static EditPart getNextConcretPart(DesignPosition position,
List<Boolean> caretIsAtRightTest) {
EditPart result = null;
boolean caretIsAtRight = true;
if (position instanceof DesignRefPosition) {
caretIsAtRight = ((DesignRefPosition) position).caretIsAtRight();
result = ((DesignRefPosition) position).getRefPart();
caretIsAtRightTest.add(Boolean.valueOf(caretIsAtRight));
}
if (result == null
|| EditModelQuery.isTransparentText(Target.resolveNode(result))) {
caretIsAtRightTest.clear();
result = tryTwoWays(position, caretIsAtRightTest);
}
return result;
}
/**
* Avoid whitespaces
*
* @param position
* @param forward
* @return the edit part at position which is non-whitespace ? TODO:
*/
public static EditPart getConcretePart(DesignPosition position,
boolean forward) {
EditPart result = null;
Node node = EditModelQuery.getInstance().getSibling(
DOMPositionHelper.toDOMPosition(position), forward);
while (node != null && EditModelQuery.isTransparentText(node)) {
node = EditModelQuery.getInstance().getSibling(node, forward);
}
if (node != null) {
result = Target.resolvePart(node);
}
return result;
}
/**
* @param position
* @param forward
* @return the next concrete part.
*/
public static EditPart getNextConcretPart(DesignPosition position,
boolean forward) {
Node node;
EditPart result = null;
node = EditModelQuery.getInstance().getSibling(
DOMPositionHelper.toDOMPosition(position), forward);
if (node != null) {
if (forward) {
while (node != null) {
if (!EditModelQuery.isTransparentText(node)
&& (result = Target.resolvePart(node)) != null) {
result = Target.resolvePart(node);
break;
}
node = node.getNextSibling();
}
} else {
while (node != null) {
if (!EditModelQuery.isTransparentText(node)
&& (result = Target.resolvePart(node)) != null) {
result = Target.resolvePart(node);
break;
}
node = node.getPreviousSibling();
}
}
}
return result;
}
private static Rectangle getRefRect(DesignPosition position) {
List<Boolean> caretLocation = new ArrayList<Boolean>();
EditPart part = getNextConcretPart(position, caretLocation);
LayoutPart layoutPart;
Rectangle rect = null;
if (part != null) {
layoutPart = new LayoutPart(part, null);
boolean caretIsAtRight = caretLocation.get(0)
.booleanValue();
final int CARET_OFFSET = 1;
Rectangle bounds = null;
IFigure figure = ((GraphicalEditPart) part).getFigure();
if (!caretIsAtRight) {
FlowBox box;
if ((box = layoutPart.getLine(0)) != null) {
bounds = LayoutPart.getBounds(box);
}
} else {
FlowBox box;
if ((box = layoutPart.getLastLine()) != null) {
bounds = LayoutPart.getBounds(box);
}
}
if (bounds == null) {
bounds = figure.getBounds();
}
if (!caretIsAtRight) {
rect = new Rectangle(bounds.x - CARET_OFFSET, bounds.y,
CaretUpdater.CARET_WIDTH, bounds.height);// new
} else {
rect = new Rectangle(bounds.getRight().x + CARET_OFFSET,
bounds.y, CaretUpdater.CARET_WIDTH, bounds.height);// new
}
figure.translateToAbsolute(rect);
} else {
System.out.println("No concrete part?"); //$NON-NLS-1$
}
return rect;
}
}