Package org.waveprotocol.wave.client.editor.content.paragraph

Source Code of org.waveprotocol.wave.client.editor.content.paragraph.Paragraph$ListStyler

/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements.  See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership.  The ASF licenses this file
* to you 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 org.waveprotocol.wave.client.editor.ElementHandlerRegistry;
import org.waveprotocol.wave.client.editor.NodeEventHandler;
import org.waveprotocol.wave.client.editor.content.CMutableDocument;
import org.waveprotocol.wave.client.editor.content.ContentElement;
import org.waveprotocol.wave.client.editor.content.ContentNode;
import org.waveprotocol.wave.model.document.indexed.LocationMapper;
import org.waveprotocol.wave.model.document.util.LineContainers;
import org.waveprotocol.wave.model.document.util.Point;
import org.waveprotocol.wave.model.util.CollectionUtils;
import org.waveprotocol.wave.model.util.Preconditions;
import org.waveprotocol.wave.model.util.StringMap;
import org.waveprotocol.wave.model.util.ValueUtils;

/**
* An editable paragraph element
*
*/
public class Paragraph {

  /**
   * Tag name for regular paragraphs
   */
  public static final String TAGNAME = "p";

  /**
   * Number of headings, h1...h{NUM_HEADING_SIZES}
   */
  public static final int NUM_HEADING_SIZES = 4;

  /**
   * Subtype attribute, for things like headings, bullet points, etc.
   */
  public static final String SUBTYPE_ATTR = "t";

  /**
   * Indentation attribute. Integral values, in "indentation units"
   */
  public static final String INDENT_ATTR = "i";

  public static final int MAX_INDENT = 50;

  /**
   * Text alignment, left, right, center, justified
   */
  public static final String ALIGNMENT_ATTR = "a";

  /**
   * Text direction, ltr, rtl
   */
  public static final String DIRECTION_ATTR = "d";

  /**
   * Text alignment, left, right, center, justified
   */
  public enum Alignment implements ContentElement.Action, LineStyle {
    /***/ LEFT("l", "left"),
    /***/ RIGHT("r", "right"),
    /***/ CENTER("c", "center"),
    /***/ JUSTIFY("j", "justify");

    private static final StringMap<Alignment> map = CollectionUtils.createStringMap();
    static {
      for (Alignment a : Alignment.values()) {
        map.put(a.value, a);
      }
    }

    public static Alignment fromValue(String v) {
      return v != null ? map.get(v) : null;
    }

    static String cssFromValue(String v) {
      Alignment a = fromValue(v);
      return a != null ? a.cssValue() : "";
    }

    final String value, css;
    private Alignment(String value, String css) {
      this.value = value;
      this.css = css;
    }

    @Override public void execute(ContentElement e) {
      apply(e, true);
    }

    @Override public void apply(ContentElement e, boolean on) {
      e.getMutableDoc().setElementAttribute(e, ALIGNMENT_ATTR,
          this == LEFT ? null : (on ? value : null));
    }

    @Override public boolean isApplied(ContentElement e) {
      Alignment val = fromValue(e.getAttribute(ALIGNMENT_ATTR));
      return this == (val == null ? LEFT : val);
    }

    /**
     * @return CSS value for the "text-align" property
     */
    public String cssValue() {
      return css;
    }

    /** Convert css value back to an enum value, null if not found. */
    public static Alignment fromCssValue(String css) {
      for (Alignment a : Alignment.values()) {
        if (a.css.equals(css)) {
          return a;
        }
      }
      return null;
    }
  }

  /**
   * Text direction, ltr vs rtl
   */
  public enum Direction implements ContentElement.Action, LineStyle {
    /***/ LTR("l", "ltr", Alignment.LEFT, Alignment.RIGHT),
    /***/ RTL("r", "rtl", Alignment.RIGHT, Alignment.LEFT);

    private static final StringMap<Direction> map = CollectionUtils.createStringMap();
    static {
      for (Direction a : Direction.values()) {
        map.put(a.value, a);
      }
    }

    static Direction fromValue(String v) {
      return v != null ? map.get(v) : null;
    }

    static String cssFromValue(String v) {
      Direction a = fromValue(v);
      return a != null ? a.cssValue() : "";
    }

    final String value, css;
    final Alignment alignment, oppositeAlignment;
    private Direction(String value, String css, Alignment alignment, Alignment oppositeAlignment) {
      this.value = value;
      this.css = css;
      this.alignment = alignment;
      this.oppositeAlignment = oppositeAlignment;
    }

    @Override public void execute(ContentElement e) {
      apply(e, true);
    }

