001/*
002 * (C) Copyright 2006-2008 Nuxeo SAS (http://nuxeo.com/) and contributors.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser General Public License
006 * (LGPL) version 2.1 which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/lgpl.html
008 *
009 * This library is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * Contributors:
015 *     Alexandre Russel
016 *
017 * $Id$
018 */
019
020package org.nuxeo.ecm.platform.annotations.gwt.client.view.annotater;
021
022import com.google.gwt.user.client.Window;
023import org.nuxeo.ecm.platform.annotations.gwt.client.controler.AnnotationController;
024import org.nuxeo.ecm.platform.annotations.gwt.client.model.AnnotationChangeListener;
025import org.nuxeo.ecm.platform.annotations.gwt.client.model.AnnotationModel;
026import org.nuxeo.ecm.platform.annotations.gwt.client.util.Utils;
027import org.nuxeo.ecm.platform.annotations.gwt.client.util.XPathUtil;
028import org.nuxeo.ecm.platform.annotations.gwt.client.view.NewAnnotationPopup;
029import org.nuxeo.ecm.platform.annotations.gwt.client.view.decorator.ImageDecorator;
030
031import com.allen_sauer.gwt.log.client.Log;
032import com.google.gwt.dom.client.DivElement;
033import com.google.gwt.dom.client.Document;
034import com.google.gwt.dom.client.Element;
035import com.google.gwt.dom.client.ImageElement;
036import com.google.gwt.dom.client.Node;
037import com.google.gwt.user.client.DOM;
038import com.google.gwt.user.client.Event;
039
040/**
041 * @author Alexandre Russel
042 */
043public class ImageAnnotater extends AbstractAnnotater implements AnnotationChangeListener {
044    private boolean writing = false;
045
046    private boolean processing = false;
047
048    private final ImageDecorator decorator;
049
050    private DivElement divElement;
051
052    private int ax = -1;
053
054    private int ay = -1;
055
056    private int bx = -1;
057
058    private int by = -1;
059
060    private ImageElement image;
061
062    public ImageAnnotater(AnnotationController controller) {
063        super(controller, true);
064        decorator = new ImageDecorator(controller);
065        controller.addModelChangeListener(this);
066    }
067
068    public void refresh() {
069        ax = -1;
070
071        ay = -1;
072
073        bx = -1;
074
075        by = -1;
076    }
077
078    @Override
079    public void onMouseDown(Event event) {
080        super.onMouseDown(event);
081        if (writing /* || processing */) {
082            Log.debug("ImageAnnotater] Ignore mouse down event");
083            return;
084        }
085        if (processing) {
086            divElement.getParentElement().removeChild(divElement);
087        }
088        image = getRootImage(event);
089        int[] absoluteTopLeft = Utils.getAbsoluteTopLeft(image, Document.get());
090        ax = event.getClientX() - absoluteTopLeft[1] + Window.getScrollLeft();
091        ay = event.getClientY() - absoluteTopLeft[0] + Window.getScrollTop();
092        bx = ax;
093        by = ay;
094        writing = true;
095        processing = true;
096        controller.disablePopupListeners();
097        addMap(ax, ay, bx, by, image);
098    }
099
100    private ImageElement getRootImage(Event event) {
101        com.google.gwt.dom.client.Element targetElement = event.getTarget();
102        ImageElement imageElement = ImageElement.as(targetElement.getOwnerDocument().getElementById(
103                "annotationRootImage"));
104        if (imageElement == null) {
105            if (targetElement.getNodeName().equalsIgnoreCase("img")) {
106                imageElement = ImageElement.as(targetElement);
107            } else if (targetElement.getNodeName().equalsIgnoreCase("div")) {
108                imageElement = getImageElementFromAnchor(targetElement);
109            }
110        }
111        return imageElement;
112    }
113
114    private static ImageElement getImageElementFromAnchor(com.google.gwt.dom.client.Element anchorElement) {
115        Node element;
116        while ((element = anchorElement.getPreviousSibling()) != null) {
117            Log.debug("getImageElementFromAnchor -- nodeName: " + element.getNodeName());
118            if (element.getNodeName().equalsIgnoreCase("img")) {
119                return ImageElement.as((Element) element.cast());
120            }
121        }
122        return null;
123    }
124
125    @Override
126    public void onMouseMove(Event event) {
127        super.onMouseMove(event);
128
129        if (!writing) {
130            return;
131        }
132        String nodeName = event.getTarget().getNodeName();
133        if (nodeName.equalsIgnoreCase("img")) {
134            ImageElement newImage = ImageElement.as(event.getTarget());
135            if ((!image.equals(newImage) || ax == -1 || ay == -1) && !controller.isMultiImage()) {
136                refresh();
137            }
138        }
139        int[] absoluteTopLeft = Utils.getAbsoluteTopLeft(image, Document.get());
140        bx = event.getClientX() - absoluteTopLeft[1] + Window.getScrollLeft();
141        by = event.getClientY() - absoluteTopLeft[0] + Window.getScrollTop();
142        updateMap(ax, ay, bx, by, image);
143    }
144
145    @Override
146    public void onMouseUp(Event event) {
147        if (!hasMoved() && writing) {
148            Log.debug("cancel mouse up image");
149            cancelMap();
150            controller.setNewAnnotationPopup(null);
151            if (controller.isAnnotationsVisible()) {
152                controller.enablePopupListeners();
153            }
154            super.onMouseUp(event);
155            return;
156        }
157
158        super.onMouseUp(event);
159
160        if (!writing) {
161            return;
162        }
163        String nodeName = event.getTarget().getNodeName();
164        if (nodeName.equalsIgnoreCase("img")) {
165            ImageElement newImage = ImageElement.as(event.getTarget());
166            if ((!image.equals(newImage) || ax == -1 || ay == -1) && !controller.isMultiImage()) {
167                refresh();
168            }
169        }
170
171        int[] absoluteTopLeft = Utils.getAbsoluteTopLeft(image, Document.get());
172        bx = event.getClientX() - absoluteTopLeft[1] + Window.getScrollLeft();
173        by = event.getClientY() - absoluteTopLeft[0] + Window.getScrollTop();
174        addMapAndGetAnnot(new int[] { ax, ay, bx, by }, image);
175        if (controller.isAnnotationsVisible()) {
176            controller.enablePopupListeners();
177        }
178        writing = false;
179        addAnnotationPopup();
180        controller.enablePopupListeners();
181    }
182
183    private void cancelMap() {
184        writing = false;
185        processing = false;
186        if (divElement != null) {
187            DOM.setEventListener((com.google.gwt.user.client.Element) divElement.cast(), null);
188            Log.debug("Parent element: " + divElement.getParentElement());
189            if (divElement.getParentElement() != null) {
190                divElement.getParentElement().removeChild(divElement);
191            }
192        }
193    }
194
195    public void updateMap(int ax2, int ay2, int bx2, int by2, ImageElement img) {
196        decorator.updateAnnotatedArea(ax2, ay2, bx2, by2, img, divElement);
197    }
198
199    private void addMap(int ax2, int ay2, int ax3, int ay3, ImageElement img) {
200        divElement = decorator.addAnnotatedArea(ax, ay, bx, by, this);
201    }
202
203    public void addMapAndGetAnnot(int[] points, ImageElement img) {
204        DOM.setEventListener((com.google.gwt.user.client.Element) divElement.cast(), null);
205        String xpath = img.getParentElement().getId();
206        xpath = XPathUtil.fromIdableName(xpath);
207        checkInt(points);
208        String xpointer = controller.filterXPointer(image, xpath, points[0], points[1], points[2], points[3]);
209        Log.debug("XPointer: " + xpointer);
210        controller.createNewAnnotation(xpointer);
211        NewAnnotationPopup popup = new NewAnnotationPopup(divElement, controller, true, "local");
212        controller.setNewAnnotationPopup(popup);
213    }
214
215    private static void checkInt(int[] points) {
216        // following code is because, on some IE machine we got float instead of
217        // integer:
218        for (int x = 0; x < points.length; x++) {
219            points[x] = ("" + points[x]).contains(".") ? Integer.parseInt(("" + points[x]).substring(0,
220                    ("" + points[x]).indexOf("."))) : points[x];
221        }
222    }
223
224    public ImageElement getImage() {
225        return image;
226    }
227
228    public void setImage(ImageElement image) {
229        this.image = image;
230    }
231
232    public void updateMap(int bx2, int by2, ImageElement image2) {
233        decorator.updateAnnotatedArea(ax, ay, bx2, by2, image2, divElement);
234    }
235
236    public void onChange(AnnotationModel model, ChangeEvent ce) {
237        if (model.getNewAnnotation() == null && ce == ChangeEvent.annotation) {
238            processing = false;
239        }
240    }
241
242}