/* * (C) Copyright 2006-2008 Nuxeo SA (http://nuxeo.com/) and others. * * 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. * * Contributors: * Alexandre Russel * * $Id$ */ package org.nuxeo.ecm.platform.annotations.gwt.client.view; import java.util.ArrayList; import java.util.List; import org.nuxeo.ecm.platform.annotations.gwt.client.AnnotationConstant; import org.nuxeo.ecm.platform.annotations.gwt.client.controler.AnnotationController; import org.nuxeo.ecm.platform.annotations.gwt.client.model.Annotation; import org.nuxeo.ecm.platform.annotations.gwt.client.model.AnnotationChangeListener; import org.nuxeo.ecm.platform.annotations.gwt.client.model.AnnotationModel; import org.nuxeo.ecm.platform.annotations.gwt.client.util.CSSClassManager; import org.nuxeo.ecm.platform.annotations.gwt.client.util.ImageRangeXPointer; import org.nuxeo.ecm.platform.annotations.gwt.client.util.NullRangeXPointer; import org.nuxeo.ecm.platform.annotations.gwt.client.util.Point; import org.nuxeo.ecm.platform.annotations.gwt.client.util.StringRangeXPointer; import org.nuxeo.ecm.platform.annotations.gwt.client.util.Utils; import org.nuxeo.ecm.platform.annotations.gwt.client.util.Visitor; import org.nuxeo.ecm.platform.annotations.gwt.client.util.XPathUtil; import org.nuxeo.ecm.platform.annotations.gwt.client.util.XPointer; import org.nuxeo.ecm.platform.annotations.gwt.client.view.decorator.DecoratorVisitor; import org.nuxeo.ecm.platform.annotations.gwt.client.view.decorator.DecoratorVisitorFactory; import org.nuxeo.ecm.platform.annotations.gwt.client.view.decorator.ImageDecorator; import com.allen_sauer.gwt.log.client.Log; import com.google.gwt.dom.client.BodyElement; import com.google.gwt.dom.client.DivElement; import com.google.gwt.dom.client.Document; import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.ImageElement; import com.google.gwt.dom.client.NodeList; import com.google.gwt.user.client.Window; /** * @author <a href="mailto:arussel@nuxeo.com">Alexandre Russel</a> */ public class AnnotatedDocument implements AnnotationChangeListener { private List<Annotation> annotations = new ArrayList<Annotation>(); private List<Annotation> decoratedAnnotations = new ArrayList<Annotation>(); private static XPathUtil xPathUtil = new XPathUtil(); private final ImageDecorator decorator; private AnnotationController controller; public AnnotatedDocument(AnnotationController controller) { this.controller = controller; decorator = new ImageDecorator(controller); } public void onChange(AnnotationModel model, ChangeEvent ce) { annotations = model.getAnnotations(); Log.debug("On change: annotations.empty? " + annotations.isEmpty()); if (annotations.isEmpty() || ce == ChangeEvent.annotation) { return; } update(); } public void update() { update(false); } public void update(boolean forceDecorate) { Log.debug("Update annotations - forceDecorate: " + forceDecorate); if (annotations == null) { return; } if (forceDecorate) { decoratedAnnotations.clear(); removeAllAnnotatedAreas(); } for (Annotation annotation : annotations) { if (!decoratedAnnotations.contains(annotation)) { Log.debug("Decorate annotation"); decorate(annotation); decoratedAnnotations.add(annotation); } } int selectedAnnotationIndex = getSelectedAnnotationIndex(); if (selectedAnnotationIndex > -1) { updateSelectedAnnotation(selectedAnnotationIndex); } if (!isAnnotationsVisible()) { Log.debug("Hide annotations!"); hideAnnotations(); // disable popup listeners in case we just added a new annotation controller.disablePopupListeners(); } } public void preDecorateDocument() { Document document = Document.get(); Log.debug("preDecorateDocument -- isMultiImage? " + controller.isMultiImage()); preDecorateDocument(document); } private static void preDecorateDocument(Document document) { Log.debug("Predecorate document !"); NodeList<Element> elements = document.getElementsByTagName("img"); for (int x = 0; x < elements.getLength(); x++) { Element element = elements.getItem(x); DivElement divElement = document.createDivElement(); divElement.getStyle().setProperty("position", "relative"); divElement.setClassName(AnnotationConstant.IGNORED_ELEMENT); String path = xPathUtil.getXPath(element); path = XPathUtil.toIdableName(path); divElement.setId(path); Element nextSibling = element.getNextSiblingElement(); Element parent = element.getParentElement(); if (nextSibling == null) { parent.appendChild(divElement); } else { parent.insertBefore(divElement, nextSibling); } divElement.appendChild(element); } } public void decorate(Annotation annotation) { XPointer xpointer = annotation.getXpointer(); if (xpointer instanceof StringRangeXPointer) { decorateStringRange((StringRangeXPointer) xpointer, annotation); } else if (xpointer instanceof ImageRangeXPointer) { decorateImageRange((ImageRangeXPointer) xpointer, annotation); } } private void decorateImageRange(ImageRangeXPointer xpointer, Annotation annotation) { ImageElement img = xpointer.getImage(controller.isMultiImage()); if (img == null) { return; } Point[] points = controller.filterAnnotation(xpointer.getTopLeft(), xpointer.getBottomRight()); if (points == null) { return; } decorator.addAnnotatedArea(points[0].getX(), points[0].getY(), points[1].getX(), points[1].getY(), img, annotation, controller); } private void decorateStringRange(StringRangeXPointer xpointer, Annotation annotation) { DecoratorVisitor processor = DecoratorVisitorFactory.forAnnotation(annotation, controller); Visitor visitor = new Visitor(processor); visitor.process(xpointer.getOwnerDocument()); } public void updateSelectedAnnotation(int index) { Annotation annotation = annotations.get(index); BodyElement bodyElement = Document.get().getBody(); if (!(annotation.getXpointer() instanceof NullRangeXPointer)) { NodeList<Element> spans = bodyElement.getElementsByTagName("span"); NodeList<Element> as = bodyElement.getElementsByTagName("div"); int scrollTop = Integer.MAX_VALUE; int scrollLeft = Integer.MAX_VALUE; for (int x = 0; x < spans.getLength(); x++) { Element element = spans.getItem(x); if (processElement(annotation, element)) { int[] absTopLeft = Utils.getAbsoluteTopLeft(element, Document.get()); if (absTopLeft[0] < scrollTop) { scrollTop = absTopLeft[0]; } if (absTopLeft[1] < scrollLeft) { scrollLeft = absTopLeft[1]; } } } for (int x = 0; x < as.getLength(); x++) { Element element = as.getItem(x); if (processElement(annotation, element)) { int[] absTopLeft = Utils.getAbsoluteTopLeft(element, Document.get()); if (absTopLeft[0] < scrollTop) { scrollTop = absTopLeft[0]; } if (absTopLeft[1] < scrollLeft) { scrollLeft = absTopLeft[1]; } } } scrollLeft = scrollLeft == Integer.MAX_VALUE ? 0 : scrollLeft; scrollTop = scrollTop == Integer.MAX_VALUE ? 0 : scrollTop; Window.scrollTo(scrollLeft, scrollTop); } } private boolean processElement(Annotation annotation, Element element) { CSSClassManager manager = new CSSClassManager(element); // remove old manager.removeClass(AnnotationConstant.SELECTED_CLASS_NAME); // set new if (manager.isClassPresent(AnnotationConstant.DECORATE_CLASS_NAME + annotation.getId())) { manager.addClass(AnnotationConstant.SELECTED_CLASS_NAME); return true; } return false; } private native int getSelectedAnnotationIndex() /*-{ if (top && typeof top['selectedAnnotationIndex'] != "undefined") { return top['selectedAnnotationIndex']; } else { return -1; } }-*/; public void hideAnnotations() { BodyElement bodyElement = Document.get().getBody(); NodeList<Element> spans = bodyElement.getElementsByTagName("span"); NodeList<Element> divs = bodyElement.getElementsByTagName("div"); for (int x = 0; x < spans.getLength(); x++) { Element element = spans.getItem(x); CSSClassManager manager = new CSSClassManager(element); if (manager.isClassPresent(AnnotationConstant.DECORATE_CLASS_NAME)) { manager.removeClass(AnnotationConstant.DECORATE_CLASS_NAME); manager.addClass(AnnotationConstant.DECORATE_NOT_CLASS_NAME); } } for (int x = 0; x < divs.getLength(); x++) { Element element = divs.getItem(x); CSSClassManager manager = new CSSClassManager(element); if (manager.isClassPresent(AnnotationConstant.DECORATE_CLASS_NAME)) { manager.removeClass(AnnotationConstant.DECORATE_CLASS_NAME); manager.addClass(AnnotationConstant.DECORATE_NOT_CLASS_NAME); } } setAnnotationsShown(false); } private native void setAnnotationsShown(boolean annotationsShown) /*-{ top['annotationsShown'] = annotationsShown; }-*/; public void showAnnotations() { BodyElement bodyElement = Document.get().getBody(); NodeList<Element> spans = bodyElement.getElementsByTagName("span"); NodeList<Element> divs = bodyElement.getElementsByTagName("div"); for (int x = 0; x < spans.getLength(); x++) { Element element = spans.getItem(x); CSSClassManager manager = new CSSClassManager(element); if (manager.isClassPresent(AnnotationConstant.DECORATE_NOT_CLASS_NAME)) { manager.removeClass(AnnotationConstant.DECORATE_NOT_CLASS_NAME); manager.addClass(AnnotationConstant.DECORATE_CLASS_NAME); } if (manager.isClassPresent(AnnotationConstant.SELECTED_NOT_CLASS_NAME)) { manager.removeClass(AnnotationConstant.SELECTED_NOT_CLASS_NAME); manager.addClass(AnnotationConstant.SELECTED_CLASS_NAME); } } for (int x = 0; x < divs.getLength(); x++) { Element element = divs.getItem(x); CSSClassManager manager = new CSSClassManager(element); if (manager.isClassPresent(AnnotationConstant.DECORATE_NOT_CLASS_NAME)) { manager.removeClass(AnnotationConstant.DECORATE_NOT_CLASS_NAME); manager.addClass(AnnotationConstant.DECORATE_CLASS_NAME); } if (manager.isClassPresent(AnnotationConstant.SELECTED_NOT_CLASS_NAME)) { manager.removeClass(AnnotationConstant.SELECTED_NOT_CLASS_NAME); manager.addClass(AnnotationConstant.SELECTED_CLASS_NAME); } } setAnnotationsShown(true); } public native boolean isAnnotationsVisible() /*-{ if (top && typeof top['annotationsShown'] != "undefined") { return top['annotationsShown']; } else { return true; } }-*/; private void removeAllAnnotatedAreas() { String className = isAnnotationsVisible() ? AnnotationConstant.DECORATE_CLASS_NAME : AnnotationConstant.DECORATE_NOT_CLASS_NAME; BodyElement bodyElement = Document.get().getBody(); NodeList<Element> as = bodyElement.getElementsByTagName("div"); removeAnchorAreas(as, className); removeSpanAreas(className); } private void removeAnchorAreas(NodeList<Element> nodes, String className) { List<Element> elements = getElementsToRemove(nodes, className); for (Element element : elements) { element.getParentElement().removeChild(element); } } private List<Element> getElementsToRemove(NodeList<Element> nodes, String className) { List<Element> elementsToRemove = new ArrayList<Element>(); for (int i = 0; i < nodes.getLength(); ++i) { Element element = nodes.getItem(i); CSSClassManager manager = new CSSClassManager(element); if (manager.isClassPresent(className)) { elementsToRemove.add(element); } } return elementsToRemove; } private void removeSpanAreas(String className) { NodeList<Element> spans = Document.get().getBody().getElementsByTagName("span"); List<Element> elements = getElementsToRemove(spans, className); while (!elements.isEmpty()) { Element element = elements.get(0); String elementHtml = element.getInnerHTML(); Element parent = element.getParentElement(); String parentHtml = parent.getInnerHTML(); String escapedClassName = element.getClassName().replaceAll("([/\\\\\\.\\*\\+\\?\\|\\(\\)\\[\\]\\{\\}$^])", "\\\\$1"); String escapedElementHtml = elementHtml.replaceAll("([/\\\\\\.\\*\\+\\?\\|\\(\\)\\[\\]\\{\\}$^])", "\\\\$1"); parentHtml = parentHtml.replaceFirst("<(span|SPAN) class=(\")?" + escapedClassName + "(\")?.*>" + escapedElementHtml + "</(span|SPAN)>", elementHtml); parent.setInnerHTML(parentHtml); spans = Document.get().getBody().getElementsByTagName("span"); elements = getElementsToRemove(spans, className); } } public void decorateSelectedText(Annotation annotation) { DecoratorVisitor processor = DecoratorVisitorFactory.forSelectedText(annotation); Visitor visitor = new Visitor(processor); StringRangeXPointer xpointer = (StringRangeXPointer) annotation.getXpointer(); visitor.process(xpointer.getOwnerDocument()); } public void removeSelectedTextDecoration(Annotation annotation) { String className = AnnotationConstant.SELECTED_TEXT_CLASS_NAME; removeSpanAreas(className); } }