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