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: package com.google.gwt.user.client.ui;
017:
018: import com.google.gwt.user.client.DOM;
019: import com.google.gwt.user.client.Element;
020: import com.google.gwt.user.client.Event;
021: import com.google.gwt.user.client.EventListener;
022:
023: /**
024: * The base class for the majority of user-interface objects. Widget adds
025: * support for receiving events from the browser and being added directly to
026: * {@link com.google.gwt.user.client.ui.Panel panels}.
027: */
028: public class Widget extends UIObject implements EventListener {
029:
030: private boolean attached;
031: private Object layoutData;
032: private Widget parent;
033:
034: /**
035: * Gets this widget's parent panel.
036: *
037: * @return the widget's parent panel
038: */
039: public Widget getParent() {
040: return parent;
041: }
042:
043: /**
044: * Determines whether this widget is currently attached to the browser's
045: * document (i.e., there is an unbroken chain of widgets between this widget
046: * and the underlying browser document).
047: *
048: * @return <code>true</code> if the widget is attached
049: */
050: public boolean isAttached() {
051: return attached;
052: }
053:
054: public void onBrowserEvent(Event event) {
055: }
056:
057: /**
058: * Removes this widget from its parent widget. If it has no parent, this
059: * method does nothing.
060: *
061: * @throws IllegalStateException if this widget's parent does not support
062: * removal (e.g. {@link Composite})
063: */
064: public void removeFromParent() {
065: if (parent instanceof HasWidgets) {
066: ((HasWidgets) parent).remove(this );
067: } else if (parent != null) {
068: throw new IllegalStateException(
069: "This widget's parent does not implement HasWidgets");
070: }
071: }
072:
073: /**
074: * If a widget implements HasWidgets, it must override this method and call
075: * onAttach() for each of its child widgets.
076: *
077: * @see Panel#onAttach()
078: */
079: protected void doAttachChildren() {
080: }
081:
082: /**
083: * If a widget implements HasWidgets, it must override this method and call
084: * onDetach() for each of its child widgets.
085: *
086: * @see Panel#onDetach()
087: */
088: protected void doDetachChildren() {
089: }
090:
091: /**
092: * This method is called when a widget is attached to the browser's document.
093: * To receive notification after a Widget has been added to the document,
094: * override the {@link #onLoad} method.
095: *
096: * <p>
097: * Subclasses that override this method must call
098: * <code>super.onAttach()</code> to ensure that the Widget has been attached
099: * to its underlying Element.
100: * </p>
101: *
102: * @throws IllegalStateException if this widget is already attached
103: */
104: protected void onAttach() {
105: if (isAttached()) {
106: throw new IllegalStateException(
107: "Should only call onAttach when the widget is detached from the browser's document");
108: }
109:
110: attached = true;
111: DOM.setEventListener(getElement(), this );
112: doAttachChildren();
113:
114: // onLoad() gets called only *after* all of the children are attached and
115: // the attached flag is set. This allows widgets to be notified when they
116: // are fully attached, and panels when all of their children are attached.
117: onLoad();
118: }
119:
120: /**
121: * This method is called when a widget is detached from the browser's
122: * document. To receive notification before a Widget is removed from the
123: * document, override the {@link #onUnload} method.
124: *
125: * <p>
126: * Subclasses that override this method must call
127: * <code>super.onDetach()</code> to ensure that the Widget has been detached
128: * from the underlying Element. Failure to do so will result in application
129: * memory leaks due to circular references between DOM Elements and JavaScript
130: * objects.
131: * </p>
132: *
133: * @throws IllegalStateException if this widget is already detached
134: */
135: protected void onDetach() {
136: if (!isAttached()) {
137: throw new IllegalStateException(
138: "Should only call onDetach when the widget is attached to the browser's document");
139: }
140:
141: try {
142: // onUnload() gets called *before* everything else (the opposite of
143: // onLoad()).
144: onUnload();
145: } finally {
146: // Put this in a finally, just in case onUnload throws an exception.
147: doDetachChildren();
148: DOM.setEventListener(getElement(), null);
149: attached = false;
150: }
151: }
152:
153: /**
154: * This method is called immediately after a widget becomes attached to the
155: * browser's document.
156: */
157: protected void onLoad() {
158: }
159:
160: /**
161: * This method is called immediately before a widget will be detached from the
162: * browser's document.
163: */
164: protected void onUnload() {
165: }
166:
167: /**
168: * Sets this object's browser element. Widget subclasses must call this method
169: * before attempting to call any other methods.
170: *
171: * If a browser element has already been attached, then it is replaced with
172: * the new element. The old event listeners are removed from the old browser
173: * element, and the event listeners are set up on the new browser element.
174: *
175: * @param elem the object's new element
176: */
177: @Override
178: protected void setElement(Element elem) {
179: if (isAttached()) {
180: // Remove old event listener to avoid leaking. onDetach will not do this
181: // for us, because it is only called when the widget itself is detached
182: // from the document.
183: DOM.setEventListener(getElement(), null);
184: }
185:
186: super .setElement(elem);
187: if (isAttached()) {
188: // Hook the event listener back up on the new element. onAttach will not
189: // do this for us, because it is only called when the widget itself is
190: // attached to the document.
191: DOM.setEventListener(elem, this );
192: }
193: }
194:
195: /**
196: * Gets the panel-defined layout data associated with this widget.
197: *
198: * @return the widget's layout data
199: * @see #setLayoutData
200: */
201: Object getLayoutData() {
202: return layoutData;
203: }
204:
205: /**
206: * Sets the panel-defined layout data associated with this widget. Only the
207: * panel that currently contains a widget should ever set this value. It
208: * serves as a place to store layout bookkeeping data associated with a
209: * widget.
210: *
211: * @param layoutData the widget's layout data
212: */
213: void setLayoutData(Object layoutData) {
214: this .layoutData = layoutData;
215: }
216:
217: /**
218: * Sets this widget's parent. This method should only be called by
219: * {@link Panel} and {@link Composite}.
220: *
221: * @param parent the widget's new parent
222: * @throws IllegalStateException if <code>parent</code> is non-null and the
223: * widget already has a parent
224: */
225: void setParent(Widget parent) {
226: Widget oldParent = this .parent;
227: if (parent == null) {
228: if (oldParent != null && oldParent.isAttached()) {
229: onDetach();
230: assert !isAttached() : "Failure of "
231: + this .getClass().getName()
232: + " to call super.onDetach()";
233: }
234: this .parent = null;
235: } else {
236: if (oldParent != null) {
237: throw new IllegalStateException(
238: "Cannot set a new parent without first clearing the old parent");
239: }
240: this .parent = parent;
241: if (parent.isAttached()) {
242: onAttach();
243: assert isAttached() : "Failure of "
244: + this .getClass().getName()
245: + " to call super.onAttach()";
246: }
247: }
248: }
249: }
|