/***************************************************************************** * Copyright (c) 2010 CEA LIST. * * * 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: * Remi Schnekenburger (CEA LIST) remi.schnekenburger@cea.fr - Initial API and implementation *****************************************************************************/ package org.eclipse.papyrus.uml.diagram.common.figure.node; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.Stack; import org.eclipse.draw2d.Label; import org.eclipse.draw2d.geometry.Point; import org.eclipse.draw2d.text.BlockFlow; import org.eclipse.draw2d.text.FlowPage; import org.eclipse.gmf.runtime.common.ui.util.DisplayUtils; import org.eclipse.gmf.runtime.draw2d.ui.text.TextFlowEx; import org.eclipse.jface.resource.FontDescriptor; import org.eclipse.jface.resource.JFaceResources; import org.eclipse.papyrus.uml.diagram.common.parser.HTMLCleaner; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Font; import org.eclipse.swt.graphics.FontData; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.widgets.Display; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; /** * Corner bend figure able to display styled text formatted in html */ public class HTMLCornerBentFigure extends CornerBentFigure implements ILabelFigure, IMultilineEditableFigure { /** indicates if the figure should use local coordinates or not */ protected boolean useLocalCoordinates = false; /** background color for this figure */ static final Color THIS_BACK = new Color(null, 248, 249, 214); /** font used by default by this figure */ static final Font FCORNERBENTCONTENTLABEL_FONT = new Font(Display.getCurrent(), "Arial", 8, SWT.NORMAL); /** key for the font style, corresponding to the type of font */ private static final String FONT_NAME = "face"; /** key for the font style, corresponding to the type of font */ private static final String FONT_SIZE = "size"; /** main flow page */ protected FlowPage page; /** properties stack to store which format to apply */ protected Stack<Styles> textProperties = new Stack<Styles>(); /** font used for the figure */ private FontData currentFontData; /** * set of font datas used by this comment. It will be cleaned juste after * the creation of the comment */ private Set<FontData> cachedFontDatas = new HashSet<FontData>(); /** * Creates a new HTMLCornerBentFigure. */ public HTMLCornerBentFigure() { super(); this.setBackgroundColor(THIS_BACK); createContents(); } /** * return the label thath contains the icon. * * @return the label that contains the icon */ public Label getIconLabel() { return this.iconLabel; } /** * Generates the basic contents for this figure */ protected void createContents() { // simply creates a Flow page, that will contains BlockFlows // representing the html content page = new FlowPage(); page.setForegroundColor(getForegroundColor()); this.add(page); } /** * @see org.eclipse.draw2d.Figure#useLocalCoordinates() * * @return <code>true</code> if this Figure uses local coordinates */ protected boolean useLocalCoordinates() { return useLocalCoordinates; } /** * Returns the current instance of this class * * @return the current instance of this class */ public HTMLCornerBentFigure getCornerBentFigure() { return this; } /** * @see org.eclipse.papyrus.uml.diagram.common.figure.node.ILabelFigure#getIcon() * * @return */ public Image getIcon() { return null; } /** * @see org.eclipse.papyrus.uml.diagram.common.figure.node.ILabelFigure#getText() * * @return */ public String getText() { return ""; } /** * @see org.eclipse.papyrus.uml.diagram.common.figure.node.ILabelFigure#setIcon(org.eclipse.swt.graphics.Image) * * @param icon */ public void setIcon(Image icon) { // TODO Auto-generated method stub } /** * @see org.eclipse.papyrus.uml.diagram.common.figure.node.ILabelFigure#setText(java.lang.String) * * @param text */ public void setText(String text) { // remove all children from page. page.removeAll(); // init the first font data currentFontData = new FontData("Wingdings", 8, SWT.NORMAL); // generates new ones generateBlockForText(text, page); } /** * Generates block list for the given text, and adds it to the root flow * page * * @param text * the string to display */ protected void generateBlockForText(String text, FlowPage page) { // parse the HMTL text and transforms it into a tree. "Body" tags // enforce the character chain to be a valid xml chain NodeList nodeList = generateNodeList("<body>" + text + "</body>"); // generate blocks from this list and adds it to the flow page children if(nodeList.getLength() > 0) { generateBlocksFromNodeList(nodeList, page); } else { // problem during parsing // return only one text flow with the content of the text TextFlowEx textFlow = new TextFlowEx(text); page.add(textFlow); } } /** * Builds the structure and content of block flows for a given list of nodes * * @param nodeList * the list of nodes from which to generates the blockflows */ protected void generateBlocksFromNodeList(NodeList nodeList, BlockFlow parentFlow) { // for each element in the list, generates the corresponding blocks for(int i = 0; i < nodeList.getLength(); i++) { Node node = nodeList.item(i); String nodeName = node.getNodeName(); short nodeType = node.getNodeType(); if(nodeType == Node.TEXT_NODE) { generateTextFromTextNode(node, parentFlow); } else { switch(HTMLTags.valueOf(nodeName)) { case body: // main tag for the comment body // create a block for the body generateBlocksFromBodyNode(node, parentFlow); break; case h3: generateBlocksFromH3Node(node, parentFlow); break; case h4: // sub section heading generateBlocksFromH4Node(node, parentFlow); break; case h5: // sub sub section heading generateBlocksFromH5Node(node, parentFlow); break; case strong: // bold character generateBlocksFromStrongNode(node, parentFlow); break; case em: // italic generateBlocksFromItalicNode(node, parentFlow); break; case u: // underline generateBlocksFromUnderlineNode(node, parentFlow); break; case sub: // subscript break; case sup: // superscript break; case blockquote: // indent left or right break; case table: // table break; case p: // paragraph generateBlocksFromParagraphNode(node, parentFlow); break; case br: generateBlocksFromBRNode(node, parentFlow); break; case font: generateBlocksForFontNode(node, parentFlow); default: break; } } } } /** * Generates code from a node representing an underlined text. * * @param node * the node from which to generate belowk flows * @param parentFlow * the parent block flow which will contain the block created */ protected void generateBlocksFromUnderlineNode(Node node, BlockFlow parentFlow) { NodeList childrenNodes = node.getChildNodes(); textProperties.push(Styles.underline); generateBlocksFromNodeList(childrenNodes, parentFlow); textProperties.pop(); } /** * Generates code from a node representing a text. * * @param node * the node from which to generate belowk flows * @param parentFlow * the parent block flow which will contain the block created */ protected void generateTextFromTextNode(Node node, BlockFlow parentFlow) { // node has type: TEXT_NODE String text = HTMLCleaner.cleanHTMLTags(node.getNodeValue()); TextFlowEx textFlow = new TextFlowEx(text); textFlow.setTextUnderline(false); boolean italic = false; boolean strong = false; boolean quote = false; boolean codeSample = false; String fontName = "Arial"; int fontSize = 2; // calculate the font to apply for(Styles style : textProperties) { switch(style) { case italic: italic = true; break; case strong: strong = true; break; case underline: textFlow.setTextUnderline(true); break; case quote: quote = true; break; case code: codeSample = true; break; case font: fontName = (Styles.font.getData().get(FONT_NAME) != null) ? (String)Styles.font.getData().get(FONT_NAME) : "Arial"; fontSize = (Styles.font.getData().get(FONT_SIZE) != null) ? ((Integer)Styles.font.getData().get(FONT_SIZE)) : 2; break; default: break; } } int style = SWT.NORMAL; if(italic) { style = style | SWT.ITALIC; } if(strong) { style = style | SWT.BOLD; } FontData fontData; if(codeSample) { fontData = new FontData("Lucida Console", 8, style); } else if(quote) { fontData = new FontData("Monotype Corsiva", 10, style); textFlow.setBackgroundColor(DisplayUtils.getDisplay().getSystemColor(SWT.COLOR_RED)); } else { // font size = [1..7] in html, but does not correspond to system // size... 2 by default => 8 in real size. // so: real size = (html font size)+6 fontData = new FontData(fontName, 2 * fontSize + 4, style); } Font font = (Font)JFaceResources.getResources().get(FontDescriptor.createFrom(fontData)); textFlow.setFont(font); parentFlow.add(textFlow); } /** * Generates code from a node representing a bolded text. * * @param node * the node from which to generate block flows * @param parentFlow * the parent block flow which will contain the block created */ protected void generateBlocksFromStrongNode(Node node, BlockFlow parentFlow) { NodeList childrenNodes = node.getChildNodes(); textProperties.push(Styles.strong); generateBlocksFromNodeList(childrenNodes, parentFlow); textProperties.pop(); } /** * Generates code from a node representing an italic styled text. * * @param node * the node from which to generate belowk flows * @param parentFlow * the parent block flow which will contain the block created */ protected void generateBlocksFromItalicNode(Node node, BlockFlow parentFlow) { NodeList childrenNodes = node.getChildNodes(); textProperties.push(Styles.italic); generateBlocksFromNodeList(childrenNodes, parentFlow); textProperties.pop(); } /** * Generates code from a node with new Font. * * @param node * the node from which to generate belowk flows * @param parentFlow * the parent block flow which will contain the block created */ protected void generateBlocksForFontNode(Node node, BlockFlow parentFlow) { // retrieves the font to apply Node fontNameNode = node.getAttributes().getNamedItem("face"); Node fontSizeNode = node.getAttributes().getNamedItem("size"); String oldFont = ""; int oldSize = 8; if(fontNameNode != null) { String fontName = fontNameNode.getNodeValue(); oldFont = (Styles.font.getData().get(FONT_NAME) != null) ? (String)Styles.font.getData().get(FONT_NAME) : "Arial"; Styles.font.getData().put(FONT_NAME, fontName); textProperties.push(Styles.font); } if(fontSizeNode != null) { int fontSize = Integer.parseInt(fontSizeNode.getNodeValue()); oldSize = (Styles.font.getData().get(FONT_SIZE) != null) ? ((Integer)Styles.font.getData().get(FONT_SIZE)) : 2; Styles.font.getData().put(FONT_SIZE, fontSize); textProperties.push(Styles.font); } NodeList childrenNodes = node.getChildNodes(); generateBlocksFromNodeList(childrenNodes, parentFlow); if(fontNameNode != null) { Styles.font.getData().put(FONT_NAME, oldFont); textProperties.pop(); } if(fontSizeNode != null) { Styles.font.getData().put(FONT_SIZE, oldSize); textProperties.pop(); } } /** * Generates code from a node representing a H3 section (header section). * * @param node * the node from which to generate belowk flows * @param parentFlow * the parent block flow which will contain the block created */ protected void generateBlocksFromH3Node(Node node, BlockFlow parentFlow) { BlockFlow blockFlow = new BlockFlow(); NodeList childrenNodes = node.getChildNodes(); textProperties.push(Styles.header3); generateBlocksFromNodeList(childrenNodes, blockFlow); textProperties.pop(); parentFlow.add(blockFlow); } /** * Generates code from a node representing a H3 section (header section). * * @param node * the node from which to generate belowk flows * @param parentFlow * the parent block flow which will contain the block created */ protected void generateBlocksFromH4Node(Node node, BlockFlow parentFlow) { BlockFlow blockFlow = new BlockFlow(); NodeList childrenNodes = node.getChildNodes(); textProperties.push(Styles.header4); generateBlocksFromNodeList(childrenNodes, blockFlow); textProperties.pop(); parentFlow.add(blockFlow); } /** * Generates code from a node representing a H3 section (header section). * * @param node * the node from which to generate belowk flows * @param parentFlow * the parent block flow which will contain the block created */ protected void generateBlocksFromH5Node(Node node, BlockFlow parentFlow) { BlockFlow blockFlow = new BlockFlow(); NodeList childrenNodes = node.getChildNodes(); textProperties.push(Styles.header5); generateBlocksFromNodeList(childrenNodes, blockFlow); textProperties.pop(); parentFlow.add(blockFlow); } /** * Pretty prints the list of child nodes * * @param childNodes */ @SuppressWarnings("unused") private void debug(NodeList childNodes) { for(int i = 0; i < childNodes.getLength(); i++) { System.err.println("[" + i + "] " + childNodes.item(i).getNodeName()); } } /** * Generates code from a node representing a body. * * @param node * the node from which to generate block flows * @param parentFlow * the parent block flow which will contain the block created */ protected void generateBlocksFromBodyNode(Node node, BlockFlow parentFlow) { BlockFlow blockFlow = new BlockFlow(); NodeList childrenNodes = node.getChildNodes(); generateBlocksFromNodeList(childrenNodes, blockFlow); parentFlow.add(blockFlow); } /** * Generates code from a node representing a paragraph. * * @param node * the node from which to generate block flows * @param parentFlow * the parent block flow which will contain the block created */ protected void generateBlocksFromParagraphNode(Node node, BlockFlow parentFlow) { BlockFlow blockFlow = new BlockFlow(); // perhaps a style is associated to the paragraph (class="code sample" // for example) NamedNodeMap attributes = node.getAttributes(); Node classNode = attributes.getNamedItem("class"); boolean hasToPop = false; if(classNode != null) { String classNodeValue = classNode.getNodeValue(); if("codeSample".equals(classNodeValue)) { hasToPop = true; textProperties.push(Styles.code); } else if("quote".equals(classNodeValue)) { textProperties.push(Styles.quote); hasToPop = true; } } NodeList childrenNodes = node.getChildNodes(); generateBlocksFromNodeList(childrenNodes, blockFlow); if(hasToPop) { textProperties.pop(); } parentFlow.add(blockFlow); } /** * Generates code from a node representing a carraige return. * * @param node * the node from which to generate block flows * @param parentFlow * the parent block flow which will contain the block created */ protected void generateBlocksFromBRNode(Node node, BlockFlow parentFlow) { BlockFlow blockFlow = new BlockFlow(); parentFlow.add(blockFlow); } /** * Generates a list of nodes from the parse of an html text * * @param text * the text to parse * @return the parsed text under the form of a list of nodes */ protected NodeList generateNodeList(String text) { return HTMLCommentParser.parse(text); } /** * Valid HTML tags enumeration */ protected enum HTMLTags { body(""), // main tag for the comment body h3(""), // section heading h4(""), // sub section heading h5(""), // sub sub section heading strong(""), // bold character em(""), // italic u(""), // underline sub(""), // subscript sup(""), // superscript blockquote(""), // indent left or right table(""), // table p(""), // paragraph br(""), // new line font(""); // specific font /** additional data for this enum */ protected String data; HTMLTags(String data) { this.data = data; } /** * Sets the data for this enum * * @param data * the data to set */ public void setData(String data) { this.data = data; } /** * Returns the data associated to this enum * * @return the data associated to this enum */ public String getData() { return data; } } /** * Styles to apply to the text */ protected enum Styles { strong, header3, header4, header5, underline, italic, code, subscript, supscript, quote, font(new HashMap<String, Object>()); /** additional data */ private Map<String, Object> data; Styles() { this.data = null; } Styles(Map<String, Object> data) { this.data = data; } /** * sets the data associated to this enum * * @param data * the data to set */ public void setData(Map<String, Object> data) { this.data = data; } /** * Returns the data for this enum * * @return the data for this enum */ public Map<String, Object> getData() { return data; } } /** * * @see org.eclipse.papyrus.uml.diagram.common.figure.node.IMultilineEditableFigure#getEditionLocation() * */ public Point getEditionLocation() { return getBounds().getTopLeft(); } }