blob: da52a3cb511a3f58f826e62e5584e363e85242aa [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2005 IBM Corporation 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:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.ui.internal.forms.widgets;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.Vector;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.eclipse.swt.SWT;
import org.eclipse.ui.forms.HyperlinkSettings;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
public class FormTextModel {
private static final DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory
.newInstance();
private boolean whitespaceNormalized = true;
private Vector paragraphs;
private IFocusSelectable[] selectableSegments;
private int selectedSegmentIndex = -1;
private int savedSelectedLinkIndex = -1;
private HyperlinkSettings hyperlinkSettings;
public static final String BOLD_FONT_ID = "f.____bold"; //$NON-NLS-1$
//private static final int TEXT_ONLY_LINK = 1;
//private static final int IMG_ONLY_LINK = 2;
//private static final int TEXT_AND_IMAGES_LINK = 3;
public FormTextModel() {
reset();
}
/*
* @see ITextModel#getParagraphs()
*/
public Paragraph[] getParagraphs() {
if (paragraphs == null)
return new Paragraph[0];
return (Paragraph[]) paragraphs
.toArray(new Paragraph[paragraphs.size()]);
}
public String getAccessibleText() {
if (paragraphs == null)
return ""; //$NON-NLS-1$
StringBuffer sbuf = new StringBuffer();
for (int i = 0; i < paragraphs.size(); i++) {
Paragraph paragraph = (Paragraph) paragraphs.get(i);
String text = paragraph.getAccessibleText();
sbuf.append(text);
}
return sbuf.toString();
}
/*
* @see ITextModel#parse(String)
*/
public void parseTaggedText(String taggedText, boolean expandURLs) {
if (taggedText == null) {
reset();
return;
}
try {
InputStream stream = new ByteArrayInputStream(taggedText
.getBytes("UTF8")); //$NON-NLS-1$
parseInputStream(stream, expandURLs);
} catch (UnsupportedEncodingException e) {
SWT.error(SWT.ERROR_UNSUPPORTED_FORMAT, e);
}
}
public void parseInputStream(InputStream is, boolean expandURLs) {
documentBuilderFactory.setNamespaceAware(true);
documentBuilderFactory.setIgnoringComments(true);
reset();
try {
DocumentBuilder parser = documentBuilderFactory
.newDocumentBuilder();
InputSource source = new InputSource(is);
Document doc = parser.parse(source);
processDocument(doc, expandURLs);
} catch (ParserConfigurationException e) {
SWT.error(SWT.ERROR_INVALID_ARGUMENT, e);
} catch (SAXException e) {
SWT.error(SWT.ERROR_INVALID_ARGUMENT, e);
} catch (IOException e) {
SWT.error(SWT.ERROR_IO, e);
}
}
private void processDocument(Document doc, boolean expandURLs) {
Node root = doc.getDocumentElement();
NodeList children = root.getChildNodes();
processSubnodes(paragraphs, children, expandURLs);
}
private void processSubnodes(Vector plist, NodeList children, boolean expandURLs) {
for (int i = 0; i < children.getLength(); i++) {
Node child = children.item(i);
if (child.getNodeType() == Node.TEXT_NODE) {
// Make an implicit paragraph
String text = getSingleNodeText(child);
if (text != null && !isIgnorableWhiteSpace(text, true)) {
Paragraph p = new Paragraph(true);
p.parseRegularText(text, expandURLs,
getHyperlinkSettings(), null);
plist.add(p);
}
} else if (child.getNodeType() == Node.ELEMENT_NODE) {
String tag = child.getNodeName().toLowerCase();
if (tag.equals("p")) { //$NON-NLS-1$
Paragraph p = processParagraph(child, expandURLs);
if (p != null)
plist.add(p);
} else if (tag.equals("li")) { //$NON-NLS-1$
Paragraph p = processListItem(child, expandURLs);
if (p != null)
plist.add(p);
}
}
}
}
private Paragraph processParagraph(Node paragraph, boolean expandURLs) {
NodeList children = paragraph.getChildNodes();
NamedNodeMap atts = paragraph.getAttributes();
Node addSpaceAtt = atts.getNamedItem("addVerticalSpace"); //$NON-NLS-1$
boolean addSpace = true;
if (addSpaceAtt == null)
addSpaceAtt = atts.getNamedItem("vspace"); //$NON-NLS-1$
if (addSpaceAtt != null) {
String value = addSpaceAtt.getNodeValue();
addSpace = value.equalsIgnoreCase("true"); //$NON-NLS-1$
}
Paragraph p = new Paragraph(addSpace);
processSegments(p, children, expandURLs);
return p;
}
private Paragraph processListItem(Node listItem, boolean expandURLs) {
NodeList children = listItem.getChildNodes();
NamedNodeMap atts = listItem.getAttributes();
Node addSpaceAtt = atts.getNamedItem("addVerticalSpace");//$NON-NLS-1$
Node styleAtt = atts.getNamedItem("style");//$NON-NLS-1$
Node valueAtt = atts.getNamedItem("value");//$NON-NLS-1$
Node indentAtt = atts.getNamedItem("indent");//$NON-NLS-1$
Node bindentAtt = atts.getNamedItem("bindent");//$NON-NLS-1$
int style = BulletParagraph.CIRCLE;
int indent = -1;
int bindent = -1;
String text = null;
boolean addSpace = true;
if (addSpaceAtt != null) {
String value = addSpaceAtt.getNodeValue();
addSpace = value.equalsIgnoreCase("true"); //$NON-NLS-1$
}
if (styleAtt != null) {
String value = styleAtt.getNodeValue();
if (value.equalsIgnoreCase("text")) { //$NON-NLS-1$
style = BulletParagraph.TEXT;
} else if (value.equalsIgnoreCase("image")) { //$NON-NLS-1$
style = BulletParagraph.IMAGE;
} else if (value.equalsIgnoreCase("bullet")) { //$NON-NLS-1$
style = BulletParagraph.CIRCLE;
}
}
if (valueAtt != null) {
text = valueAtt.getNodeValue();
if (style == BulletParagraph.IMAGE)
text = "i." + text; //$NON-NLS-1$
}
if (indentAtt != null) {
String value = indentAtt.getNodeValue();
try {
indent = Integer.parseInt(value);
} catch (NumberFormatException e) {
}
}
if (bindentAtt != null) {
String value = bindentAtt.getNodeValue();
try {
bindent = Integer.parseInt(value);
} catch (NumberFormatException e) {
}
}
BulletParagraph p = new BulletParagraph(addSpace);
p.setIndent(indent);
p.setBulletIndent(bindent);
p.setBulletStyle(style);
p.setBulletText(text);
processSegments(p, children, expandURLs);
return p;
}
private void processSegments(Paragraph p, NodeList children,
boolean expandURLs) {
for (int i = 0; i < children.getLength(); i++) {
Node child = children.item(i);
ParagraphSegment segment = null;
if (child.getNodeType() == Node.TEXT_NODE) {
String value = getSingleNodeText(child);
if (value != null && !isIgnorableWhiteSpace(value, false)) {
p.parseRegularText(value, expandURLs,
getHyperlinkSettings(), null);
}
} else if (child.getNodeType() == Node.ELEMENT_NODE) {
String name = child.getNodeName();
if (name.equalsIgnoreCase("img")) { //$NON-NLS-1$
segment = processImageSegment(child);
} else if (name.equalsIgnoreCase("a")) { //$NON-NLS-1$
segment = processHyperlinkSegment(child,
getHyperlinkSettings());
} else if (name.equalsIgnoreCase("span")) { //$NON-NLS-1$
processTextSegment(p, expandURLs, child);
} else if (name.equalsIgnoreCase("b")) { //$NON-NLS-1$
String text = getNodeText(child);
String fontId = BOLD_FONT_ID;
p.parseRegularText(text, expandURLs,
getHyperlinkSettings(), fontId);
} else if (name.equalsIgnoreCase("br")) { //$NON-NLS-1$
segment = new BreakSegment();
} else if (name.equalsIgnoreCase("control")) { //$NON-NLS-1$
segment = processControlSegment(child);
}
}
if (segment != null) {
p.addSegment(segment);
}
}
}
private boolean isIgnorableWhiteSpace(String text, boolean ignoreSpaces) {
for (int i = 0; i < text.length(); i++) {
char c = text.charAt(i);
if (ignoreSpaces && c == ' ')
continue;
if (c == '\n' || c == '\r' || c == '\f')
continue;
return false;
}
return true;
}
private ImageSegment processImageSegment(Node image) {
ImageSegment segment = new ImageSegment();
processObjectSegment(segment, image, "i."); //$NON-NLS-1$
return segment;
}
private ControlSegment processControlSegment(Node control) {
ControlSegment segment = new ControlSegment();
processObjectSegment(segment, control, "o."); //$NON-NLS-1$
Node fill = control.getAttributes().getNamedItem("fill"); //$NON-NLS-1$
if (fill!=null) {
String value = fill.getNodeValue();
boolean doFill = value.equalsIgnoreCase("true"); //$NON-NLS-1$
segment.setFill(doFill);
}
return segment;
}
private void processObjectSegment(ObjectSegment segment, Node object, String prefix) {
NamedNodeMap atts = object.getAttributes();
Node id = atts.getNamedItem("href"); //$NON-NLS-1$
Node align = atts.getNamedItem("align"); //$NON-NLS-1$
if (id != null) {
String value = id.getNodeValue();
segment.setObjectId(prefix + value);
}
if (align != null) {
String value = align.getNodeValue().toLowerCase();
if (value.equals("top")) //$NON-NLS-1$
segment.setVerticalAlignment(ImageSegment.TOP);
else if (value.equals("middle")) //$NON-NLS-1$
segment.setVerticalAlignment(ImageSegment.MIDDLE);
else if (value.equals("bottom")) //$NON-NLS-1$
segment.setVerticalAlignment(ImageSegment.BOTTOM);
}
}
private void appendText(String value, StringBuffer buf, int[] spaceCounter) {
if (!whitespaceNormalized)
buf.append(value);
else {
for (int j = 0; j < value.length(); j++) {
char c = value.charAt(j);
if (c == ' ' || c == '\t') {
// space
if (++spaceCounter[0] == 1) {
buf.append(c);
}
} else if (c == '\n' || c == '\r' || c == '\f') {
// new line
if (++spaceCounter[0] == 1) {
buf.append(' ');
}
} else {
// other characters
spaceCounter[0] = 0;
buf.append(c);
}
}
}
}
private String getNormalizedText(String text) {
int[] spaceCounter = new int[1];
StringBuffer buf = new StringBuffer();
if (text == null)
return null;
appendText(text, buf, spaceCounter);
return buf.toString();
}
private String getSingleNodeText(Node node) {
return getNormalizedText(node.getNodeValue());
}
private String getNodeText(Node node) {
NodeList children = node.getChildNodes();
StringBuffer buf = new StringBuffer();
int[] spaceCounter = new int[1];
for (int i = 0; i < children.getLength(); i++) {
Node child = children.item(i);
if (child.getNodeType() == Node.TEXT_NODE) {
String value = child.getNodeValue();
appendText(value, buf, spaceCounter);
}
}
return buf.toString().trim();
}
private ParagraphSegment processHyperlinkSegment(Node link,
HyperlinkSettings settings) {
NamedNodeMap atts = link.getAttributes();
String href = null;
boolean wrapAllowed = true;
String boldFontId = null;
Node hrefAtt = atts.getNamedItem("href"); //$NON-NLS-1$
if (hrefAtt != null) {
href = hrefAtt.getNodeValue();
}
Node boldAtt = atts.getNamedItem("bold"); //$NON-NLS-1$
if (boldAtt != null) {
boldFontId = BOLD_FONT_ID;
}
Node nowrap = atts.getNamedItem("nowrap"); //$NON-NLS-1$
if (nowrap != null) {
String value = nowrap.getNodeValue();
if (value != null && value.equalsIgnoreCase("true")) //$NON-NLS-1$
wrapAllowed = false;
}
Object status = checkChildren(link);
if (status instanceof Node) {
Node child = (Node)status;
ImageHyperlinkSegment segment = new ImageHyperlinkSegment();
segment.setHref(href);
segment.setWordWrapAllowed(wrapAllowed);
Node alt = child.getAttributes().getNamedItem("alt"); //$NON-NLS-1$
if (alt!=null)
segment.setTooltipText(alt.getNodeValue());
Node text = child.getAttributes().getNamedItem("text"); //$NON-NLS-1$
if (text!=null)
segment.setText(text.getNodeValue());
processObjectSegment(segment, child, "i."); //$NON-NLS-1$
return segment;
} else if (status instanceof String) {
String text = (String) status;
TextHyperlinkSegment segment = new TextHyperlinkSegment(text,
settings, null);
segment.setHref(href);
segment.setFontId(boldFontId);
Node alt = atts.getNamedItem("alt"); //$NON-NLS-1$
if (alt!=null)
segment.setTooltipText(alt.getNodeValue());
segment.setWordWrapAllowed(wrapAllowed);
return segment;
} else {
AggregateHyperlinkSegment parent = new AggregateHyperlinkSegment();
parent.setHref(href);
NodeList children = link.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node child = children.item(i);
if (child.getNodeType() == Node.TEXT_NODE) {
String value = child.getNodeValue();
TextHyperlinkSegment ts = new TextHyperlinkSegment(
getNormalizedText(value), settings, null);
Node alt = atts.getNamedItem("alt"); //$NON-NLS-1$
if (alt!=null)
ts.setTooltipText(alt.getNodeValue());
ts.setWordWrapAllowed(wrapAllowed);
parent.add(ts);
} else if (child.getNodeType() == Node.ELEMENT_NODE) {
String name = child.getNodeName();
if (name.equalsIgnoreCase("img")) { //$NON-NLS-1$
ImageHyperlinkSegment is = new ImageHyperlinkSegment();
processObjectSegment(is, child, "i."); //$NON-NLS-1$
Node alt = child.getAttributes().getNamedItem("alt"); //$NON-NLS-1$
if (alt!=null)
is.setTooltipText(alt.getNodeValue());
parent.add(is);
is.setWordWrapAllowed(wrapAllowed);
}
}
}
return parent;
}
}
private Object checkChildren(Node node) {
boolean text = false;
Node imgNode = null;
//int status = 0;
NodeList children = node.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node child = children.item(i);
if (child.getNodeType() == Node.TEXT_NODE)
text = true;
else if (child.getNodeType() == Node.ELEMENT_NODE
&& child.getNodeName().equalsIgnoreCase("img")) { //$NON-NLS-1$
imgNode = child;
}
}
if (text && imgNode == null)
return getNodeText(node);
else if (!text && imgNode != null)
return imgNode;
else return null;
}
private void processTextSegment(Paragraph p, boolean expandURLs,
Node textNode) {
String text = getNodeText(textNode);
NamedNodeMap atts = textNode.getAttributes();
Node font = atts.getNamedItem("font"); //$NON-NLS-1$
Node color = atts.getNamedItem("color"); //$NON-NLS-1$
String fontId = null;
String colorId = null;
if (font != null) {
fontId = "f." + font.getNodeValue(); //$NON-NLS-1$
}
if (color != null) {
colorId = "c." + color.getNodeValue(); //$NON-NLS-1$
}
p.parseRegularText(text, expandURLs, getHyperlinkSettings(), fontId,
colorId);
}
public void parseRegularText(String regularText, boolean convertURLs) {
reset();
if (regularText == null)
return;
regularText = getNormalizedText(regularText);
Paragraph p = new Paragraph(true);
paragraphs.add(p);
int pstart = 0;
for (int i = 0; i < regularText.length(); i++) {
char c = regularText.charAt(i);
if (p == null) {
p = new Paragraph(true);
paragraphs.add(p);
}
if (c == '\n') {
String text = regularText.substring(pstart, i);
pstart = i + 1;
p.parseRegularText(text, convertURLs, getHyperlinkSettings(),
null);
p = null;
}
}
if (p != null) {
// no new line
String text = regularText.substring(pstart);
p.parseRegularText(text, convertURLs, getHyperlinkSettings(), null);
}
}
public HyperlinkSettings getHyperlinkSettings() {
return hyperlinkSettings;
}
public void setHyperlinkSettings(HyperlinkSettings settings) {
this.hyperlinkSettings = settings;
}
private void reset() {
if (paragraphs == null)
paragraphs = new Vector();
paragraphs.clear();
selectedSegmentIndex = -1;
savedSelectedLinkIndex = -1;
selectableSegments = null;
}
IFocusSelectable[] getFocusSelectableSegments() {
if (selectableSegments != null || paragraphs == null)
return selectableSegments;
Vector result = new Vector();
for (int i = 0; i < paragraphs.size(); i++) {
Paragraph p = (Paragraph) paragraphs.get(i);
ParagraphSegment[] segments = p.getSegments();
for (int j = 0; j < segments.length; j++) {
if (segments[j] instanceof IFocusSelectable)
result.add(segments[j]);
}
}
selectableSegments = (IFocusSelectable[]) result
.toArray(new IFocusSelectable[result.size()]);
return selectableSegments;
}
public IHyperlinkSegment getHyperlink(int index) {
IFocusSelectable[] selectables = getFocusSelectableSegments();
if (selectables.length>index) {
IFocusSelectable link = selectables[index];
if (link instanceof IHyperlinkSegment)
return (IHyperlinkSegment)link;
}
return null;
}
public IHyperlinkSegment findHyperlinkAt(int x, int y) {
IFocusSelectable[] selectables = getFocusSelectableSegments();
for (int i = 0; i < selectables.length; i++) {
IFocusSelectable segment = selectables[i];
if (segment instanceof IHyperlinkSegment) {
IHyperlinkSegment link = (IHyperlinkSegment)segment;
if (link.contains(x, y))
return link;
}
}
return null;
}
public int getHyperlinkCount() {
return getFocusSelectableSegments().length;
}
public int indexOf(IHyperlinkSegment link) {
IFocusSelectable[] selectables = getFocusSelectableSegments();
for (int i = 0; i < selectables.length; i++) {
IFocusSelectable segment = selectables[i];
if (segment instanceof IHyperlinkSegment) {
IHyperlinkSegment l = (IHyperlinkSegment)segment;
if (link==l)
return i;
}
}
return -1;
}
public ParagraphSegment findSegmentAt(int x, int y) {
for (int i = 0; i < paragraphs.size(); i++) {
Paragraph p = (Paragraph) paragraphs.get(i);
ParagraphSegment segment = p.findSegmentAt(x, y);
if (segment != null)
return segment;
}
return null;
}
public void clearCache(String fontId) {
for (int i = 0; i < paragraphs.size(); i++) {
Paragraph p = (Paragraph) paragraphs.get(i);
p.clearCache(fontId);
}
}
public IFocusSelectable getSelectedSegment() {
if (selectableSegments==null || selectedSegmentIndex == -1)
return null;
return selectableSegments[selectedSegmentIndex];
}
public int getSelectedSegmentIndex() {
return selectedSegmentIndex;
}
public boolean linkExists(IHyperlinkSegment link) {
if (selectableSegments==null)
return false;
for (int i=0; i<selectableSegments.length; i++) {
if (selectableSegments[i]==link)
return true;
}
return false;
}
public boolean traverseFocusSelectableObjects(boolean next) {
IFocusSelectable[] selectables = getFocusSelectableSegments();
if (selectables == null)
return false;
int size = selectables.length;
if (next) {
selectedSegmentIndex++;
} else
selectedSegmentIndex--;
if (selectedSegmentIndex < 0 || selectedSegmentIndex > size - 1) {
selectedSegmentIndex = -1;
}
return selectedSegmentIndex != -1;
}
public IFocusSelectable getNextFocusSegment(boolean next) {
IFocusSelectable[] selectables = getFocusSelectableSegments();
if (selectables == null)
return null;
int nextIndex = next?selectedSegmentIndex+1:selectedSegmentIndex-1;
if (nextIndex < 0 || nextIndex > selectables.length - 1) {
return null;
}
return selectables[nextIndex];
}
public boolean restoreSavedLink() {
if (savedSelectedLinkIndex!= -1) {
selectedSegmentIndex = savedSelectedLinkIndex;
return true;
}
return false;
}
public void selectLink(IHyperlinkSegment link) {
if (link == null) {
savedSelectedLinkIndex = selectedSegmentIndex;
selectedSegmentIndex = -1;
}
else {
select(link);
}
}
public void select(IFocusSelectable selectable) {
IFocusSelectable[] selectables = getFocusSelectableSegments();
selectedSegmentIndex = -1;
if (selectables == null)
return;
for (int i = 0; i < selectables.length; i++) {
if (selectables[i].equals(selectable)) {
selectedSegmentIndex = i;
break;
}
}
}
public boolean hasFocusSegments() {
IFocusSelectable[] segments = getFocusSelectableSegments();
if (segments.length > 0)
return true;
return false;
}
public void dispose() {
paragraphs = null;
selectedSegmentIndex = -1;
savedSelectedLinkIndex = -1;
selectableSegments = null;
}
/**
* @return Returns the whitespaceNormalized.
*/
public boolean isWhitespaceNormalized() {
return whitespaceNormalized;
}
/**
* @param whitespaceNormalized
* The whitespaceNormalized to set.
*/
public void setWhitespaceNormalized(boolean whitespaceNormalized) {
this.whitespaceNormalized = whitespaceNormalized;
}
}