You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

MousePlugin.java 9.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. /*
  2. * Copyright 2011, The gwtquery team.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
  5. * in compliance with the License. You may obtain a copy of the License at
  6. *
  7. * http://www.apache.org/licenses/LICENSE-2.0
  8. *
  9. * Unless required by applicable law or agreed to in writing, software distributed under the License
  10. * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
  11. * or implied. See the License for the specific language governing permissions and limitations under
  12. * the License.
  13. */
  14. package com.google.gwt.query.client.plugins;
  15. import com.google.gwt.core.client.Duration;
  16. import com.google.gwt.dom.client.Element;
  17. import com.google.gwt.dom.client.NativeEvent;
  18. import com.google.gwt.query.client.Function;
  19. import com.google.gwt.query.client.GQuery;
  20. import com.google.gwt.query.client.plugins.events.GqEvent;
  21. import com.google.gwt.user.client.Event;
  22. /**
  23. * Base class for all plug-in that need to handle some mouse interactions.
  24. *
  25. */
  26. public abstract class MousePlugin extends UiPlugin {
  27. private GqEvent startEvent;
  28. private boolean started = false;
  29. private Duration mouseUpDuration;
  30. private MouseOptions options;
  31. private boolean preventClickEvent = false;
  32. private boolean touchSupported = false;
  33. private int startX = -1;
  34. private int startY = -1;
  35. protected MousePlugin(GQuery gq) {
  36. super(gq);
  37. }
  38. protected void destroyMouseHandler() {
  39. as(Events)
  40. .unbind(Event.ONMOUSEDOWN | Event.ONCLICK | Event.ONTOUCHSTART, getPluginName(), null);
  41. }
  42. /**
  43. * Return a String identifying the plugin. This string is used as namespace when we bind handlers.
  44. *
  45. */
  46. protected abstract String getPluginName();
  47. /**
  48. * This method initialize all needed handlers.
  49. *
  50. */
  51. protected void initMouseHandler(MouseOptions options) {
  52. this.options = options;
  53. for (final Element e : elements()) {
  54. $(e).as(Events).bind(Event.ONMOUSEDOWN, getPluginName(), (Object) null, new Function() {
  55. @Override
  56. public boolean f(com.google.gwt.user.client.Event event) {
  57. if (touchSupported) {
  58. return true;
  59. }
  60. return mouseDown(e, GqEvent.create(event));
  61. }
  62. }).bind(Event.ONTOUCHSTART, getPluginName(), (Object) null, new Function() {
  63. public boolean f(com.google.gwt.user.client.Event event) {
  64. if (event.getTouches().length() > 1) {
  65. return true;
  66. }
  67. touchSupported = true;
  68. return mouseDown(e, GqEvent.create(event));
  69. }
  70. }).bind(Event.ONCLICK, getPluginName(), (Object) null, new Function() {
  71. @Override
  72. public boolean f(com.google.gwt.user.client.Event event) {
  73. preventClickEvent |= !mouseClick(e, GqEvent.create(event));
  74. if (preventClickEvent) {
  75. preventClickEvent = false;
  76. event.stopPropagation();
  77. event.preventDefault();
  78. return false;
  79. }
  80. return true;
  81. }
  82. });
  83. }
  84. }
  85. /**
  86. * Test if the mouse down event must be handled by the plugin or not.
  87. */
  88. protected boolean mouseCapture(Element draggable, GqEvent event) {
  89. return true;
  90. }
  91. /**
  92. * Method called when mouse click.
  93. */
  94. protected boolean mouseClick(Element element, GqEvent event) {
  95. return true;
  96. }
  97. /**
  98. * Method called when mouse down occur on the element.
  99. *
  100. * You should not override this method. Instead, override {@link #mouseStart(Element, GqEvent)}
  101. * method.
  102. */
  103. protected boolean mouseDown(Element element, GqEvent event) {
  104. // test if an other plugin handle the mouseStart
  105. if (isEventAlreadyHandled(event)) {
  106. return false;
  107. }
  108. if (started) { // case where we missed a mouseup
  109. mouseUp(element, event);
  110. }
  111. // calculate all interesting variables
  112. reset(event);
  113. if (notHandleMouseDown(element, event)) {
  114. return true;
  115. }
  116. if (delayConditionMet() && distanceConditionMet(event)) {
  117. started = mouseStart(element, event);
  118. if (!started) {
  119. event.getOriginalEvent().preventDefault();
  120. return true;
  121. }
  122. }
  123. bindOtherEvents(element);
  124. if (!touchSupported) { // click event are not triggered if we call preventDefault on touchstart event.
  125. event.getOriginalEvent().preventDefault();
  126. }
  127. markEventAsHandled(event);
  128. return true;
  129. }
  130. /**
  131. * Method called when the mouse is dragging.
  132. */
  133. protected abstract boolean mouseDrag(Element element, GqEvent event);
  134. /**
  135. * Method called on MouseMove event.
  136. *
  137. * You should not override this method. Instead, override {@link #mouseMove(Element, GqEvent)}
  138. * method
  139. *
  140. */
  141. protected boolean mouseMove(Element element, GqEvent event) {
  142. if (started) {
  143. event.getOriginalEvent().preventDefault();
  144. return mouseDrag(element, event);
  145. }
  146. if (delayConditionMet() && distanceConditionMet(event)) {
  147. started = mouseStart(element, startEvent);
  148. if (started) {
  149. mouseDrag(element, event);
  150. } else {
  151. mouseUp(element, event);
  152. }
  153. }
  154. return !started;
  155. }
  156. /**
  157. * Method called when the mouse is clicked and all conditions for starting the plugin are met.
  158. *
  159. */
  160. protected abstract boolean mouseStart(Element element, GqEvent event);
  161. /**
  162. * Method called when the mouse button is released.
  163. */
  164. protected abstract boolean mouseStop(Element element, GqEvent event);
  165. /**
  166. * Method called when mouse is released..
  167. *
  168. * You should not override this method. Instead, override {@link #mouseStop(Element, GqEvent)}
  169. * method.
  170. */
  171. protected boolean mouseUp(Element element, GqEvent event) {
  172. unbindOtherEvents();
  173. if (started) {
  174. started = false;
  175. preventClickEvent = event.getCurrentEventTarget() == startEvent.getCurrentEventTarget();
  176. mouseStop(element, event);
  177. }
  178. return true;
  179. }
  180. private void bindOtherEvents(final Element element) {
  181. int moveEvent = touchSupported ? Event.ONTOUCHMOVE : Event.ONMOUSEMOVE;
  182. int endEvents = touchSupported ? Event.ONTOUCHEND : Event.ONMOUSEUP;
  183. $(document).as(Events).bind(moveEvent, getPluginName(), (Object) null, new Function() {
  184. @Override
  185. public boolean f(com.google.gwt.user.client.Event e) {
  186. mouseMove(element, (GqEvent) GqEvent.create(e));
  187. return false;
  188. }
  189. }).bind(endEvents, getPluginName(), (Object) null, new Function() {
  190. @Override
  191. public boolean f(com.google.gwt.user.client.Event e) {
  192. mouseUp(element, (GqEvent) GqEvent.create(e));
  193. return false;
  194. }
  195. });
  196. // TODO Event.ONTOUCHEND | Event.ONTOUCHCANCEL don't work -> investigate
  197. if (touchSupported) {
  198. $(document).as(Events).bind(Event.ONTOUCHCANCEL, getPluginName(), (Object) null,
  199. new Function() {
  200. @Override
  201. public boolean f(com.google.gwt.user.client.Event e) {
  202. mouseUp(element, (GqEvent) GqEvent.create(e));
  203. return false;
  204. }
  205. });
  206. }
  207. }
  208. private boolean delayConditionMet() {
  209. if (mouseUpDuration == null) {
  210. return false;
  211. }
  212. return options.getDelay() <= mouseUpDuration.elapsedMillis();
  213. }
  214. private boolean distanceConditionMet(GqEvent event) {
  215. int neededDistance = options.getDistance();
  216. int xDistance = Math.abs(startX - getClientX(event));
  217. int yDistance = Math.abs(startY - getClientY(event));
  218. // in jQuery-ui we take the greater distance between x and y... not really
  219. // good !
  220. // int mouseDistance = Math.max(xMouseDistance, yMouseDistance);
  221. // use Pythagor theorem !!
  222. int mouseDistance = (int) Math.sqrt(xDistance * xDistance + yDistance * yDistance);
  223. return mouseDistance >= neededDistance;
  224. }
  225. private native boolean isEventAlreadyHandled(GqEvent event) /*-{
  226. var result = event.mouseHandled ? event.mouseHandled : false;
  227. return result;
  228. }-*/;
  229. private native void markEventAsHandled(GqEvent event) /*-{
  230. event.mouseHandled = true;
  231. }-*/;
  232. private boolean notHandleMouseDown(Element element, GqEvent mouseDownEvent) {
  233. boolean isNotBoutonLeft = mouseDownEvent.getButton() != NativeEvent.BUTTON_LEFT;
  234. Element eventTarget = mouseDownEvent.getEventTarget().cast();
  235. boolean isElementCancel = false;
  236. if (options.getCancel() != null) {
  237. isElementCancel =
  238. $(eventTarget).parents().add($(eventTarget)).filter(options.getCancel()).length() > 0;
  239. }
  240. return isNotBoutonLeft || isElementCancel || !mouseCapture(element, mouseDownEvent);
  241. }
  242. private void reset(GqEvent nativeEvent) {
  243. this.startEvent = nativeEvent;
  244. this.startX = getClientX(nativeEvent);
  245. this.startY = getClientY(nativeEvent);
  246. this.mouseUpDuration = new Duration();
  247. }
  248. private void unbindOtherEvents() {
  249. int events =
  250. touchSupported ? Event.ONTOUCHCANCEL | Event.ONTOUCHEND | Event.ONTOUCHMOVE
  251. : Event.ONMOUSEUP | Event.ONMOUSEMOVE;
  252. $(document).as(Events).unbind(events, getPluginName(), null);
  253. }
  254. protected int getClientX(Event e) {
  255. if (touchSupported) {
  256. return e.getTouches().get(0).getClientX();
  257. } else {
  258. return e.getClientX();
  259. }
  260. }
  261. protected int getClientY(Event e) {
  262. if (touchSupported) {
  263. return e.getTouches().get(0).getClientY();
  264. } else {
  265. return e.getClientY();
  266. }
  267. }
  268. }