001: /*
002: * MyGWT Widget Library
003: * Copyright(c) 2007, MyGWT.
004: * licensing@mygwt.net
005: *
006: * http://mygwt.net/license
007: */
008: package net.mygwt.ui.client.fx;
009:
010: import net.mygwt.ui.client.Events;
011: import net.mygwt.ui.client.MyDOM;
012: import net.mygwt.ui.client.Style;
013: import net.mygwt.ui.client.event.BaseEvent;
014: import net.mygwt.ui.client.event.DragListener;
015: import net.mygwt.ui.client.event.Listener;
016: import net.mygwt.ui.client.event.TypedListener;
017: import net.mygwt.ui.client.util.Observable;
018: import net.mygwt.ui.client.util.Rectangle;
019: import net.mygwt.ui.client.widget.Component;
020:
021: import com.google.gwt.user.client.Command;
022: import com.google.gwt.user.client.DOM;
023: import com.google.gwt.user.client.DeferredCommand;
024: import com.google.gwt.user.client.Element;
025: import com.google.gwt.user.client.Event;
026: import com.google.gwt.user.client.EventPreview;
027: import com.google.gwt.user.client.Window;
028: import com.google.gwt.user.client.ui.KeyboardListener;
029: import com.google.gwt.user.client.ui.Widget;
030:
031: /**
032: * Adds drag behavior to any widget. Drag operations can be initiated from the
033: * widget itself, or another widget, such as the header in a dialog.
034: *
035: * <p>
036: * It is possible to specify event targets that will be ignored. If the target
037: * element has a 'my-nodrag' style it will not trigger a drag operation.
038: * </p>
039: *
040: * <dl>
041: * <dt><b>Events:</b></dt>
042: *
043: * <dd><b>DragStart</b> : (source, widget, event) <br>
044: * Fires after a drag has started.
045: * <ul>
046: * <li>source : this</li>
047: * <li>widget : drag widget</li>
048: * <li>event : the dom event</li>
049: * </ul>
050: * </dd>
051: *
052: * <dd><b>DragMove</b> : (source, widget, event)<br>
053: * Fires after the mouse moves.
054: * <ul>
055: * <li>source : this</li>
056: * <li>widget : drag widget</li>
057: * <li>event : the dom event</li>
058: * </ul>
059: * </dd>
060: *
061: * <dd><b>DragCancel</b> : (source, widget, event)<br>
062: * Fires after a drag has been cancelled.
063: * <ul>
064: * <li>source : this</li>
065: * <li>widget : drag widget</li>
066: * <li>event : the dom event</li>
067: * </ul>
068: * </dd>
069: *
070: * <dd><b>DragEnd</b> : (source, widget, event) <br>
071: * Fires after a drag has ended.
072: * <ul>
073: * <li>source : this</li>
074: * <li>widget : drag widget</li>
075: * <li>event : the dom event</li>
076: * </ul>
077: * </dd>
078: * </dl>
079: */
080: public class Draggable extends Observable {
081:
082: /**
083: * <code>true</code> to use a proxy widget during drag operation. Default
084: * value is <code>true</code>
085: */
086: public boolean useProxy = true;
087:
088: /**
089: * proxyStyle is the style name used for proxy drags. Default value is
090: * 'my-drag-proxy'.
091: */
092: public String proxyStyle = "my-drag-proxy";
093:
094: /**
095: * <code>true</code> to stop vertical movement. Default value is
096: * <code>false</code>
097: */
098: public boolean constrainVertical = false;
099:
100: /**
101: * <code>true</code> to stop horizontal movement. Default value is
102: * <code>false</code>
103: */
104: public boolean constrainHorizontal = false;
105:
106: /**
107: * <code>true</code> to set client area constraints. Default value is
108: * <code>true</code>
109: */
110: public boolean constrainClient = true;
111:
112: /**
113: * <code>true</code> to move source widget aftet a proxy drag. Default
114: * values is <code>true</code>
115: */
116: public boolean moveAfterProxyDrag = true;
117:
118: /**
119: * If specified, drag will be constrained by container bounds.
120: */
121: public Widget container;
122:
123: /**
124: * <code>true</code> to set proxy dimensions the same as the drag widget.
125: * Default value is <code>true</code>
126: */
127: public boolean sizeProxyToSource = true;
128:
129: /**
130: * updateZIndex specifies if the CSS z-index should be updated on the widget
131: * being dragged. Setting this value to <code>true</code> will ensure that
132: * the dragged element is always displayed over all other widgets. Default
133: * value is <code>true</code>.
134: */
135: public boolean updateZIndex = true;
136:
137: private Component dragWidget;
138: private Component handle;
139: private boolean dragging;
140: private boolean enabled = true;
141: private int dragStartX, dragStartY;
142: private int lastX, lastY;
143: private int clientWidth, clientHeight;
144: private int conX, conY, conWidth, conHeight;
145: private Rectangle startBounds;
146: private Element proxyElem;
147: private EventPreview preview;
148: private BaseEvent dragEvent;
149: private int xLeft = Style.DEFAULT, xRight = Style.DEFAULT;
150: private int xTop = Style.DEFAULT, xBottom = Style.DEFAULT;
151:
152: /**
153: * Creates a new draggable instance.
154: *
155: * @param dragWidget the widget to be dragged
156: */
157: public Draggable(Component dragWidget) {
158: this (dragWidget, dragWidget);
159: }
160:
161: /**
162: * Create a new draggable instance.
163: *
164: * @param dragWidget the widget to be dragged
165: * @param handle the widget drags will be initiated from
166: */
167: public Draggable(final Component dragWidget, Component handle) {
168: this .dragWidget = dragWidget;
169: this .handle = handle;
170:
171: MyDOM.makePositionable(dragWidget.getElement());
172:
173: handle.sinkEvents(Event.MOUSEEVENTS);
174: handle.addListener(Events.MouseDown, new Listener() {
175: public void handleEvent(BaseEvent be) {
176: onMouseDown(be);
177: }
178: });
179:
180: preview = new EventPreview() {
181: public boolean onEventPreview(Event event) {
182: DOM.eventCancelBubble(event, true);
183: DOM.eventPreventDefault(event);
184: switch (DOM.eventGetType(event)) {
185: case Event.ONKEYDOWN:
186: int key = DOM.eventGetKeyCode(event);
187: if (key == KeyboardListener.KEY_ESCAPE && dragging) {
188: cancelDrag(event);
189: }
190: break;
191: case Event.ONMOUSEMOVE:
192: onMouseMove(event);
193: break;
194: case Event.ONMOUSEUP:
195: stopDrag(event);
196: break;
197: }
198: return true;
199: }
200: };
201: }
202:
203: /**
204: * Adds a listener to receive drag events.
205: *
206: * @param listener the drag listener to be added
207: */
208: public void addDragListener(DragListener listener) {
209: TypedListener l = new TypedListener(listener);
210: addListener(Events.DragStart, l);
211: addListener(Events.DragMove, l);
212: addListener(Events.DragCancel, l);
213: addListener(Events.DragEnd, l);
214: }
215:
216: /**
217: * Returns the drag handle.
218: *
219: * @return the drag handle
220: */
221: public Widget getDragHandle() {
222: return handle;
223: }
224:
225: /**
226: * Returns the widget being dragged.
227: *
228: * @return the drag widget
229: */
230: public Widget getDragWidget() {
231: return dragWidget;
232: }
233:
234: /**
235: * Returns <code>true</code> if a drag is in progress.
236: *
237: * @return the drag state
238: */
239: public boolean isDragging() {
240: return dragging;
241: }
242:
243: /**
244: * Removes a previously added listener.
245: *
246: * @param listener the listener to be removed
247: */
248: public void removeDragListener(DragListener listener) {
249: if (eventTable == null)
250: return;
251: eventTable.unhook(Events.DragStart, listener);
252: eventTable.unhook(Events.DragMove, listener);
253: eventTable.unhook(Events.DragCancel, listener);
254: eventTable.unhook(Events.DragEnd, listener);
255: }
256:
257: /**
258: * Enables dragging if the argument is <code>true</code>, and disables it
259: * otherwise.
260: *
261: * @param enabled the new enabled state
262: */
263: public void setEnabled(boolean enabled) {
264: this .enabled = enabled;
265: }
266:
267: /**
268: * Constrains the horizontal travel.
269: *
270: * @param left the number of pixels the element can move to the left
271: * @param right the number of pixels the element can move to the right
272: */
273: public void setXConstraint(int left, int right) {
274: xLeft = left;
275: xRight = right;
276: }
277:
278: /**
279: * Constrains the vertical travel.
280: *
281: * @param top the number of pixels the element can move to the up
282: * @param bottom the number of pixels the element can move to the down
283: */
284: public void setYConstraint(int top, int bottom) {
285: xTop = top;
286: xBottom = bottom;
287: }
288:
289: private void afterDrag() {
290: MyDOM.removeStyleName(MyDOM.getBody(), "my-no-selection");
291: DeferredCommand.addCommand(new Command() {
292: public void execute() {
293: dragWidget.enableEvents(true);
294: }
295: });
296: }
297:
298: private void cancelDrag(Event event) {
299: if (dragging) {
300: DOM.removeEventPreview(preview);
301: dragging = false;
302: if (useProxy) {
303: MyDOM.disableTextSelection(proxyElem, false);
304: Element body = MyDOM.getBody();
305: DOM.removeChild(body, proxyElem);
306: proxyElem = null;
307: }
308: if (!useProxy) {
309: MyDOM.setLocation(dragWidget.getElement(),
310: startBounds.x, startBounds.y);
311: }
312:
313: fireEvent(Events.DragCancel);
314: afterDrag();
315: }
316: }
317:
318: private void onMouseDown(BaseEvent be) {
319: if (!enabled) {
320: return;
321: }
322:
323: Element target = be.getTarget();
324: String s = DOM.getElementProperty(target, "className");
325: if (s != null && s.indexOf("my-nodrag") != -1) {
326: return;
327: }
328: be.cancelBubble();
329: startBounds = MyDOM.getBounds(dragWidget.getElement(), true);
330:
331: dragWidget.enableEvents(false);
332: startDrag(be.event);
333:
334: DOM.addEventPreview(preview);
335:
336: clientWidth = Window.getClientWidth()
337: + MyDOM.getBodyScrollLeft();
338: clientHeight = Window.getClientHeight()
339: + MyDOM.getBodyScrollTop();
340:
341: dragStartX = be.getClientX();
342: dragStartY = be.getClientY();
343:
344: if (container != null) {
345: conX = container.getAbsoluteLeft();
346: conY = container.getAbsoluteTop();
347: conWidth = container.getOffsetWidth();
348: conHeight = container.getOffsetHeight();
349: }
350: }
351:
352: private void onMouseMove(Event event) {
353: if (proxyElem != null) {
354: MyDOM.setVisibility(proxyElem, true);
355: }
356: int x = DOM.eventGetClientX(event);
357: int y = DOM.eventGetClientY(event);
358:
359: if (dragging) {
360: int left = startBounds.x + (x - dragStartX);
361: int top = startBounds.y + (y - dragStartY);
362:
363: int width = dragWidget.getOffsetWidth();
364: int height = dragWidget.getOffsetHeight();
365:
366: if (constrainClient) {
367: left = Math.max(left, 0);
368: top = Math.max(top, 0);
369: left = Math.min(clientWidth - width, left);
370:
371: if (Math.min(clientHeight - height, top) > 0) {
372: top = Math.max(2, Math.min(clientHeight - height,
373: top));
374: }
375: }
376:
377: if (container != null) {
378: left = Math.max(left, conX);
379: left = Math.min(conX + conWidth
380: - dragWidget.getOffsetWidth(), left);
381: top = Math.min(conY + conHeight
382: - dragWidget.getOffsetHeight(), top);
383: top = Math.max(top, conY);
384: }
385:
386: if (xLeft != Style.DEFAULT) {
387: left = Math.max(startBounds.x - xLeft, left);
388: }
389: if (xRight != Style.DEFAULT) {
390: left = Math.min(startBounds.x + xRight, left);
391: }
392:
393: if (xTop != Style.DEFAULT) {
394: top = Math.max(startBounds.y - xTop, top);
395:
396: }
397: if (xBottom != Style.DEFAULT) {
398: top = Math.min(startBounds.y + xBottom, top);
399: }
400:
401: if (constrainHorizontal) {
402: left = startBounds.x;
403: }
404: if (constrainVertical) {
405: top = startBounds.y;
406: }
407:
408: lastX = left;
409: lastY = top;
410:
411: if (useProxy) {
412: MyDOM.setLeftTop(proxyElem, left, top);
413: } else {
414: MyDOM.setLocation(dragWidget.getElement(), left, top);
415: }
416:
417: dragEvent.source = this ;
418: dragEvent.widget = dragWidget;
419: dragEvent.event = event;
420: fireEvent(Events.DragMove, dragEvent);
421: }
422: }
423:
424: private void startDrag(Event event) {
425: MyDOM.addStyleName(MyDOM.getBody(), "my-no-selection");
426:
427: if (updateZIndex) {
428: DOM.setIntStyleAttribute(dragWidget.getElement(), "zIndex",
429: MyDOM.getZIndex());
430: }
431:
432: BaseEvent be = new BaseEvent(dragWidget);
433: be.event = event;
434: fireEvent(Events.DragStart, be);
435:
436: if (dragEvent == null) {
437: dragEvent = new BaseEvent();
438: }
439:
440: dragging = true;
441: if (useProxy) {
442: if (proxyElem == null) {
443: proxyElem = DOM.createDiv();
444: MyDOM.setVisibility(proxyElem, false);
445: MyDOM.setStyleName(proxyElem, proxyStyle);
446: MyDOM.disableTextSelection(proxyElem, true);
447: Element body = MyDOM.getBody();
448: DOM.appendChild(body, proxyElem);
449: DOM.setIntStyleAttribute(proxyElem, "zIndex", MyDOM
450: .getZIndex());
451: DOM
452: .setStyleAttribute(proxyElem, "position",
453: "absolute");
454: }
455: MyDOM.setVisibility(proxyElem, false);
456:
457: if (sizeProxyToSource) {
458: MyDOM.setBounds(proxyElem, startBounds);
459: }
460:
461: // did listeners change size?
462: if (be.height > 0) {
463: MyDOM.setHeight(proxyElem, be.height, true);
464: }
465: if (be.width > 0) {
466: MyDOM.setWidth(proxyElem, be.width, true);
467: }
468: }
469: }
470:
471: private void stopDrag(Event event) {
472: if (dragging) {
473: DOM.removeEventPreview(preview);
474: dragging = false;
475: if (useProxy) {
476: if (moveAfterProxyDrag) {
477: Rectangle rect = MyDOM.getBounds(proxyElem, false);
478: MyDOM.setLocation(dragWidget.getElement(), rect.x,
479: rect.y);
480: }
481: MyDOM.disableTextSelection(proxyElem, false);
482: Element body = MyDOM.getBody();
483: DOM.removeChild(body, proxyElem);
484: proxyElem = null;
485: }
486: BaseEvent be = new BaseEvent(dragWidget);
487: be.event = event;
488: be.x = lastX;
489: be.y = lastY;
490: fireEvent(Events.DragEnd, be);
491: afterDrag();
492: }
493: }
494:
495: }
|