com.google.gwt.user.client.ui.Widget.java Source code

Java tutorial

Introduction

Here is the source code for com.google.gwt.user.client.ui.Widget.java

Source

/*
 * Copyright 2008 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.google.gwt.user.client.ui;

import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.EventTarget;
import com.google.gwt.event.dom.client.DomEvent;
import com.google.gwt.event.logical.shared.AttachEvent;
import com.google.gwt.event.logical.shared.AttachEvent.Handler;
import com.google.gwt.event.logical.shared.HasAttachHandlers;
import com.google.gwt.event.shared.EventHandler;
import com.google.gwt.event.shared.GwtEvent;
import com.google.gwt.event.shared.HandlerManager;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.EventListener;

/**
 * The base class for the majority of user-interface objects. Widget adds
 * support for receiving events from the browser and being added directly to
 * {@link com.google.gwt.user.client.ui.Panel panels}.
 */
public class Widget extends UIObject implements EventListener, HasAttachHandlers, IsWidget {
    /**
     * This convenience method makes a null-safe call to
     * {@link IsWidget#asWidget()}.
     *
     * @return the widget aspect, or <code>null</code> if w is null
     */
    public static Widget asWidgetOrNull(IsWidget w) {
        return w == null ? null : w.asWidget();
    }

    /**
     * A bit-map of the events that should be sunk when the widget is attached
     * to the DOM. (We delay the sinking of events to improve startup
     * performance.) When the widget is attached, this is set to -1
     * <p>
     * Package protected to allow Composite to see it.
     */
    int eventsToSink;

    private boolean attached;

    private HandlerManager handlerManager;

    private Object layoutData;

    private Widget parent;

    @Override
    public HandlerRegistration addAttachHandler(Handler handler) {
        return addHandler(handler, AttachEvent.getType());
    }

    /**
     * For <a href=
     * "http://code.google.com/p/google-web-toolkit/wiki/UnderstandingMemoryLeaks"
     * >browsers which do not leak</a>, adds a native event handler to the
     * widget. Note that, unlike the
     * {@link #addDomHandler(EventHandler, com.google.gwt.event.dom.client.DomEvent.Type)}
     * implementation, there is no need to attach the widget to the DOM in order
     * to cause the event handlers to be attached.
     *
     * @param <H>
     *            the type of handler to add
     * @param type
     *            the event key
     * @param handler
     *            the handler
     * @return {@link HandlerRegistration} used to remove the handler
     */
    public final <H extends EventHandler> HandlerRegistration addBitlessDomHandler(final H handler,
            DomEvent.Type<H> type) {
        assert handler != null : "handler must not be null";
        assert type != null : "type must not be null";
        sinkBitlessEvent(type.getName());
        return ensureHandlers().addHandler(type, handler);
    }

    /**
     * Adds a native event handler to the widget and sinks the corresponding
     * native event. If you do not want to sink the native event, use the
     * generic addHandler method instead.
     *
     * @param <H>
     *            the type of handler to add
     * @param type
     *            the event key
     * @param handler
     *            the handler
     * @return {@link HandlerRegistration} used to remove the handler
     */
    public final <H extends EventHandler> HandlerRegistration addDomHandler(final H handler,
            DomEvent.Type<H> type) {
        assert handler != null : "handler must not be null";
        assert type != null : "type must not be null";
        int typeInt = Event.getTypeInt(type.getName());
        if (typeInt == -1) {
            sinkBitlessEvent(type.getName());
        } else {
            sinkEvents(typeInt);
        }
        return ensureHandlers().addHandler(type, handler);
    }

    /**
     * Adds this handler to the widget.
     *
     * @param <H>
     *            the type of handler to add
     * @param type
     *            the event type
     * @param handler
     *            the handler
     * @return {@link HandlerRegistration} used to remove the handler
     */
    public final <H extends EventHandler> HandlerRegistration addHandler(final H handler, GwtEvent.Type<H> type) {
        return ensureHandlers().addHandler(type, handler);
    }

    @Override
    public Widget asWidget() {
        return this;
    }

    @Override
    public void fireEvent(GwtEvent<?> event) {
        if (handlerManager != null) {
            handlerManager.fireEvent(event);
        }
    }

    /**
     * Gets the panel-defined layout data associated with this widget.
     *
     * @return the widget's layout data
     * @see #setLayoutData
     */
    public Object getLayoutData() {
        return layoutData;
    }

    @Override
    public int getOffsetHeight() {
        // FIXME - not efficient check
        return isAttached() ? super.getOffsetHeight() : 0;
    }

    @Override
    public int getOffsetWidth() {
        // FIXME - not efficient check
        return isAttached() ? super.getOffsetWidth() : 0;
    }

    /**
     * Gets this widget's parent panel.
     *
     * @return the widget's parent panel
     */
    public Widget getParent() {
        return parent;
    }

    /**
     * Determines whether this widget is currently attached to the browser's
     * document (i.e., there is an unbroken chain of widgets between this widget
     * and the underlying browser document).
     *
     * @return <code>true</code> if the widget is attached
     */
    @Override
    public boolean isAttached() {
        return attached;
    }

