001: /*
002: * Copyright 2007 Google Inc.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005: * use this file except in compliance with the License. You may obtain a copy of
006: * the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012: * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013: * License for the specific language governing permissions and limitations under
014: * the License.
015: *
016: * Contributers:
017: * Darell Meyer <darrell@mygwt.net> - Derived implementation
018: */
019: package net.mygwt.ui.client.widget;
020:
021: import java.util.ArrayList;
022: import java.util.List;
023:
024: import net.mygwt.ui.client.Events;
025: import net.mygwt.ui.client.MyDOM;
026: import net.mygwt.ui.client.MyGWT;
027: import net.mygwt.ui.client.event.BaseEvent;
028: import net.mygwt.ui.client.event.Listener;
029: import net.mygwt.ui.client.fx.FXStyle;
030: import net.mygwt.ui.client.util.Point;
031: import net.mygwt.ui.client.util.Rectangle;
032:
033: import com.google.gwt.user.client.DOM;
034: import com.google.gwt.user.client.Element;
035: import com.google.gwt.user.client.Event;
036: import com.google.gwt.user.client.EventPreview;
037: import com.google.gwt.user.client.Window;
038: import com.google.gwt.user.client.ui.KeyboardListener;
039: import com.google.gwt.user.client.ui.RootPanel;
040: import com.google.gwt.user.client.ui.Widget;
041: import com.google.gwt.user.client.ui.WidgetHelper;
042:
043: /**
044: * A shadow panel that can "pop up" over other widgets. It overlays the
045: * browser's client area (and any previously-created popups).
046: *
047: * <dl>
048: * <dt><b>Events:</b></dt>
049: *
050: * <dd><b>BeforeOpen</b> : (widget)<br>
051: * <div>Fires before the popup is displayed. Listeners can set the
052: * <code>doit</code> field to <code>false</code> to cancel the action.</div>
053: * <ul>
054: * <li>widget : this</li>
055: * </ul>
056: * </dd>
057: *
058: * <dd><b>Open</b> : (widget)<br>
059: * <div>Fires after a popup is opened.</div>
060: * <ul>
061: * <li>widget : this</li>
062: * </ul>
063: * </dd>
064: *
065: * <dd><b>BeforeClose</b> : (widget)<br>
066: * <div>Fires before the popup is closed. Listeners can set the
067: * <code>doit</code> field to <code>false</code> to cancel the action.</div>
068: * <ul>
069: * <li>widget : this</li>
070: * </ul>
071: * </dd>
072: *
073: * <dd><b>Close</b> : (widget)<br>
074: * <div>Fires after a popup is closed.</div>
075: * <ul>
076: * <li>widget : this</li>
077: * </ul>
078: * </dd>
079: *
080: * <dt><b>CSS:</b></dt>
081: * <dd>.my-popup (the popup itself)</dd>
082: * </dl>
083: */
084: public class Popup extends Component implements EventPreview {
085:
086: private boolean animate = true;
087: private boolean eventPreview = true;
088: private boolean autoFocus = true;
089: private int yOffset = 15;
090: private int xOffset = 10;
091: private boolean constrainViewport = true;
092: private int duration = 200;
093: private boolean showing, autoHide;
094: private Widget widget;
095: private FramePanel framePanel;
096: private Shadow shadow;
097: private boolean showShadow;
098: private List ignoreElements;
099:
100: /**
101: * Creates a new popup panel.
102: */
103: public Popup() {
104: baseStyle = "my-popup";
105: }
106:
107: /**
108: * Creates an new popup panel.
109: *
110: * @param autoHide <code>true</code> to hide if "click" occurs outside of
111: * the popup
112: */
113: public Popup(boolean autoHide) {
114: this ();
115: this .autoHide = autoHide;
116: }
117:
118: /**
119: * Centers the panel within the viewport.
120: */
121: public void center() {
122: MyDOM.center(getElement());
123: }
124:
125: /**
126: * Returns <code>true</code> if animations are enabled.
127: *
128: * @return the animation state
129: */
130: public boolean getAnimate() {
131: return animate;
132: }
133:
134: /**
135: * Returns <code>true</code> if auto focus is enabled.
136: *
137: * @return the auto focus state
138: */
139: public boolean getAutoFocus() {
140: return autoFocus;
141: }
142:
143: /**
144: * Returns <code>true</code> if auto hide is enabled.
145: *
146: * @return the auto hide state
147: */
148: public boolean getAutoHide() {
149: return autoHide;
150: }
151:
152: /**
153: * Returns <code>true</code> if the popup will be constrained to the
154: * viewport.
155: *
156: * @return the constrain viewport state
157: */
158: public boolean getConstrainViewport() {
159: return constrainViewport;
160: }
161:
162: /**
163: * Returns the animation duration.
164: *
165: * @return the duration
166: */
167: public int getDuration() {
168: return duration;
169: }
170:
171: /**
172: * Returns <code>true</code> if event preview is enabled.
173: *
174: * @return the event preview state
175: */
176: public boolean getEventPreview() {
177: return eventPreview;
178: }
179:
180: /**
181: * Any elements added to this list will be ignored when auto close is enabled.
182: *
183: * @return the list of ignored elements
184: */
185: public List getIgnoreList() {
186: if (ignoreElements == null) {
187: ignoreElements = new ArrayList();
188: }
189: return ignoreElements;
190: }
191:
192: /**
193: * Returns <code>true</coe> if the shadow is enabled.
194: *
195: * @return the show shadow state
196: */
197: public boolean getShadow() {
198: return showShadow;
199: }
200:
201: /**
202: * Returns the x offset.
203: *
204: * @return the x offset
205: */
206: public int getXOffset() {
207: return xOffset;
208: }
209:
210: /**
211: * Returns the y offset.
212: *
213: * @return the offset
214: */
215: public int getYOffset() {
216: return yOffset;
217: }
218:
219: /**
220: * Hides the popup. This has no effect if it is not currently visible.
221: */
222: public void hide() {
223: if (!fireEvent(Events.BeforeClose)) {
224: return;
225: }
226: if (!showing)
227: return;
228: showing = false;
229:
230: framePanel.onHide(getElement());
231: FramePanel.push(framePanel);
232:
233: if (showShadow) {
234: shadow.remove();
235: Shadow.push(shadow);
236: }
237:
238: if (eventPreview) {
239: DOM.removeEventPreview(this );
240: }
241:
242: if (animate) {
243: FXStyle fx = new FXStyle(getElement());
244: fx.duration = duration;
245: fx.addListener(Events.EffectComplete, new Listener() {
246: public void handleEvent(BaseEvent be) {
247: afterHide();
248: }
249: });
250: fx.fadeOut();
251: } else {
252: afterHide();
253: }
254: }
255:
256: public boolean isVisible() {
257: return showing;
258: }
259:
260: public boolean onEventPreview(Event event) {
261: int type = DOM.eventGetType(event);
262: Element target = DOM.eventGetTarget(event);
263: switch (type) {
264: case Event.ONMOUSEDOWN:
265: case Event.ONMOUSEUP:
266: case Event.ONMOUSEMOVE:
267: case Event.ONCLICK:
268: case Event.ONDBLCLICK: {
269: if (DOM.getCaptureElement() == null) {
270: // Disallow mouse events outside of the popup.
271: if (!DOM.isOrHasChild(getElement(), target)) {
272: // If it's a click event, and auto-hide is enabled: hide the popup
273: // and _don't_ eat the event.
274: if (autoHide && (type == Event.ONCLICK)
275: || isRightClick(event)) {
276: if (ignoreElements != null) {
277: for (int i = 0; i < ignoreElements.size(); i++) {
278: Element elem = (Element) ignoreElements
279: .get(i);
280: if (DOM.isOrHasChild(elem, target)) {
281: return true;
282: }
283: }
284: }
285: if (onAutoHide(event)) {
286: hide();
287: return true;
288: }
289: return false;
290: }
291: return false;
292: }
293: }
294: break;
295: }
296: case Event.ONKEYUP:
297: int code = DOM.eventGetKeyCode(event);
298: BaseEvent be = new BaseEvent();
299: be.event = event;
300: be.widget = this ;
301: be.item = widget;
302: onKeyPress(be);
303: switch (code) {
304: case KeyboardListener.KEY_ESCAPE:
305: onAutoHide(event);
306: }
307: break;
308: }
309: return true;
310: }
311:
312: /**
313: * Specifies if the hiding and showing of the panel should be animated.
314: * Default value is <code>true</code>.
315: *
316: * @param animate <code>true</code> to enable animations
317: */
318: public void setAnimate(boolean animate) {
319: this .animate = animate;
320: }
321:
322: /**
323: * Specifies if focus should be set on the popup when displayed. Default value
324: * is <code>true</code>.
325: *
326: * @param autoFocus <code>true</code> to enable auto focus
327: */
328: public void setAutoFocus(boolean autoFocus) {
329: this .autoFocus = autoFocus;
330: }
331:
332: /**
333: * Sets the auto hide state. When auto hide is enabled, the popup is closed
334: * when a click occurs outside the popup's bounds.
335: *
336: * @param autoHide <code>true</code> to enable auto hide
337: */
338: public void setAutoHide(boolean autoHide) {
339: this .autoHide = autoHide;
340: }
341:
342: /**
343: * Specifies if the popup location should be forced to the viewport area.
344: * Default value is <code>true</code>.
345: *
346: * @param constrainViewport <code>true<code> to enable
347: */
348: public void setConstrainViewport(boolean constrainViewport) {
349: this .constrainViewport = constrainViewport;
350: }
351:
352: /**
353: * Sets the length of the fade effect in milliseconds. Default value is 200.
354: *
355: * @param duration the duration in millisecond
356: */
357: public void setDuration(int duration) {
358: this .duration = duration;
359: }
360:
361: /**
362: * Sets whether event preview should be enabled when opening the popup.
363: * Default value is <code>true</code>.
364: *
365: * @param eventPreview the event preview state
366: */
367: public void setEventPreview(boolean eventPreview) {
368: this .eventPreview = eventPreview;
369: }
370:
371: /**
372: * Sets whether the shadow should be displayed. Defualt value is
373: * <code>false</code>.
374: *
375: * @param show <code>true</code> to show the shadow
376: */
377: public void setShadow(boolean show) {
378: this .showShadow = show;
379: }
380:
381: /**
382: * Sets the popup's content.
383: *
384: * @param widget the content widget
385: */
386: public void setWidget(Widget widget) {
387: this .widget = widget;
388: }
389:
390: /**
391: * Sets the x offset. Only applies when constrainViewerport is
392: * <code>true</code>. Default value is 10.
393: *
394: * @param xOffset the x offset
395: */
396: public void setXOffset(int xOffset) {
397: this .xOffset = xOffset;
398: }
399:
400: /**
401: * Sets the y offset. Only applies when constrainViewerport is
402: * <code>true</code>. Default value is 15.
403: *
404: * @param yOffset the y offset
405: */
406: public void setYOffset(int yOffset) {
407: this .yOffset = yOffset;
408: }
409:
410: /**
411: * Displays the popup. It must have a child widget before this method is
412: * called.
413: */
414: public void show() {
415: if (showing) {
416: return;
417: }
418: if (!fireEvent(Events.BeforeOpen)) {
419: return;
420: }
421: onShowPopup();
422: }
423:
424: /**
425: * Displays the popup.
426: *
427: * @param elem the element to align to
428: * @param pos the position
429: */
430: public void show(Element elem, String pos) {
431: if (showing) {
432: return;
433: }
434: if (!fireEvent(Events.BeforeOpen)) {
435: return;
436: }
437: DOM.appendChild(getElement(), widget.getElement());
438: Point p = MyDOM.getAlignToXY(getElement(), elem, pos, null);
439: MyDOM.setLeftTop(getElement(), p.x, p.y);
440: onShowPopup();
441: }
442:
443: /**
444: * Displays the popup.
445: *
446: * @param elem the element to align to
447: * @param pos the postion
448: * @param offsets the offsets
449: */
450: public void show(Element elem, String pos, int[] offsets) {
451: if (showing) {
452: return;
453: }
454: if (!fireEvent(Events.BeforeOpen)) {
455: return;
456: }
457: DOM.appendChild(getElement(), widget.getElement());
458: Point p = MyDOM.getAlignToXY(getElement(), elem, pos, offsets);
459: MyDOM.setLeftTop(getElement(), p.x, p.y);
460: onShowPopup();
461: }
462:
463: /**
464: * Shows the popup at the specified location.
465: *
466: * @param x the x coordinate
467: * @param y the y coordinate
468: */
469: public void show(int x, int y) {
470: DOM.appendChild(getElement(), widget.getElement());
471: MyDOM.setLeftTop(getElement(), x, y);
472: onShowPopup();
473: }
474:
475: /**
476: * Displays the popup aligned to the bottom left of the widget. For exact
477: * control of popup position see {@link MyDOM#alignTo}.
478: *
479: * @param widget the widget to use for alignment
480: */
481: public void show(Widget widget) {
482: int[] offset = new int[] { 0, 2 };
483: Point p = MyDOM.getAlignToXY(getElement(), widget.getElement(),
484: null, offset);
485: MyDOM.setLeftTop(getElement(), p.x, p.y);
486: onShowPopup();
487: }
488:
489: protected void afterHide() {
490: RootPanel.get().remove(this );
491: showing = false;
492:
493: WidgetHelper.doDetach(widget);
494: fireEvent(Events.Close);
495: }
496:
497: protected void afterShow() {
498: if (showShadow) {
499: shadow = Shadow.pop();
500: shadow.show(this );
501: }
502:
503: if (autoFocus) {
504: MyDOM.setFocus(getElement(), true);
505: }
506:
507: fireEvent(Events.Open);
508: }
509:
510: /**
511: * Subclasses may override to cancel the close from an auto hide.
512: *
513: * @param event the current event
514: * @return <code>true</code> to allow close, <code>false</code> to cancel
515: */
516: protected boolean onAutoHide(Event event) {
517: return true;
518: }
519:
520: protected void onRender() {
521: setElement(DOM.createDiv());
522: setStyleName(baseStyle);
523: setStyleAttribute("position", "absolute");
524: setStyleAttribute("zIndex", "100");
525: }
526:
527: protected void onKeyPress(BaseEvent be) {
528:
529: }
530:
531: protected void onShowPopup() {
532: showing = true;
533:
534: DOM.appendChild(getElement(), widget.getElement());
535:
536: int zIndex = MyDOM.getZIndex();
537: DOM.setIntStyleAttribute(getElement(), "zIndex", zIndex);
538:
539: MyDOM.setVisibility(getElement(), false);
540: setStyleAttribute("position", "absolute");
541:
542: RootPanel.get().add(this );
543:
544: framePanel = FramePanel.pop();
545: framePanel.onShow(getElement(), MyDOM.getZIndex() - 1);
546:
547: if (constrainViewport) {
548: int clientHeight = Window.getClientHeight()
549: + MyDOM.getBodyScrollTop();
550: int clientWidth = Window.getClientWidth()
551: + MyDOM.getBodyScrollLeft();
552:
553: Rectangle r = MyDOM.getBounds(getElement());
554:
555: int x = r.x;
556: int y = r.y;
557:
558: if (y + r.height > clientHeight) {
559: y = clientHeight - r.height - yOffset;
560: MyDOM.setTop(getElement(), y);
561: }
562: if (x + r.width > clientWidth) {
563: x = clientWidth - r.width - xOffset;
564: MyDOM.setLeft(getElement(), x);
565: }
566: }
567:
568: if (!widget.isAttached()) {
569: WidgetHelper.doAttach(widget);
570: }
571:
572: MyDOM.setVisibility(getElement(), true);
573:
574: if (eventPreview) {
575: DOM.addEventPreview(this );
576: }
577:
578: if (animate) {
579: FXStyle fx = new FXStyle(getElement());
580: fx.duration = duration;
581: fx.addListener(Events.EffectComplete, new Listener() {
582: public void handleEvent(BaseEvent be) {
583: afterShow();
584: }
585: });
586: fx.fadeIn();
587: } else {
588: afterShow();
589: }
590: }
591:
592: private boolean isRightClick(Event event) {
593: int type = DOM.eventGetType(event);
594: if (type == Event.ONMOUSEUP) {
595: if (DOM.eventGetButton(event) == Event.BUTTON_RIGHT
596: || (MyGWT.isMac && DOM.eventGetCtrlKey(event))) {
597: return true;
598: }
599: }
600: return false;
601: }
602:
603: }
|