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.widget;
009:
010: import java.util.ArrayList;
011: import java.util.List;
012:
013: import net.mygwt.ui.client.Events;
014: import net.mygwt.ui.client.MyDOM;
015: import net.mygwt.ui.client.Style;
016: import net.mygwt.ui.client.event.BaseEvent;
017: import net.mygwt.ui.client.event.Listener;
018: import net.mygwt.ui.client.fx.Draggable;
019: import net.mygwt.ui.client.util.DelayedTask;
020: import net.mygwt.ui.client.util.Rectangle;
021:
022: import com.google.gwt.user.client.DOM;
023: import com.google.gwt.user.client.Element;
024: import com.google.gwt.user.client.Event;
025: import com.google.gwt.user.client.ui.HTML;
026: import com.google.gwt.user.client.ui.RootPanel;
027: import com.google.gwt.user.client.ui.Widget;
028: import com.google.gwt.user.client.ui.WidgetHelper;
029:
030: /**
031: * Creates draggable splitter bar on the side of a widget.
032: *
033: * <dl>
034: * <dt><b>Styles:</b></dt>
035: * <dd>NORTH, WEST, SOUTH, EAST</dd>
036: *
037: * <dl>
038: * <dt><b>Events:</b></dt>
039: *
040: * <dd><b>Resize</b> : (widget, item, size)<br>
041: * <div>Fires after the split bar has been moved.</div>
042: * <ul>
043: * <li>widget : this</li>
044: * <li>item : the widget being resized</li>
045: * <li>size : the new size</li>
046: * </ul>
047: * </dd>
048: *
049: * <dd><b>DragStart</b> : (widget,event) <div>Fires after a drag has started.</div>
050: * <ul>
051: * <li>widget : this</li>
052: * <li>event : the dom event</li>
053: * </ul>
054: * </dd>
055: *
056: * <dd><b>DragEnd</b> : (widget,event) <div>Fires after a drag has ended.</div>
057: * <ul>
058: * <li>widget : this</li>
059: * <li>event : the dom event</li>
060: * </ul>
061: * </dd>
062: *
063: * <dl>
064: */
065: public class SplitBar extends Component {
066:
067: /**
068: * Specifies if a frame panel should be displayed while dragging split bars.
069: * Frame panel uses a hidden iframe to allow the split bar to travel over
070: * embedded objects such as applets and flash. There is a slight performance
071: * hit when enabled. Default value is <code>false</code>.
072: */
073: public static boolean useFramePanel;
074:
075: /**
076: * Transparent shim that allows drags over iframes.
077: */
078: private static HTML shim;
079: private static FramePanel framePanel;
080:
081: private static List attachedBars;
082: private static DelayedTask delayedTask;
083:
084: static {
085: shim = new HTML();
086: shim.setStyleName("my-splitbar-shim");
087: shim.setSize("2000px", "2000px");
088: RootPanel.get().add(shim);
089: shim.setVisible(false);
090: attachedBars = new ArrayList();
091:
092: delayedTask = new DelayedTask(new Listener() {
093: public void handleEvent(BaseEvent be) {
094: int count = attachedBars.size();
095: for (int i = 0; i < count; i++) {
096: SplitBar bar = (SplitBar) attachedBars.get(i);
097: bar.updateHandle();
098: }
099: }
100: });
101: }
102:
103: static void updateHandles() {
104: delayedTask.delay(400);
105: }
106:
107: private int minSize = 10;
108: private int maxSize = 2000;
109: private int barWidth = 4;
110: private boolean autoSize = true;
111: private int yOffset = 0;
112: private int xOffset = 0;
113: private Element resizeElem;
114: private Component resizeWidget;
115: private Component containerWidget;
116: private Draggable draggable;
117: private Rectangle startBounds;
118: private Listener listener;
119: private DelayedTask delay;
120:
121: /**
122: * Creates a new split bar.
123: *
124: * @param style the bar location
125: * @param resizeWidget the widget being resized
126: */
127: public SplitBar(final int style, final Component resizeWidget) {
128: this .style = style;
129: this .resizeWidget = resizeWidget;
130: this .resizeElem = resizeWidget.getElement();
131:
132: final Widget fSplitBar = this ;
133:
134: listener = new Listener() {
135: public void handleEvent(BaseEvent be) {
136: switch (be.type) {
137: case Events.Attach:
138: MyDOM.insertBefore(getElement(), resizeElem);
139: WidgetHelper.doAttach(fSplitBar);
140: updateHandle();
141: attachedBars.add(fSplitBar);
142: break;
143: case Events.Detach:
144: WidgetHelper.doDetach(fSplitBar);
145: MyDOM.removeFromParent(getElement());
146: attachedBars.remove(fSplitBar);
147: break;
148: case Events.Resize:
149: delay.delay(400);
150: break;
151: }
152: }
153: };
154:
155: resizeWidget.addListener(Events.Attach, listener);
156: resizeWidget.addListener(Events.Detach, listener);
157: resizeWidget.addListener(Events.Resize, listener);
158:
159: setElement(DOM.createDiv());
160:
161: if (style == Style.SOUTH || style == Style.NORTH) {
162: setStyleName("my-hsplitbar");
163: } else {
164: setStyleName("my-vsplitbar");
165: }
166: MyDOM.setStyleAttribute(getElement(), "position", "absolute");
167:
168: draggable = new Draggable(this );
169: draggable.updateZIndex = false;
170: draggable.proxyStyle = "my-splitbar-proxy";
171:
172: Listener dragListener = new Listener() {
173: public void handleEvent(BaseEvent be) {
174: if (be.type == Events.DragStart) {
175: onStartDrag(be);
176: }
177: if (be.type == Events.DragEnd) {
178: onEndDrag(be);
179: }
180:
181: if (be.type == Events.DragCancel) {
182: onCancelDrag(be);
183: }
184: }
185:
186: };
187: draggable.addListener(Events.DragStart, dragListener);
188: draggable.addListener(Events.DragEnd, dragListener);
189: draggable.addListener(Events.DragCancel, dragListener);
190:
191: sinkEvents(Event.MOUSEEVENTS);
192:
193: if (resizeWidget.isAttached()) {
194: BaseEvent be = new BaseEvent();
195: be.type = Events.Attach;
196: listener.handleEvent(be);
197: }
198:
199: delay = new DelayedTask(new Listener() {
200: public void handleEvent(BaseEvent be) {
201: updateHandle();
202: }
203: });
204: }
205:
206: /**
207: * Creates a new split bar.
208: *
209: * @param style the bar location
210: * @param resizeWidget the widget being resized
211: * @param container the widget the split bar proxy will be sized to
212: */
213: public SplitBar(int style, Component resizeWidget,
214: Component container) {
215: this (style, resizeWidget);
216: this .containerWidget = container;
217: draggable.container = container;
218: }
219:
220: /**
221: * Returns <code>true</code> if auto size is enabled.
222: *
223: * @return the auto size state
224: */
225: public boolean getAutoSize() {
226: return autoSize;
227: }
228:
229: /**
230: * Returns the bar's width.
231: *
232: * @return the bar width
233: */
234: public int getBarWidth() {
235: return barWidth;
236: }
237:
238: /**
239: * Returns the split bar's draggable instance.
240: *
241: * @return the draggable instance
242: */
243: public Draggable getDraggable() {
244: return draggable;
245: }
246:
247: /**
248: * Returns the max size.
249: *
250: * @return the max size
251: */
252: public int getMaxSize() {
253: return maxSize;
254: }
255:
256: /**
257: * Returns the minimum size.
258: *
259: * @return the minium size
260: */
261: public int getMinSize() {
262: return minSize;
263: }
264:
265: /**
266: * Returns the resize widget.
267: *
268: * @return the resize widget
269: */
270: public Widget getResizeWidget() {
271: return resizeWidget;
272: }
273:
274: /**
275: * Returns the split bar's style information.
276: *
277: * @return the style information
278: */
279: public int getStyle() {
280: return style;
281: }
282:
283: /**
284: * Returns the x offset.
285: *
286: * @return the x offset
287: */
288: public int getXOffset() {
289: return yOffset;
290: }
291:
292: /**
293: * Returns the y offset.
294: *
295: * @return the y offset
296: */
297: public int getYOffset() {
298: return xOffset;
299: }
300:
301: /**
302: * Removes the split bar from the resize widget.
303: */
304: public void release() {
305: resizeWidget.removeListener(Events.Attach, listener);
306: resizeWidget.removeListener(Events.Detach, listener);
307: resizeWidget.removeListener(Events.Resize, listener);
308: RootPanel.get().remove(this );
309: }
310:
311: /**
312: * Specifies if the size of the the resize widget should be updated
313: * automatically after a drag operation using a proxy. Default value is
314: * <code>true</code>.
315: *
316: * @param autoSize <code>true</code> to enable auto sizing
317: */
318: public void setAutoSize(boolean autoSize) {
319: this .autoSize = autoSize;
320: }
321:
322: /**
323: * Sets width of bar in pixels. Default value is 4.
324: *
325: * @param barWidth the bar width
326: */
327: public void setBarWidth(int barWidth) {
328: this .barWidth = barWidth;
329: }
330:
331: /**
332: * Sets the maximum size of the resize widget. Default value is 2000.
333: *
334: * @param maxSize the max size
335: */
336: public void setMaxSize(int maxSize) {
337: this .maxSize = maxSize;
338: }
339:
340: /**
341: * Sets the minimum size of the resize widget. Default value is 10.
342: *
343: * @param minSize the minimum size
344: */
345: public void setMinSize(int minSize) {
346: this .minSize = minSize;
347: }
348:
349: /**
350: * Specifies the amount of pixels the bar should be offset to the left.
351: * Default value is 0.
352: *
353: * @param xOffset the x offset
354: */
355: public void setXOffset(int xOffset) {
356: this .yOffset = xOffset;
357: }
358:
359: /**
360: * Sets the amount of pixels the bar should be offset to the top. Default
361: * value is 0.
362: *
363: * @param yOffset the y offset
364: */
365: public void setYOffset(int yOffset) {
366: this .xOffset = yOffset;
367: }
368:
369: public void updateHandle() {
370: if (!isAttached() || !resizeWidget.isAttached()) {
371: return;
372: }
373: Rectangle rect = MyDOM.getBounds(resizeElem, false);
374: int x = rect.x;
375: int y = rect.y;
376:
377: if (!MyDOM.isVisibleBox()) {
378: y -= MyDOM.getDecorationWidth(resizeElem, Style.TOP);
379: x -= MyDOM.getDecorationWidth(resizeElem, Style.LEFT);
380: }
381:
382: int w = rect.width;
383: int h = rect.height;
384:
385: switch (style) {
386: case Style.SOUTH:
387: MyDOM.setBounds(getElement(), x + yOffset, y + h + xOffset,
388: w, barWidth, false);
389: break;
390: case Style.WEST:
391: MyDOM.setBounds(getElement(), x - barWidth + yOffset, y
392: + xOffset, barWidth, h, false);
393: break;
394: case Style.NORTH:
395: MyDOM.setBounds(getElement(), x + yOffset, y - barWidth
396: + xOffset, w, barWidth, false);
397: break;
398: case Style.EAST:
399: MyDOM.setBounds(getElement(), x + w + yOffset, y + xOffset,
400: barWidth, h, false);
401: break;
402: }
403:
404: }
405:
406: private void onCancelDrag(BaseEvent be) {
407: shim.setVisible(false);
408: resizeWidget.enableEvents(true);
409: updateHandle();
410: }
411:
412: private void onEndDrag(BaseEvent bee) {
413: shim.setVisible(false);
414: if (useFramePanel) {
415: framePanel.onHide(shim.getElement());
416: FramePanel.push(framePanel);
417: }
418:
419: int x = bee.x;
420: int y = bee.y;
421: int width = resizeWidget.getOffsetWidth();
422: int height = resizeWidget.getOffsetHeight();
423:
424: int diffY = y - startBounds.y + 4;
425: int diffX = x - startBounds.x + 4;
426:
427: resizeWidget.enableEvents(true);
428:
429: BaseEvent be = new BaseEvent(this );
430: be.item = resizeWidget;
431:
432: switch (style) {
433: case Style.NORTH: {
434: be.size = height - diffY;
435: if (autoSize) {
436: MyDOM.setY(resizeElem, y);
437: MyDOM.setHeight(resizeElem, height - diffY);
438: }
439: break;
440: }
441: case Style.SOUTH: {
442: be.size = height + diffY;
443: if (autoSize) {
444: MyDOM.setHeight(resizeElem, diffY);
445: resizeWidget.setHeight(diffY);
446: }
447: break;
448: }
449: case Style.WEST: {
450: be.size = width - diffX;
451: if (autoSize) {
452: MyDOM.setX(getElement(), x);
453: resizeWidget.setWidth(width - diffX);
454: }
455: break;
456: }
457: case Style.EAST: {
458: be.size = width + diffX;
459: if (autoSize) {
460: resizeWidget.setWidth(diffX);
461: }
462: break;
463: }
464: }
465: be.type = Events.DragEnd;
466: be.widget = this ;
467: fireEvent(Events.DragEnd, be);
468:
469: fireEvent(Events.Resize, be);
470: updateHandle();
471: }
472:
473: private void onStartDrag(BaseEvent be) {
474: be.type = Events.DragStart;
475: be.widget = this ;
476: fireEvent(Events.DragStart, be);
477:
478: shim.setVisible(true);
479:
480: DOM.setIntStyleAttribute(shim.getElement(), "zIndex", MyDOM
481: .getZIndex() - 1);
482:
483: if (useFramePanel) {
484: framePanel = FramePanel.pop();
485: DOM.setIntStyleAttribute(framePanel.getElement(), "zIndex",
486: MyDOM.getZIndex() - 3);
487: framePanel.onShow(shim.getElement());
488: }
489:
490: resizeWidget.enableEvents(false);
491:
492: if (containerWidget != null) {
493: switch (style) {
494: case Style.WEST:
495: case Style.EAST:
496: int h = containerWidget.getHeight(true);
497: be.height = h;
498: break;
499: case Style.NORTH:
500: case Style.SOUTH:
501: int w = containerWidget.getWidth(true);
502: be.width = w;
503: break;
504: }
505: }
506:
507: startBounds = new Rectangle();
508: startBounds.y = be.getClientY();
509: startBounds.x = be.getClientX();
510:
511: boolean v = style == Style.WEST || style == Style.EAST;
512: int size;
513: if (v) {
514: size = MyDOM.getWidth(resizeElem, false);
515: } else {
516: size = MyDOM.getHeight(resizeElem, false);
517: }
518:
519: int c1 = size - minSize;
520: if (size < minSize) {
521: c1 = 0;
522: }
523: int c2 = Math.max(maxSize - size, 0);
524: if (v) {
525: draggable.constrainVertical = true;
526: draggable.setXConstraint(style == Style.WEST ? c2 : c1,
527: style == Style.WEST ? c1 : c2);
528: } else {
529: draggable.constrainHorizontal = true;
530: draggable.setYConstraint(style == Style.NORTH ? c2 : c1,
531: style == Style.NORTH ? c1 : c2);
532: }
533: }
534:
535: }
|