    @Override public void apply(ContentElement e, boolean on) {
      e.getMutableDoc().setElementAttribute(e, DIRECTION_ATTR,
          this == LTR ? null : (on ? value : null));
      if (on && oppositeAlignment.isApplied(e)) {
        alignment.apply(e, true);
      }
    }

    @Override public boolean isApplied(ContentElement e) {
      Direction val = fromValue(e.getAttribute(DIRECTION_ATTR));
      return this == (val == null ? LTR : val);
    }

    /**
     * @return HTML value for the "dir" attribute
     */
    public String cssValue() {
      return css;
    }
  }

  /**
   * Type for list element mode.
   */
  public static final String LIST_TYPE = "li";

  public static final String LIST_STYLE_ATTR = "listyle";

  public static final String LIST_STYLE_DECIMAL = "decimal";

  /**
   * Registers handlers for paragraphs
   */
  public static void register(ElementHandlerRegistry registry) {
    register(TAGNAME, registry);
  }

  /**
   * Default renderer that responds to mutation events
   */
  public static final ParagraphRenderer DEFAULT_RENDERER = new ParagraphRenderer(
      new DefaultParagraphHtmlRenderer());

  /**
   * Default NiceHtmlRenderer for paragraphs.
   */
  public static final ParagraphNiceHtmlRenderer DEFAULT_NICE_HTML_RENDERER =
      new ParagraphNiceHtmlRenderer();

  /**
   * Default handler for user events
   */
  public static final NodeEventHandler DEFAULT_EVENT_HANDLER = new ParagraphEventHandler();

  /**
   * Registers paragraph handlers for any provided tag names / type attributes.
   */
  public static void register(String tagName, ElementHandlerRegistry registry) {
    registry.registerEventHandler(tagName, DEFAULT_EVENT_HANDLER);
    registry.registerRenderingMutationHandler(tagName, DEFAULT_RENDERER);
  }

  /**
   * Indents paragraphs
   */
  public static final ContentElement.Action INDENTER = new ContentElement.Action() {
    public void execute(ContentElement e) {
      ParagraphEventHandler.indent(e, 1);
    }
  };

  /**
   * Outdents paragraphs
   */
  public static final ContentElement.Action OUTDENTER = new ContentElement.Action() {
    public void execute(ContentElement e) {
      ParagraphEventHandler.indent(e, -1);
    }
  };

  private static class RegularStyler implements LineStyle {
    private final String type;
    RegularStyler(String type) {
      this.type = type;
    }

    @Override public void apply(ContentElement e, boolean isOn) {
      e.getMutableDoc().setElementAttribute(e, SUBTYPE_ATTR, isOn ? type : null);
      e.getMutableDoc().setElementAttribute(e, LIST_STYLE_ATTR, null);
    }

    @Override public boolean isApplied(ContentElement e) {
      return ValueUtils.equal(type, e.getAttribute(SUBTYPE_ATTR));
    }
  }

  private static class ListStyler implements LineStyle {
    private final String type;
    ListStyler(String type) {
      this.type = type;
    }

    @Override public void apply(ContentElement e, boolean isOn) {
      e.getMutableDoc().setElementAttribute(e, SUBTYPE_ATTR, isOn ? LIST_TYPE : null);
      e.getMutableDoc().setElementAttribute(e, LIST_STYLE_ATTR, isOn ? type : null);
    }

    @Override public boolean isApplied(ContentElement e) {
      return Paragraph.LIST_TYPE.equals(e.getAttribute(SUBTYPE_ATTR))
          && ValueUtils.equal(type, e.getAttribute(LIST_STYLE_ATTR));
    }
  }

  public static LineStyle regularStyle(String type) {
    Preconditions.checkArgument(!LIST_TYPE.equals(type),
      "Don't use regularStyle() for list styles, use listStyle()");
    return new RegularStyler(type);
  }

  public static LineStyle listStyle(String listStyleType) {
    return new ListStyler(listStyleType);
  }

  /**
   * TODO(danilatos): Get rid of most if not all uses of ContentElement.Action
   * as they are better replaced by LineStyle. For now, this adapter.
   */
  public static ContentElement.Action asAction(final LineStyle style, final boolean isOn) {
    return new ContentElement.Action() {
      @Override public void execute(ContentElement e) {
        style.apply(e, isOn);
      }
    };
  }

  public static Line getFirstLine(LocationMapper<ContentNode> mapper, int start) {
    Point<ContentNode> point = mapper.locate(start);
    CMutableDocument doc = point.getContainer().getMutableDoc();
    LineContainers.checkNotParagraphDocument(doc);

    // get line element we are in:
    ContentNode first = LineContainers.getRelatedLineElement(doc, point);

    if (first == null) {
      return null;
    }

    // go through the lines one by one:
    return Line.fromLineElement(first.asElement());
  }