    @Override
    public void onBrowserEvent(Event event) {
        switch (DOM.eventGetType(event)) {
        case Event.ONMOUSEOVER:
            // Only fire the mouse over event if it's coming from outside this
            // widget.
        case Event.ONMOUSEOUT:
            // Only fire the mouse out event if it's leaving this
            // widget.
            EventTarget relatedEventTarget = event.getRelatedEventTarget();
            if (relatedEventTarget != null && Element.is(relatedEventTarget)) {
                Element related = relatedEventTarget.cast();
                if (related != null && getElement().isOrHasChild(related)) {
                    return;
                }
            }
            break;
        }
        DomEvent.fireNativeEvent(event, this, this.getElement());
    }

    /**
     * Removes this widget from its parent widget, if one exists.
     *
     * <p>
     * If it has no parent, this method does nothing. If it is a "root" widget
     * (meaning it's been added to the detach list via
     * {@link RootPanel#detachOnWindowClose(Widget)}), it will be removed from
     * the detached immediately. This makes it possible for Composites and
     * Panels to adopt root widgets.
     * </p>
     *
     * @throws IllegalStateException
     *             if this widget's parent does not support removal (e.g.
     *             {@link Composite})
     */
    public void removeFromParent() {
        if (parent == null) {
            // If the widget had no parent, check to see if it was in the detach
            // list
            // and remove it if necessary.
            if (RootPanel.isInDetachList(this)) {
                RootPanel.detachNow(this);
            }
        } else if (parent instanceof HasWidgets) {
            ((HasWidgets) parent).remove(this);
        } else if (parent != null) {
            throw new IllegalStateException("This widget's parent does not implement HasWidgets");
        }
    }

    /**
     * Sets the panel-defined layout data associated with this widget. Only the
     * panel that currently contains a widget should ever set this value. It
     * serves as a place to store layout bookkeeping data associated with a
     * widget.
     *
     * @param layoutData
     *            the widget's layout data
     */
    public void setLayoutData(Object layoutData) {
        this.layoutData = layoutData;
    }

    /**
     * Overridden to defer the call to super.sinkEvents until the first time
     * this widget is attached to the dom, as a performance enhancement.
     * Subclasses wishing to customize sinkEvents can preserve this deferred
     * sink behavior by putting their implementation behind a check of
     * <code>isOrWasAttached()</code>:
     *
     * <pre>
     * {@literal @}Override
     * public void sinkEvents(int eventBitsToAdd) {
     *   if (isOrWasAttached()) {
     *     /{@literal *} customized sink code goes here {@literal *}/
     *   } else {
     *     super.sinkEvents(eventBitsToAdd);
     *  }
     *}
     * </pre>
     */
    @Override
    public void sinkEvents(int eventBitsToAdd) {
        if (isOrWasAttached()) {
            super.sinkEvents(eventBitsToAdd);
        } else {
            eventsToSink |= eventBitsToAdd;
        }
    }

    @Override
    public void unsinkEvents(int eventBitsToRemove) {
        if (isOrWasAttached()) {
            super.unsinkEvents(eventBitsToRemove);
        } else {
            eventsToSink &= ~eventBitsToRemove;
        }
    }

    /**
     * Creates the {@link HandlerManager} used by this Widget. You can override
     * this method to create a custom {@link HandlerManager}.
     *
     * @return the {@link HandlerManager} you want to use
     */
    protected HandlerManager createHandlerManager() {
        return new HandlerManager(this);
    }

    /**
     * Fires an event on a child widget. Used to delegate the handling of an
     * event from one widget to another.
     *
     * @param event
     *            the event
     * @param target
     *            fire the event on the given target
     */
    protected void delegateEvent(Widget target, GwtEvent<?> event) {
        target.fireEvent(event);
    }

    /**
     * If a widget contains one or more child widgets that are not in the
     * logical widget hierarchy (the child is physically connected only on the
     * DOM level), it must override this method and call {@link #onAttach()} for
     * each of its child widgets.
     *
     * @see #onAttach()
     */
    protected void doAttachChildren() {
    }

    /**
     * If a widget contains one or more child widgets that are not in the
     * logical widget hierarchy (the child is physically connected only on the
     * DOM level), it must override this method and call {@link #onDetach()} for
     * each of its child widgets.
     *
     * @see #onDetach()
     */
    protected void doDetachChildren() {
    }

    /**
     * Gets the number of handlers listening to the event type.
     *
     * @param type
     *            the event type
     * @return the number of registered handlers
     */
    protected int getHandlerCount(GwtEvent.Type<?> type) {
        return handlerManager == null ? 0 : handlerManager.getHandlerCount(type);
    }

    /**
     * Has this widget ever been attached?
     *
     * @return true if this widget ever been attached to the DOM, false
     *         otherwise
     */
    protected final boolean isOrWasAttached() {
        return eventsToSink == -1;
    }

