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