/** * Copyright 2010 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.waveprotocol.wave.client.editor.content.paragraph; import static org.waveprotocol.wave.client.editor.content.paragraph.constants.ParagraphRenderingConstants.INDENT_UNIT_SIZE_PX; import com.google.gwt.dom.client.Document; import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.Style; import com.google.gwt.dom.client.Style.FontWeight; import com.google.gwt.dom.client.Style.Unit; import org.waveprotocol.wave.client.common.util.DomHelper; import org.waveprotocol.wave.client.editor.content.ContentElement; import org.waveprotocol.wave.client.editor.content.HasImplNodelets; import org.waveprotocol.wave.client.editor.content.Renderer; import org.waveprotocol.wave.client.editor.content.paragraph.Paragraph.Alignment; import org.waveprotocol.wave.client.editor.content.paragraph.Paragraph.Direction; /** * @author danilatos@google.com (Daniel Danilatos) */ public class DefaultParagraphHtmlRenderer implements ParagraphHtmlRenderer { // TODO(danilatos): Use CssResource private static final String NUMBERED_CLASSNAME = "numbered"; private static final String[] BULLET_CLASSNAMES = new String[] { "bullet-type-0", "bullet-type-1", "bullet-type-2" }; private static String bulletClassName(int indent) { return BULLET_CLASSNAMES[indent % 3]; } /** * Size of the largest heading */ public static final double MAX_HEADING_SIZE_EM = 1.75; /** * Size of the smallest heading */ public static final double MIN_HEADING_SIZE_EM = 1.0; /** * Tag name to use for html implementation for paragraph element mode. */ public static final String PARAGRAPH_IMPL_TAGNAME = "div"; /** * Tag name to use for html implementation for list element mode. */ public static final String LIST_IMPL_TAGNAME = "li"; private final String implTagName; public DefaultParagraphHtmlRenderer() { this(PARAGRAPH_IMPL_TAGNAME); } public DefaultParagraphHtmlRenderer(String implTagName) { this.implTagName = implTagName; } /** * Unless very specific behaviour is wanted, it is better to override * {@link #createNodelet(Renderable)} if a different nodelet from a simple * named element is desired. */ @Override public Element createDomImpl(Renderable element) { Element nodelet = createNodelet(element); assert nodelet.getFirstChild() == null : "was not given an empty nodelet"; ParagraphHelper.INSTANCE.onEmpty(nodelet); return element.setAutoAppendContainer(nodelet); } @Override public void updateRendering(HasImplNodelets element, String type, String listStyle, int indent, Alignment alignment, Direction direction) { Element implNodelet = element.getImplNodelet(); ParagraphBehaviour behaviour = ParagraphBehaviour.of(type); boolean toListItem = behaviour == ParagraphBehaviour.LIST; boolean isListItem = implNodelet.getTagName().equalsIgnoreCase(LIST_IMPL_TAGNAME); if (isListItem != toListItem) { Element newNodelet = createNodeletInner(toListItem); DomHelper.replaceElement(implNodelet, newNodelet); element.setBothNodelets(newNodelet); // Ideally onRepair shouldn't require a ContentElement ParagraphHelper.INSTANCE.onRepair((ContentElement) element); implNodelet = newNodelet; } //// Type logic //// double fontSize = -1; FontWeight fontWeight = null; implNodelet.removeClassName(NUMBERED_CLASSNAME); switch (behaviour) { case LIST: if (Paragraph.LIST_STYLE_DECIMAL.equals(listStyle)) { implNodelet.addClassName(NUMBERED_CLASSNAME); } break; case HEADING: fontWeight = FontWeight.BOLD; double headingNum = Integer.parseInt(type.substring(1)); // Do this with CSS instead. // h1 -> 1.75, h4 -> 1, others linearly in between. double factor = 1 - (headingNum - 1) / (Paragraph.NUM_HEADING_SIZES - 1); fontSize = MIN_HEADING_SIZE_EM + factor * (MAX_HEADING_SIZE_EM - MIN_HEADING_SIZE_EM); break; } //// Indent logic //// for (String bulletType : BULLET_CLASSNAMES) { implNodelet.removeClassName(bulletType); } if (behaviour == ParagraphBehaviour.LIST) { if (listStyle == null) { implNodelet.addClassName(bulletClassName(indent)); } indent++; } int margin = indent * INDENT_UNIT_SIZE_PX; //// Update actual values //// // NOTE(danilatos): For these, it might be more efficient to check that the // value has changed before changing it. This is not currently known. Style style = implNodelet.getStyle(); if (fontSize != -1) { style.setFontSize(fontSize, Unit.EM); } else { style.clearFontSize(); } if (fontWeight != null) { style.setFontWeight(fontWeight); } else { style.clearFontWeight(); } if (alignment != null) { style.setProperty("textAlign", alignment.cssValue()); } else { style.clearProperty("textAlign"); } if (direction != null) { style.setProperty("direction", direction.cssValue()); } else { style.clearProperty("direction"); } if (margin == 0) { style.clearMarginLeft(); style.clearMarginRight(); } else { if (direction == Direction.RTL) { style.setMarginRight(margin, Unit.PX); style.clearMarginLeft(); } else { style.setMarginLeft(margin, Unit.PX); style.clearMarginRight(); } } } @Override public void updateListValue(HasImplNodelets element, int value) { element.getImplNodelet().setAttribute("value", String.valueOf(value)); } /** * Override this method to use something other than a simple named element. * (The tag name is parametrisable via the constructor). * * @param element See {@link Renderer#createDomImpl(Renderable)} * @return the nodelet to use for rendering, must not contain any children */ protected Element createNodelet(Renderable element) { return createNodeletInner(false); } private Element createNodeletInner(boolean isListItem) { return Document.get().createElement(isListItem ? LIST_IMPL_TAGNAME : implTagName); } }