    /**
     * <p>
     * This method is called when a widget is attached to the browser's
     * document. To receive notification after a Widget has been added to the
     * document, override the {@link #onLoad} method or use
     * {@link #addAttachHandler}.
     * </p>
     * <p>
     * It is strongly recommended that you override {@link #onLoad()} or
     * {@link #doAttachChildren()} instead of this method to avoid
     * inconsistencies between logical and physical attachment states.
     * </p>
     * <p>
     * Subclasses that override this method must call
     * <code>super.onAttach()</code> to ensure that the Widget has been attached
     * to its underlying Element.
     * </p>
     *
     * @throws IllegalStateException
     *             if this widget is already attached
     * @see #onLoad()
     * @see #doAttachChildren()
     */
    protected void onAttach() {
        if (isAttached()) {
            throw new IllegalStateException(
                    "Should only call onAttach when the widget is detached from the browser's document");
        }
        attached = true;
        // Event hookup code
        DOM.setEventListener(getElement(), this);
        int bitsToAdd = eventsToSink;
        eventsToSink = -1;
        if (bitsToAdd > 0) {
            sinkEvents(bitsToAdd);
        }
        doAttachChildren();
        // onLoad() gets called only *after* all of the children are attached
        // and
        // the attached flag is set. This allows widgets to be notified when
        // they
        // are fully attached, and panels when all of their children are
        // attached.
        onLoad();
        AttachEvent.fire(this, true);
    }

    /**
     * <p>
     * This method is called when a widget is detached from the browser's
     * document. To receive notification before a Widget is removed from the
     * document, override the {@link #onUnload} method or use
     * {@link #addAttachHandler}.
     * </p>
     * <p>
     * It is strongly recommended that you override {@link #onUnload()} or
     * {@link #doDetachChildren()} instead of this method to avoid
     * inconsistencies between logical and physical attachment states.
     * </p>
     * <p>
     * Subclasses that override this method must call
     * <code>super.onDetach()</code> to ensure that the Widget has been detached
     * from the underlying Element. Failure to do so will result in application
     * memory leaks due to circular references between DOM Elements and
     * JavaScript objects.
     * </p>
     *
     * @throws IllegalStateException
     *             if this widget is already detached
     * @see #onUnload()
     * @see #doDetachChildren()
     */
    protected void onDetach() {
        if (!isAttached()) {
            throw new IllegalStateException(
                    "Should only call onDetach when the widget is attached to the browser's document");
        }
        try {
            // onUnload() gets called *before* everything else (the opposite of
            // onLoad()).
            onUnload();
            AttachEvent.fire(this, false);
        } finally {
            // Put this in a finally, just in case onUnload throws an exception.
            try {
                doDetachChildren();
            } finally {
                // Put this in a finally, in case doDetachChildren throws an
                // exception.
                DOM.setEventListener(getElement(), null);
                attached = false;
            }
        }
    }

    /**
     * This method is called immediately after a widget becomes attached to the
     * browser's document.
     */
    protected void onLoad() {
    }

    /**
     * This method is called immediately before a widget will be detached from
     * the browser's document.
     */
    protected void onUnload() {
    }

    /**
     * Ensures the existence of the handler manager.
     *
     * @return the handler manager
     */
    HandlerManager ensureHandlers() {
        return handlerManager == null ? handlerManager = createHandlerManager() : handlerManager;
    }

    HandlerManager getHandlerManager() {
        return handlerManager;
    }

    @Override
    void replaceElement(com.google.gwt.dom.client.Element elem) {
        if (isAttached()) {
            // Remove old event listener to avoid leaking. onDetach will not do
            // this
            // for us, because it is only called when the widget itself is
            // detached
            // from the document.
            DOM.setEventListener(getElement(), null);
        }
        super.replaceElement(elem);
        if (isAttached()) {
            // Hook the event listener back up on the new element. onAttach will
            // not
            // do this for us, because it is only called when the widget itself
            // is
            // attached to the document.
            DOM.setEventListener(getElement(), this);
        }
    }

    /**
     * Sets this widget's parent. This method should only be called by
     * {@link Panel} and {@link Composite}.
     *
     * @param parent
     *            the widget's new parent
     * @throws IllegalStateException
     *             if <code>parent</code> is non-null and the widget already has
     *             a parent
     */
    void setParent(Widget parent) {
        Widget oldParent = this.parent;
        if (parent == null) {
            try {
                if (oldParent != null && oldParent.isAttached()) {
                    onDetach();
                    assert !isAttached() : "Failure of " + this.getClass().getName() + " to call super.onDetach()";
                }
            } finally {
                // Put this in a finally in case onDetach throws an exception.
                this.parent = null;
            }
        } else {
            if (oldParent != null) {
                throw new IllegalStateException("Cannot set a new parent without first clearing the old parent");
            }
            this.parent = parent;
            if (parent.isAttached()) {
                onAttach();
                assert isAttached() : "Failure of " + this.getClass().getName() + " to call super.onAttach()";
            }
        }
    }
}