  public interface LineStyle {
    /**
     * Applies an action to a line based on a button state.
     *
     * @param e line element
     * @param isOn true if the style should be applied, false if it should be removed.
     */
    void apply(ContentElement e, boolean isOn);

    /**
     * @param e line element
     * @return true if the style is considered applied to the given element.
     */
    boolean isApplied(ContentElement e);
  }

  public static void toggle(LocationMapper<ContentNode> mapper,
                           int start, int end, LineStyle style) {
    traverse(mapper, start, end, asAction(style, !appliesEntirely(mapper, start, end, style)));
  }

  public static void apply(LocationMapper<ContentNode> mapper,
      int start, int end, LineStyle style, boolean isOn) {
    traverse(mapper, start, end, asAction(style, isOn));
  }

  public static boolean appliesEntirely(LocationMapper<ContentNode> mapper,
      int start, int end, final LineStyle style) {
    final boolean[] applied = new boolean[]{true, false};
    traverse(mapper, start, end, new ContentElement.Action() {
      @Override public void execute(ContentElement e) {
        applied[0] &= style.isApplied(e);
        applied[1] = true; // to make sure it ran at all
      }
    });

    return applied[0] && applied[1];
  }

  /**
   * Apply an action over all paragraphs entirely or partially contained by the
   * given range.
   *
   * Does not deep traverse to find all paragraphs, assumes they are siblings of
   * each other.
   *
   * @param mapper for getting nodes from the range
   * @param start start of range
   * @param end end of range
   * @param action action to perform on paragraphs (other nodes ignored)
   */
  public static void traverse(LocationMapper<ContentNode> mapper,
      int start, int end, ContentElement.Action action) {

    // go through the lines one by one:
    Line lineAt = getFirstLine(mapper, start);
    if (lineAt == null) {
      return;
    }

    while (lineAt != null) {
      ContentElement lineTag = lineAt.getLineElement();

      // apply if it's ok
      int position = mapper.getLocation(lineTag);
      if (position >= end) { // equals implies selection is before line tag, so don't apply.
        break;
      }

      // traverse to the next one
      action.execute(lineTag);
      lineAt = lineAt.next();
    }
  }

  /** Gets the previous local paragraph sibling, or null if there is one. */
  public static ContentNode getLocalParagraphBackwards(ContentNode at) {
    while (at != null && !LineRendering.isLocalParagraph(at)) {
      at = at.getPreviousSibling();
    }
    return at;
  }

  /**
   * @param node
   * @return true iff the given node is a list item.
   */
  // TODO(user): Handle line elements
  public static boolean isListItem(ContentNode node) {
    return LineRendering.isLocalParagraph(node)
        && Paragraph.LIST_TYPE.equals(node.asElement().getAttribute(SUBTYPE_ATTR));
  }

  /**
   * @param node
   * @return true iff the given node is a heading
   */
  public static boolean isHeading(ContentNode node) {
    String tagName = node.asElement().getAttribute(SUBTYPE_ATTR);
    if (tagName != null && tagName.length() == 2) {
      if (tagName.charAt(0) == 'h' || tagName.charAt(0) == 'H') {
        int size = tagName.charAt(1) - '0';
        if (size >= 1 && size <= 4) {
          return true;
        }
      }
    }
    return false;
  }

  /**
   * Returns the heading side of the given node (Assumes node is a heading.)
   *
   * @param node
   */
  public static int getHeadingSize(ContentNode node) {
    assert node.asElement() != null;
    String tagName = node.asElement().getAttribute(SUBTYPE_ATTR);
    assert tagName != null && tagName.length() == 2;
    int size = tagName.charAt(1) - '0';
    assert size >= 1 && size <= 4;
    return size;
  }

  static int getIndent(String str) {
    if (str == null) {
      return 0;
    }
    try {
      return Math.min(Math.max(0, Integer.parseInt(str)), Paragraph.MAX_INDENT);
    } catch (NumberFormatException e) {
      // Would only happen if the schema were invalidated
      return 0;
    }
  }

  public static boolean isDecimalListItem(ContentElement e) {
    return
        Paragraph.LIST_TYPE.equals(e.getAttribute(Paragraph.SUBTYPE_ATTR)) &&
        Paragraph.LIST_STYLE_DECIMAL.equals(e.getAttribute(Paragraph.LIST_STYLE_ATTR));
  }

  static int getIndent(ContentElement p) {
    return getIndent(p.getAttribute(INDENT_ATTR));
  }
}
TOP

Related Classes of org.waveprotocol.wave.client.editor.content.paragraph.Paragraph$ListStyler

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.