blob: 27d448880acc0f1db30415aa2ab8f8db67391a31 [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.commands.range;
import java.util.Arrays;
import org.eclipse.core.runtime.Assert;
import org.eclipse.gef.EditPart;
import org.eclipse.jst.pagedesigner.IHTMLConstants;
import org.eclipse.jst.pagedesigner.dom.DOMPosition;
import org.eclipse.jst.pagedesigner.dom.DOMPositionHelper;
import org.eclipse.jst.pagedesigner.dom.DOMRange;
import org.eclipse.jst.pagedesigner.dom.DOMRefPosition;
import org.eclipse.jst.pagedesigner.dom.DOMUtil;
import org.eclipse.jst.pagedesigner.dom.EditHelper;
import org.eclipse.jst.pagedesigner.dom.EditModelQuery;
import org.eclipse.jst.pagedesigner.dom.IDOMPosition;
import org.eclipse.jst.pagedesigner.dom.IDOMRefPosition;
import org.eclipse.jst.pagedesigner.parts.TextEditPart;
import org.eclipse.jst.pagedesigner.viewer.IHTMLGraphicalViewer;
import org.eclipse.wst.sse.core.internal.provisional.INodeNotifier;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
/**
* @author mengbo
*/
public class ParagraphApplyStyleCommand extends ApplyStyleCommand {
/**
* the list of possible html heading tags
*/
private static final String[] HH = { "h1", "h2", "h3", "h4", "h5", "h6" };
/**
* @param viewer
* @param tag
* @param property
* @param value
*/
public ParagraphApplyStyleCommand(IHTMLGraphicalViewer viewer, String tag,
String property, String value) {
super(viewer, tag, property, value);
}
/**
* @param viewer
* @param node
* @param property
* @param value
*/
public ParagraphApplyStyleCommand(IHTMLGraphicalViewer viewer,
Element node, String property, String value) {
super(viewer, node, property, value);
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jst.pagedesigner.commands.range.RangeModeCommand#doRangeExecute(org.eclipse.jst.pagedesigner.dom.DOMRange)
*/
protected DOMRange doRangeExecute(DOMRange range) {
if (range != null) {
boolean ordered = range.isOrdered();
IDOMPosition start = ordered ? range.getStartPosition() : range
.getEndPosition();
IDOMPosition end = ordered ? range.getEndPosition() : range
.getStartPosition();
Node common = null;
Node container = null;
if (EditModelQuery.isSame(range)) {
container = start.getContainerNode();
ParagraphFinder finder = new ParagraphFinder(start);
Paragraph p = finder.getParagraph(start);
start = p.getStart();
end = p.getEnd();
common = p.getLowestContainer();
} else {
common = EditModelQuery.getInstance().getCommonAncestor(start,
end);
}
DOMRange rt;
// This code is for h1-h6 only, it may need to be replaced.
if ((rt = replaceExistingH(start, end)) != null) {
return rt;
}
// replace existing p
if (getTag().equalsIgnoreCase(IHTMLConstants.TAG_P)) {
rt = replaceExistingP(start, end);
if (rt != null) {
return rt;
}
}
if (start.getContainerNode() == end.getContainerNode()) {
int offset1 = start.getOffset();
int offset2 = end.getOffset();
IDOMPosition old = start;
start = split(start);
// parent is splited
if (start != old) {
container = start.getNextSiblingNode();
offset2 -= offset1;
end = new DOMPosition(container, offset2);
}
end = split(end);
} else {
start = split(common, start);
end = split(common, end);
}
range = InsertStyleTag(new DOMRange(start, end));
}
return range;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.gef.commands.Command#canExecute()
*/
public boolean canExecute() {
return true;
}
/*
* Try to split the node so that we can avoid wrap its children directly.
* Begining from 'position' the split can reach as high as the level of
* 'common'.
*/
private IDOMPosition split(Node common, IDOMPosition position) {
Assert.isTrue(EditModelQuery.isChild(common, position
.getContainerNode()));
Node container = position.getContainerNode();
String[] styleNodes = new String[EditModelQuery.HTML_STYLE_NODES.size()];
EditModelQuery.HTML_STYLE_NODES.toArray(styleNodes);
while (EditModelQuery.isText(container) || (container != common && //
EditModelQuery.containItem(styleNodes, container, true))) {
IDOMPosition old = position;
position = EditHelper.splitNode(position);
if (old == position) {
int pos = EditHelper.getLocation(position);
switch (pos) {
case -1:
position = new DOMRefPosition(position.getContainerNode(),
false);
break;
case 1:
position = new DOMRefPosition(position.getContainerNode(),
true);
}
}
Node containerBackup = container;
container = container.getParentNode();
if (containerBackup.getNodeName().equalsIgnoreCase(
IHTMLConstants.TAG_P)) {
container.removeChild(containerBackup);
}
}
return position;
}
/*
* Split the position's container node only.
*/
private IDOMPosition split(IDOMPosition position) {
Node container = position.getContainerNode();
String[] styleNodes = new String[EditModelQuery.HTML_STYLE_NODES.size()];
EditModelQuery.HTML_STYLE_NODES.toArray(styleNodes);
if (EditModelQuery.isText(container)
|| EditModelQuery.containItem(styleNodes, container, true)) {
return EditHelper.splitNode(position);
}
return position;
}
private DOMRange replaceExistingH(IDOMPosition start, IDOMPosition end) {
Node common = EditModelQuery.getInstance()
.getCommonAncestor(start, end);
// Here we insert some code to avoid creating tags duplicated. but these
// are not the entire cases.
if (Arrays.asList(HH).contains(
getAName(getTag()).toLowerCase())
&& Arrays.asList(HH).contains(
getAName(common.getNodeName()).toLowerCase())) {
// uncheck action menu
if (getAName(getTag()).toLowerCase().equalsIgnoreCase(
getAName(common.getNodeName()).toLowerCase())) {
NodeList nodes = common.getChildNodes();
for (int i = 0, size = nodes.getLength(); i < size; i++) {
common.getParentNode().insertBefore(nodes.item(i), common);
}
common.getParentNode().removeChild(common);
return new DOMRange(start, end);
}
start = DOMPositionHelper.toDOMRefPosition(start);
end = DOMPositionHelper.toDOMRefPosition(end);
Node newHNode = EditModelQuery.getDocumentNode(common)
.createElement(getTag());
EditModelQuery.copyChildren(common, newHNode);
common.getParentNode().replaceChild(newHNode, common);
return new DOMRange(start, end);
}
return null;
}
private DOMRange replaceExistingP(IDOMPosition start, IDOMPosition end) {
// find the selected startNode,endNode and start node's parent node
Node startNode = start instanceof IDOMRefPosition ? start
.getNextSiblingNode() : start.getContainerNode();
Node endNode = end instanceof IDOMRefPosition ? end
.getPreviousSiblingNode() : end.getContainerNode();
Node parentNode = startNode.getParentNode();
if (!(start.isText()) && start instanceof DOMPosition) {
startNode = startNode.getChildNodes().item(start.getOffset());
parentNode = start.getContainerNode();
}
if (!(end.isText()) && end instanceof DOMPosition) {
// because the offset is based on the position between nodes,so we
// need to reduce one from the offset
// in order to get the correct end node.
endNode = endNode.getChildNodes().item(end.getOffset() - 1);
}
// compute selected character number in the text or selected element
// number under a node
int len = 0;
// if (start instanceof DOMPosition && end instanceof DOMPosition
// || start instanceof IDOMPosition && end instanceof IDOMPosition) {
// TODO: as written, this will be the only statement run, since
// both start and end are instanceof IDOMPosition by defn.
len = end.getOffset() - start.getOffset();
// } else {
// IDOMRefPosition startRef = null;
// IDOMRefPosition endRef = null;
// if (!(start.isText()) && start instanceof DOMPosition) {
// startRef = new DOMRefPosition(startNode, false);
// } else if (!(end.isText()) && end instanceof DOMPosition) {
// endRef = new DOMRefPosition(endNode, true);
// }
// len = (endRef != null ? endRef.getOffset() : end.getOffset())
// - (startRef != null ? startRef.getOffset() : start
// .getOffset());
// }
// if a full Text node is selected,and the Text node is the only child
// of its parent
if ((startNode == endNode) && (startNode instanceof Text)) {
TextEditPart part = (TextEditPart) ((INodeNotifier) startNode)
.getAdapterFor(EditPart.class);
boolean condition = false;
if (start instanceof IDOMRefPosition
|| (start instanceof DOMPosition && !start.isText())) {
condition = parentNode.getNodeName().equalsIgnoreCase(
IHTMLConstants.TAG_P)
&& parentNode.getChildNodes().getLength() == 1;
} else {
condition = parentNode.getNodeName().equalsIgnoreCase(
IHTMLConstants.TAG_P)
&& parentNode.getChildNodes().getLength() == 1
&& part.getTextData().length() == len;
}
if (condition) {
// if uncheck the align action
if (this._applyingNode
.getAttribute(IHTMLConstants.ATTR_ALIGN)
.equals(
((Element) parentNode)
.getAttribute(IHTMLConstants.ATTR_ALIGN))) {
((Element) parentNode)
.removeAttribute(IHTMLConstants.ATTR_ALIGN);
IDOMPosition startPos = new DOMPosition(parentNode, 0);
IDOMPosition endPos = new DOMRefPosition(endNode, true);
return new DOMRange(startPos, endPos);
}
// else replace the align attribute
/**
* this._applyingNode.appendChild(startNode);
* parentNode.getParentNode().replaceChild(this._applyingNode,
* parentNode);
*/
String align = this._applyingNode
.getAttribute(IHTMLConstants.ATTR_ALIGN);
((Element) parentNode).setAttribute(IHTMLConstants.ATTR_ALIGN,
align);
IDOMPosition startPos = new DOMPosition(parentNode, 0);
IDOMPosition endPos = new DOMRefPosition(endNode, true);
return new DOMRange(startPos, endPos);
}
} else {
if (parentNode != null
&& parentNode.getNodeName().equalsIgnoreCase(
IHTMLConstants.TAG_P)
&& parentNode.getChildNodes().getLength() == len) {
if (this._applyingNode
.getAttribute(IHTMLConstants.ATTR_ALIGN)
.equals(
((Element) parentNode)
.getAttribute(IHTMLConstants.ATTR_ALIGN))) {
((Element) parentNode)
.removeAttribute(IHTMLConstants.ATTR_ALIGN);
IDOMPosition startPos = new DOMPosition(parentNode, 0);
IDOMPosition endPos = new DOMRefPosition(endNode, true);
return new DOMRange(startPos, endPos);
}
/**
* Node sibling = startNode.getNextSibling();
* this._applyingNode.appendChild(startNode); Node
* endNodeSibling = endNode.getNextSibling(); while (sibling !=
* null && startNode != endNode && sibling != endNodeSibling) {
* Node tempNode = sibling.getNextSibling();
* this._applyingNode.appendChild(sibling); sibling = tempNode; }
* parentNode.getParentNode().replaceChild(this._applyingNode,
* parentNode);
*/
String align = this._applyingNode
.getAttribute(IHTMLConstants.ATTR_ALIGN);
((Element) parentNode).setAttribute(IHTMLConstants.ATTR_ALIGN,
align);
IDOMPosition startPos = new DOMPosition(parentNode, 0);
IDOMPosition endPos = new DOMRefPosition(endNode, true);
return new DOMRange(startPos, endPos);
}
}
return null;
}
private DOMRange InsertStyleTag(DOMRange range) {
if (range == null || range.isEmpty()) {
return null;
}
boolean ordered = range.isOrdered();
IDOMPosition start = ordered ? range.getStartPosition() : range
.getEndPosition();
IDOMPosition end = ordered ? range.getEndPosition() : range
.getStartPosition();
Node startContainer = start.getContainerNode();
Node endContainer = end.getContainerNode();
Node common = DOMUtil.findCommonAncester(start.getContainerNode(), end
.getContainerNode());
if (common == null) {
// should not happen.
return null;
}
if (startContainer instanceof Text) {
// if the start offset is 0,then skip split the Text
if (start.getOffset() > 0) {
startContainer = ((Text) startContainer).splitText(start
.getOffset());
start = new DOMRefPosition(startContainer, false);
}
} else {
startContainer = start.getNextSiblingNode();
}
if (endContainer instanceof Text) {
if (end.getOffset() > 0) {
endContainer = ((Text) endContainer).splitText(end
.getOffset());
endContainer = endContainer.getPreviousSibling();
} else {
endContainer = endContainer.getPreviousSibling();
}
} else {
endContainer = end.getPreviousSiblingNode();
}
// now the startContainer and the endContainer should share the same
// parent
Element newNode = createStyleElement();
startContainer.getParentNode()
.insertBefore(newNode, startContainer);
Node sibling = startContainer.getNextSibling();
newNode.appendChild(startContainer);
Node endNodeSibling = endContainer.getNextSibling();
while (sibling != null && startContainer != endContainer
&& sibling != endNodeSibling) {
Node tempNode = sibling.getNextSibling();
newNode.appendChild(sibling);
sibling = tempNode;
}
IDOMPosition startPos = new DOMPosition(newNode, 0);
IDOMPosition endPos = new DOMRefPosition(endContainer, true);
return new DOMRange(startPos, endPos);
}
private static String getAName(String name) {
return name == null ? "" : name;
}
}