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}