package org.swellrt.beta.client.js.editor.annotation;
import java.util.function.Consumer;
import org.waveprotocol.wave.client.common.util.JsoStringSet;
import org.waveprotocol.wave.client.editor.content.CMutableDocument;
import org.waveprotocol.wave.client.editor.content.ContentDocument;
import org.waveprotocol.wave.client.editor.content.ContentElement;
import org.waveprotocol.wave.client.editor.content.ContentNode;
import org.waveprotocol.wave.client.editor.content.misc.CaretAnnotations;
import org.waveprotocol.wave.model.document.MutableAnnotationSet;
import org.waveprotocol.wave.model.document.RangedAnnotation;
import org.waveprotocol.wave.model.document.indexed.LocationMapper;
import org.waveprotocol.wave.model.document.util.DocHelper;
import org.waveprotocol.wave.model.document.util.LineContainers;
import org.waveprotocol.wave.model.document.util.Point;
import org.waveprotocol.wave.model.document.util.Range;
import org.waveprotocol.wave.model.util.StringSet;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.Node;
import com.google.gwt.user.client.Event;
import jsinterop.annotations.JsFunction;
import jsinterop.annotations.JsIgnore;
import jsinterop.annotations.JsOptional;
import jsinterop.annotations.JsProperty;
import jsinterop.annotations.JsType;
/**
*
*
* @author pablojan@gmail.com
*
*/
@JsType(namespace = "swellrt", name = "Annotation")
public class AnnotationInstance {
public static final int EVENT_ADDED = 1;
public static final int EVENT_MUTATED = 2;
public static final int EVENT_REMOVED = 3;
public static final int EVENT_MOUSE = 4;
@JsFunction
public interface Handler {
public boolean exec(int type, AnnotationInstance annotation, @JsOptional Event event);
}
/** the selection range is equals to annotation range or inside it */
public static final int MATCH_IN = 0;
/**
* the selection range is partially out of the annotation range or selection
* spans more content beyond annotation
*/
public static final int MATCH_OUT = 1;
public static int getRangeMatch(Range selectionRange, Range annotationRange) {
boolean in = (selectionRange.getStart() >= annotationRange.getStart()
&& selectionRange.getEnd() <= annotationRange.getEnd());
return in ? AnnotationInstance.MATCH_IN : AnnotationInstance.MATCH_OUT;
}
private final Annotation annotation;
// public to Js */
public final String name;
public String value;
public Range range;
public int matchType;
public String text;
private final LocationMapper<ContentNode> mapper;
private final CMutableDocument doc;
private final MutableAnnotationSet<Object> localAnnotations;
private final CaretAnnotations caret;
private final ContentElement node; // optional
protected static Node lookupNodelet(ContentNode n) {
if (n != null) {
Node rightwardsNodelet = n.getImplNodeletRightwards();
if (rightwardsNodelet != null) {
return rightwardsNodelet;
}
}
return null;
}
@SuppressWarnings("rawtypes")
protected static Range getAnnotationRange(ContentElement node, String name) {
int start = node.getMutableDoc().getLocation(node);
int end = node.getMutableDoc().getLocation(node.getNextSibling());
StringSet keys = JsoStringSet.create();
keys.add(name);
Range[] result = new Range[1];
Consumer<RangedAnnotation> rangeMatcher = new Consumer<RangedAnnotation>() {
@Override
public void accept(RangedAnnotation r) {
if (r.value() != null && r.key().equals(name)) {
result[0] = new Range(r.start(), r.end());
}
}
};
if (Annotation.isLocal(name)) {
node.getContext().localAnnotations().rangedAnnotations(start, end, keys)
.forEach(rangeMatcher);
} else {
node.getMutableDoc().rangedAnnotations(start, end, keys).forEach(rangeMatcher);
}
return result[0];
}
/**
* Use this method from annotation search. See {@link AnnotationRegistry}
*/
@JsIgnore
public static AnnotationInstance create(ContentDocument doc, String name, String value,
Range range, int matchType) {
return AnnotationInstance.create(name, value, doc.getMutableDoc(), doc.getLocationMapper(),
doc.getLocalAnnotations(), doc.getContext().editing().editorContext().getCaretAnnotations(),
range, matchType, null);
}
@JsIgnore
public static AnnotationInstance create(String name, String value, Range range,
ContentElement node, int matchType) {
return AnnotationInstance.create(name, value, node.getMutableDoc(), node.getLocationMapper(),
node.getContext().localAnnotations(),
node.getContext().editing().editorContext().getCaretAnnotations(), range, matchType, node);
}
/**
* Use this method from event handlers. See {@link TextAnnotation}
*/
@JsIgnore
public static AnnotationInstance create(String name, String value, ContentElement node) {
Range range = getAnnotationRange(node, name);
return AnnotationInstance.create(name, value, node.getMutableDoc(), node.getLocationMapper(),
node.getContext().localAnnotations(),
node.getContext().editing().editorContext().getCaretAnnotations(), range, MATCH_IN, node);
}
@JsIgnore
public static AnnotationInstance create(String name, String value, CMutableDocument doc,
LocationMapper<ContentNode> mapper, MutableAnnotationSet<Object> localAnnotations,
CaretAnnotations caret, Range range, int matchType, ContentElement node) {
return new AnnotationInstance(name, value, doc, mapper, localAnnotations, caret, range,
matchType, node);
}
protected AnnotationInstance(String name, String value, CMutableDocument doc,
LocationMapper<ContentNode> mapper, MutableAnnotationSet<Object> localAnnotations,
CaretAnnotations caret, Range range, int matchType, ContentElement node) {
super();
this.annotation = AnnotationRegistry.get(name);
this.name = name;
this.value = value;
this.range = range != null ? Range.create(range.getStart(), range.getEnd()) : null; // clone
this.matchType = matchType;
this.mapper = this.doc = doc;
try {
// sometimes range's content change faster than this call,
// prevent exception
this.text = range != null ? DocHelper.getText(doc, range.getStart(), range.getEnd()) : "";
} catch (Exception e) {
this.text = "";
}
this.localAnnotations = localAnnotations;
this.caret = caret;
this.node = node;
}
@JsProperty
public Element getLine() {
if (range != null) {
Point<ContentNode> point = doc.locate(range.getStart() + 1);
ContentElement lineNode = LineContainers.getRelatedLineElement(doc, point);
return lookupNodelet(lineNode).getParentElement();
} else if (node != null) {
ContentElement lineNode = LineContainers.getRelatedLineElement(doc,
doc.locate(doc.getLocation(node)));
return lookupNodelet(lineNode).getParentElement();
}
return null;
}
@JsProperty
public Node getNode() {
Node e = lookupNodelet(node);
if (e == null && range != null) {
Point<ContentNode> point = doc.locate(range.getStart() + 1);
e = point.getContainer().getParentElement().getImplNodelet();
}
return e;
}
public void update(String value) {
if (range != null) {
annotation.update(doc, mapper, localAnnotations, caret, range, value);
this.value = value;
}
}
public void mutate(String text) {
if (range != null) {
Range mutatedRange = annotation.mutate(doc, mapper, localAnnotations, caret, range, text,
value);
this.text = text;
this.range = mutatedRange;
}
}
public void clear() {
if (range != null) {
annotation.reset(doc, mapper, localAnnotations, caret, range);
this.value = null;
this.text = null;
this.range = null;
}
}
}