1 /* 2 * This file is part of ELKI: 3 * Environment for Developing KDD-Applications Supported by Index-Structures 4 * 5 * Copyright (C) 2018 6 * ELKI Development Team 7 * 8 * This program is free software: you can redistribute it and/or modify 9 * it under the terms of the GNU Affero General Public License as published by 10 * the Free Software Foundation, either version 3 of the License, or 11 * (at your option) any later version. 12 * 13 * This program is distributed in the hope that it will be useful, 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 * GNU Affero General Public License for more details. 17 * 18 * You should have received a copy of the GNU Affero General Public License 19 * along with this program. If not, see <http://www.gnu.org/licenses/>. 20 */ 21 package de.lmu.ifi.dbs.elki.visualization.batikutil; 22 23 import org.apache.batik.util.SVGConstants; 24 import org.w3c.dom.Element; 25 import org.w3c.dom.events.Event; 26 import org.w3c.dom.events.EventListener; 27 import org.w3c.dom.events.EventTarget; 28 import org.w3c.dom.svg.SVGPoint; 29 30 import de.lmu.ifi.dbs.elki.logging.LoggingUtil; 31 import de.lmu.ifi.dbs.elki.visualization.css.CSSClass; 32 import de.lmu.ifi.dbs.elki.visualization.svg.SVGPlot; 33 import de.lmu.ifi.dbs.elki.visualization.svg.SVGUtil; 34 35 /** 36 * A simple dragable area for Batik. 37 * 38 * @author Erich Schubert 39 * @since 0.4.0 40 * 41 * @has - - - DragListener 42 * @has - - - Element 43 */ 44 public class DragableArea implements EventListener { 45 /** 46 * Our element node. 47 */ 48 final protected Element element; 49 50 /** 51 * The coordinate system node. 52 */ 53 final protected Element coordref; 54 55 /** 56 * The plot we are attached to. 57 */ 58 final protected SVGPlot svgp; 59 60 /** 61 * The point where the drag started. 62 */ 63 protected SVGPoint startDragPoint = null; 64 65 /** 66 * A listener to notify on drags (when not subclassing). 67 */ 68 protected DragListener listener = null; 69 70 /** 71 * Constructor for a dragable area. use getElement() to get the DOM node. 72 * 73 * Note: always remember to call 'destroy()' to remove listeners! 74 * 75 * @param plot Plot we'll be added to 76 * @param x X position 77 * @param y Y position 78 * @param w Width 79 * @param h Height 80 */ DragableArea(SVGPlot plot, double x, double y, double w, double h)81 public DragableArea(SVGPlot plot, double x, double y, double w, double h) { 82 this.svgp = plot; 83 this.element = plot.svgRect(x, y, w, h); 84 makeInvisible(); 85 this.coordref = this.element; 86 enableStart(); 87 } 88 89 /** 90 * Constructor for a dragable area. use getElement() to get the DOM node. 91 * 92 * Note: always remember to call 'destroy()' to remove listeners! 93 * 94 * @param plot Plot we'll be added to 95 * @param coordref Element defining the coordinate system 96 * @param x X position 97 * @param y Y position 98 * @param w Width 99 * @param h Height 100 */ DragableArea(SVGPlot plot, Element coordref, double x, double y, double w, double h)101 public DragableArea(SVGPlot plot, Element coordref, double x, double y, double w, double h) { 102 this.svgp = plot; 103 this.element = plot.svgRect(x, y, w, h); 104 makeInvisible(); 105 this.coordref = coordref; 106 enableStart(); 107 } 108 109 /** 110 * Constructor for a dragable area. use getElement() to get the DOM node. 111 * 112 * Note: always remember to call 'destroy()' to remove listeners! 113 * 114 * @param plot Plot we'll be added to 115 * @param x X position 116 * @param y Y position 117 * @param w Width 118 * @param h Height 119 * @param listener Drag listener 120 */ DragableArea(SVGPlot plot, double x, double y, double w, double h, DragListener listener)121 public DragableArea(SVGPlot plot, double x, double y, double w, double h, DragListener listener) { 122 this.svgp = plot; 123 this.element = plot.svgRect(x, y, w, h); 124 makeInvisible(); 125 this.coordref = this.element; 126 this.listener = listener; 127 enableStart(); 128 } 129 130 /** 131 * Constructor for a dragable area. use getElement() to get the DOM node. 132 * 133 * Note: always remember to call 'destroy()' to remove listeners! 134 * 135 * @param plot Plot we'll be added to 136 * @param coordref Element defining the coordinate system 137 * @param x X position 138 * @param y Y position 139 * @param w Width 140 * @param h Height 141 * @param listener Drag listener 142 */ DragableArea(SVGPlot plot, Element coordref, double x, double y, double w, double h, DragListener listener)143 public DragableArea(SVGPlot plot, Element coordref, double x, double y, double w, double h, DragListener listener) { 144 this.svgp = plot; 145 this.element = plot.svgRect(x, y, w, h); 146 makeInvisible(); 147 this.coordref = coordref; 148 this.listener = listener; 149 enableStart(); 150 } 151 152 /** 153 * Remove the listeners 154 */ destroy()155 public void destroy() { 156 disableStart(); 157 disableStop(); 158 } 159 160 /** 161 * The DOM element. 162 * 163 * @return the element 164 */ getElement()165 public Element getElement() { 166 return element; 167 } 168 169 /** 170 * Enable capturing of 'mousedown' events. 171 */ enableStart()172 public void enableStart() { 173 EventTarget targ = (EventTarget) element; 174 targ.addEventListener(SVGConstants.SVG_EVENT_MOUSEDOWN, this, false); 175 } 176 177 /** 178 * Disable capturing of 'mousedown' events. 179 */ disableStart()180 public void disableStart() { 181 EventTarget targ = (EventTarget) element; 182 targ.removeEventListener(SVGConstants.SVG_EVENT_MOUSEDOWN, this, false); 183 } 184 185 /** 186 * Enable capturing of 'mousemove' and 'mouseup' events. 187 */ enableStop()188 protected void enableStop() { 189 EventTarget targ = svgp.getDocument().getRootElement(); 190 targ.addEventListener(SVGConstants.SVG_EVENT_MOUSEMOVE, this, false); 191 targ.addEventListener(SVGConstants.SVG_EVENT_MOUSEUP, this, false); 192 // FIXME: listen on the background object! 193 targ.addEventListener(SVGConstants.SVG_EVENT_MOUSEOUT, this, false); 194 } 195 196 /** 197 * Disable capturing of 'mousemove' and 'mouseup' events. 198 */ disableStop()199 protected void disableStop() { 200 EventTarget targ = svgp.getDocument().getRootElement(); 201 targ.removeEventListener(SVGConstants.SVG_EVENT_MOUSEMOVE, this, false); 202 targ.removeEventListener(SVGConstants.SVG_EVENT_MOUSEUP, this, false); 203 // FIXME: listen on the background object! 204 targ.removeEventListener(SVGConstants.SVG_EVENT_MOUSEOUT, this, false); 205 } 206 207 @Override handleEvent(Event evt)208 public void handleEvent(Event evt) { 209 if (evt.getType().equals(SVGConstants.SVG_EVENT_MOUSEDOWN)) { 210 SVGPoint dragPoint = getCoordinates(evt); 211 if (startDrag(dragPoint, evt)) { 212 // LoggingUtil.warning("Starting drag: "+dragPoint); 213 startDragPoint = dragPoint; 214 enableStop(); 215 } 216 } else if (evt.getType().equals(SVGConstants.SVG_EVENT_MOUSEMOVE)) { 217 if (startDragPoint != null) { 218 SVGPoint dragPoint = getCoordinates(evt); 219 if (!duringDrag(startDragPoint, dragPoint, evt, evt.getTarget() == element)) { 220 // cancel the drag operation 221 startDragPoint = null; 222 disableStop(); 223 } 224 } 225 } else if (evt.getType().equals(SVGConstants.SVG_EVENT_MOUSEUP)) { 226 if (startDragPoint != null) { 227 SVGPoint dragPoint = getCoordinates(evt); 228 if (endDrag(startDragPoint, dragPoint, evt, evt.getTarget() == element)) { 229 // LoggingUtil.warning("Drag completed: "+dragPoint); 230 startDragPoint = null; 231 disableStop(); 232 } 233 } 234 } else if (evt.getType().equals(SVGConstants.SVG_EVENT_MOUSEOUT)) { 235 // When leaving the document with the mouse! 236 if (startDragPoint != null && evt.getTarget() == evt.getCurrentTarget()) { 237 // LoggingUtil.warning("Mouseout: "+evt.getTarget().toString()); 238 SVGPoint dragPoint = getCoordinates(evt); 239 if (endDrag(startDragPoint, dragPoint, evt, false)) { 240 // LoggingUtil.warning("Drag completed: "+dragPoint); 241 startDragPoint = null; 242 disableStop(); 243 } 244 } 245 } else { 246 LoggingUtil.warning("Unrecognized event: " + evt); 247 } 248 } 249 250 /** 251 * Return the event coordinates for this event. 252 * 253 * @param evt Event 254 * @return Coordinates 255 */ getCoordinates(Event evt)256 protected SVGPoint getCoordinates(Event evt) { 257 return SVGUtil.elementCoordinatesFromEvent(this.svgp.getDocument(), this.coordref, evt); 258 } 259 260 /** 261 * Action to do on drag start. 262 * 263 * @param startPoint Point where the drag was started. 264 * @param evt The event object 265 * @return {@code true} to start the drag operation 266 */ startDrag(SVGPoint startPoint, Event evt)267 protected boolean startDrag(SVGPoint startPoint, Event evt) { 268 if (listener != null) { 269 return listener.startDrag(startPoint, evt); 270 } 271 return true; 272 } 273 274 /** 275 * Method called during drags. 276 * 277 * @param startPoint Drag starting point 278 * @param dragPoint Drag end point 279 * @param evt The event object 280 * @param inside Inside the tracked element 281 * @return {@code true} to continue the drag 282 */ duringDrag(SVGPoint startPoint, SVGPoint dragPoint, Event evt, boolean inside)283 protected boolean duringDrag(SVGPoint startPoint, SVGPoint dragPoint, Event evt, boolean inside) { 284 if (listener != null) { 285 return listener.duringDrag(startPoint, dragPoint, evt, inside); 286 } 287 return true; 288 } 289 290 /** 291 * Method called when a drag was ended. 292 * 293 * @param startPoint Drag starting point 294 * @param dragPoint Drag end point 295 * @param evt The event object 296 * @param inside Success flag 297 * @return {@code true} to complete the drag 298 */ endDrag(SVGPoint startPoint, SVGPoint dragPoint, Event evt, boolean inside)299 protected boolean endDrag(SVGPoint startPoint, SVGPoint dragPoint, Event evt, boolean inside) { 300 if (listener != null) { 301 return listener.endDrag(startPoint, dragPoint, evt, inside); 302 } 303 return true; 304 } 305 306 /** 307 * Make the rectangle invisible. 308 */ makeInvisible()309 public void makeInvisible() { 310 CSSClass cls = new CSSClass(this, "unused"); 311 cls.setStatement(SVGConstants.CSS_FILL_OPACITY_PROPERTY, "0"); 312 cls.setStatement(SVGConstants.CSS_CURSOR_PROPERTY, SVGConstants.CSS_POINTER_VALUE); 313 SVGUtil.setAtt(element, SVGConstants.SVG_STYLE_ATTRIBUTE, cls.inlineCSS()); 314 } 315 316 /** 317 * Make the rectangle visible, for debug purposes. 318 */ makeVisible()319 public void makeVisible() { 320 CSSClass cls = new CSSClass(this, "unused"); 321 cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_GREEN_VALUE); 322 cls.setStatement(SVGConstants.CSS_FILL_OPACITY_PROPERTY, "0.2"); 323 cls.setStatement(SVGConstants.CSS_CURSOR_PROPERTY, SVGConstants.CSS_POINTER_VALUE); 324 SVGUtil.setAtt(element, SVGConstants.SVG_STYLE_ATTRIBUTE, cls.inlineCSS()); 325 } 326 327 /** 328 * Listener interface for drag events. 329 * 330 * @author Erich Schubert 331 * 332 */ 333 public interface DragListener { 334 /** 335 * Action to do on drag start. 336 * 337 * @param startPoint Point where the drag was started. 338 * @param evt The event object 339 * @return {@code true} to start the drag operation 340 */ startDrag(SVGPoint startPoint, Event evt)341 boolean startDrag(SVGPoint startPoint, Event evt); 342 343 /** 344 * Method called during drags. 345 * 346 * @param startPoint Drag starting point 347 * @param dragPoint Drag end point 348 * @param evt The event object 349 * @param inside Inside the tracked element 350 * @return {@code true} to continue the drag 351 */ duringDrag(SVGPoint startPoint, SVGPoint dragPoint, Event evt, boolean inside)352 boolean duringDrag(SVGPoint startPoint, SVGPoint dragPoint, Event evt, boolean inside); 353 354 /** 355 * Method called when a drag was ended. 356 * 357 * @param startPoint Drag starting point 358 * @param dragPoint Drag end point 359 * @param evt The event object 360 * @param inside Whether the end point was inside the area 361 * @return {@code true} to complete the drag 362 */ endDrag(SVGPoint startPoint, SVGPoint dragPoint, Event evt, boolean inside)363 boolean endDrag(SVGPoint startPoint, SVGPoint dragPoint, Event evt, boolean inside); 364 } 365 } 366