org.opencms.gwt.client.ui.CmsPopup.java Source code

Java tutorial

Introduction

Here is the source code for org.opencms.gwt.client.ui.CmsPopup.java

Source

/*
 * This library is part of OpenCms -
 * the Open Source Content Management System
 *
 * Copyright (c) Alkacon Software GmbH (http://www.alkacon.com)
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * For further information about Alkacon Software, please see the
 * company website: http://www.alkacon.com
 *
 * For further information about OpenCms, please see the
 * project website: http://www.opencms.org
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package org.opencms.gwt.client.ui;

import org.opencms.gwt.client.Messages;
import org.opencms.gwt.client.ui.I_CmsButton.ButtonStyle;
import org.opencms.gwt.client.ui.css.I_CmsLayoutBundle;
import org.opencms.gwt.client.util.CmsFadeAnimation;
import org.opencms.util.CmsStringUtil;

import java.util.Iterator;

import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.EventTarget;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.dom.client.Style.Position;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.MouseDownEvent;
import com.google.gwt.event.dom.client.MouseDownHandler;
import com.google.gwt.event.dom.client.MouseMoveEvent;
import com.google.gwt.event.dom.client.MouseMoveHandler;
import com.google.gwt.event.dom.client.MouseUpEvent;
import com.google.gwt.event.dom.client.MouseUpHandler;
import com.google.gwt.event.logical.shared.CloseEvent;
import com.google.gwt.event.logical.shared.CloseHandler;
import com.google.gwt.event.logical.shared.ResizeEvent;
import com.google.gwt.event.logical.shared.ResizeHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Event.NativePreviewEvent;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.IsWidget;
import com.google.gwt.user.client.ui.PopupPanel;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.user.client.ui.WidgetCollection;

/**
 * Provides a pop up dialog base.
 * 
 * @since 8.0.0
 */
public class CmsPopup extends PopupPanel implements I_CmsAutoHider {

    /**
     * The dialog button panel.<p>
     */
    private class ButtonPanel extends FlowPanel {

        /**
         * Default constructor.<p>
         */
        protected ButtonPanel() {

            // nothing to do
        }

        /**
         * Making function visible.<p>
         * 
         * @see com.google.gwt.user.client.ui.Widget#onAttach()
         */
        @Override
        protected void onAttach() {

            super.onAttach();
        }

        /**
         * Making function visible.<p>
         * 
         * @see com.google.gwt.user.client.ui.Widget#onDetach()
         */
        @Override
        protected void onDetach() {

            super.onDetach();
        }
    }

    /**
     * The dialog caption.<p>
     */
    private class Caption extends HTML {

        /**
         * Default constructor.<p>
         */
        protected Caption() {

            // nothing to do
        }

        /**
         * Making function visible.<p>
         * 
         * @see com.google.gwt.user.client.ui.Widget#onAttach()
         */
        @Override
        protected void onAttach() {

            super.onAttach();
        }

        /**
         * Making function visible.<p>
         * 
         * @see com.google.gwt.user.client.ui.Widget#onDetach()
         */
        @Override
        protected void onDetach() {

            super.onDetach();
        }
    }

    /**
     * The dialog close button.<p>
     */
    private class CloseButton extends CmsPushButton {

        /**
         * Default constructor.<p>
         */
        protected CloseButton() {

            // nothing to do
        }

        /**
         * Making function visible.<p>
         * 
         * @see com.google.gwt.user.client.ui.Widget#onAttach()
         */
        @Override
        protected void onAttach() {

            super.onAttach();
        }

        /**
         * Making function visible.<p>
         * 
         * @see com.google.gwt.user.client.ui.Widget#onDetach()
         */
        @Override
        protected void onDetach() {

            super.onDetach();
        }
    }

    /**
     * The dialog mouse handler.<p>
     */
    private class MouseHandler implements MouseDownHandler, MouseUpHandler, MouseMoveHandler {

        /**
         * Default constructor.<p>
         */
        protected MouseHandler() {

            // nothing to do
        }

        public void onMouseDown(MouseDownEvent event) {

            beginDragging(event);
        }

        public void onMouseMove(MouseMoveEvent event) {

            continueDragging(event);
        }

        public void onMouseUp(MouseUpEvent event) {

            endDragging(event);
        }
    }

    /** The default width of this dialog. */
    private static final int DEFAULT_WIDTH = 300;

    /** The window width. */
    protected int m_windowWidth;

    /** The panel holding the dialog's buttons. */
    private ButtonPanel m_buttonPanel;

    /** The dialog caption. */
    private Caption m_caption;

    private WidgetCollection m_children;

    /** Body offset left. */
    private int m_clientLeft;

    /** Body offset top. */
    private int m_clientTop;

    /** The panel for the close button. */
    private CloseButton m_close;

    /** The popup container element. */
    private com.google.gwt.user.client.Element m_containerElement;

    /** The content height correction, used when explicitly setting the dialog height. */
    private int m_contentHeightCorrection = 6;

    /** Flag if dragging. */
    private boolean m_dragging;

    /** Drag starting x position. */
    private int m_dragStartX;

    /** Drag starting y position. */
    private int m_dragStartY;

    /** The main widget of this dialog containing all others. */
    private Element m_main;

    /** The resize handler registration .*/
    private HandlerRegistration m_resizeHandlerRegistration;

    /** Signals whether a animation should be used to show the popup or not. */
    private boolean m_useAnimation = true;

    /**
     * Constructor.<p>
     */
    public CmsPopup() {

        this(DEFAULT_WIDTH);
    }

    /**
     * Constructor setting the width of the dialog.<p>
     * 
     * @param width the width to set
     */
    public CmsPopup(int width) {

        super(false, true);
        // super(autoHide, modal);

        m_containerElement = super.getContainerElement();
        setStyleName(I_CmsLayoutBundle.INSTANCE.dialogCss().popup());
        m_containerElement.setClassName(I_CmsLayoutBundle.INSTANCE.dialogCss().popupContent());
        setGlassStyleName(I_CmsLayoutBundle.INSTANCE.dialogCss().popupOverlay());
        Element dragOverlay = DOM.createDiv();
        dragOverlay.setClassName(I_CmsLayoutBundle.INSTANCE.dialogCss().dragOverlay());
        getElement().insertFirst(dragOverlay);

        m_caption = new Caption();
        m_caption.setStyleName(I_CmsLayoutBundle.INSTANCE.dialogCss().caption());
        // Add the caption to the top of the popup-panel. We need to
        // logically adopt the caption so we can catch mouse events.
        DOM.appendChild(m_containerElement, m_caption.getElement());
        adopt(m_caption);
        m_children = new WidgetCollection(this);
        m_main = DOM.createDiv();
        m_main.addClassName(I_CmsLayoutBundle.INSTANCE.dialogCss().popupMainContent());
        m_main.addClassName(I_CmsLayoutBundle.INSTANCE.dialogCss().contentPadding());
        DOM.appendChild(m_containerElement, m_main);
        m_buttonPanel = new ButtonPanel();
        m_buttonPanel.setStyleName(I_CmsLayoutBundle.INSTANCE.dialogCss().hideButtonPanel());
        // Add the caption to the top of the popup-panel. We need to
        // logically adopt the caption so we can catch mouse events.
        DOM.appendChild(m_containerElement, m_buttonPanel.getElement());
        adopt(m_buttonPanel);

        MouseHandler mouseHandler = new MouseHandler();
        addDomHandler(mouseHandler, MouseDownEvent.getType());
        addDomHandler(mouseHandler, MouseUpEvent.getType());
        addDomHandler(mouseHandler, MouseMoveEvent.getType());

        setWidth(width);
        getElement().addClassName(I_CmsLayoutBundle.INSTANCE.dialogCss().hideCaption());
    }

    /**
     * Constructor setting the dialog caption.<p>
     * 
     * @param caption the caption to set
     */
    public CmsPopup(String caption) {

        this();
        setCaption(caption);
    }

    /**
     * Constructor setting caption and width.<p>
     * 
     * @param caption the caption to set
     * @param width the width to set
     */
    public CmsPopup(String caption, int width) {

        this(width);
        setCaption(caption);
    }

    /**
     * The constructor.<p>
     * 
     * @param title the title and heading of the dialog
     * @param content the content widget
     */
    public CmsPopup(String title, Widget content) {

        this(title);
        setMainContent(content);
    }

    /**
     * Wraps the given Widget with a cornered border, padding and margin.<p>
     *  
     * @param w the widget to wrap
     * 
     * @return a new widget that wraps the given one
     */
    public static Widget wrapWithBorderPadding(Widget w) {

        w.addStyleName(I_CmsLayoutBundle.INSTANCE.dialogCss().borderPadding());
        w.addStyleName(I_CmsLayoutBundle.INSTANCE.generalCss().cornerAll());
        SimplePanel panel = new SimplePanel();
        panel.add(w);
        return panel;
    }

    /**
     * Adds the given child widget.<p>
     * 
     * @param w the widget
     */
    @Override
    public void add(Widget w) {

        add(w, m_main);
    }

    /**
     * Adds a button widget to the button panel.<p>
     * 
     * @param button the button widget
     */
    public void addButton(Widget button) {

        addButton(button, 0);
    }

    /**
     * Adds a button widget to the button panel before the given position.<p>
     * 
     * @param button the button widget
     * @param position the position to insert the button
     */
    public void addButton(Widget button, int position) {

        m_buttonPanel.insert(button, position);
        m_buttonPanel.setStyleName(I_CmsLayoutBundle.INSTANCE.dialogCss().popupButtonPanel());
    }

    /**
     * Adds a close "button" to the top of the popup.<p>
     * 
     * @param cmd the command that should be executed when the close button is clicked
     */
    public void addDialogClose(final Command cmd) {

        if (m_close == null) {
            m_close = new CloseButton();
            m_close.setTitle(Messages.get().key(Messages.GUI_CLOSE_0));
            m_close.addStyleName(I_CmsLayoutBundle.INSTANCE.dialogCss().closePopup());
            m_close.setImageClass(I_CmsLayoutBundle.INSTANCE.dialogCss().closePopupImage());
            m_close.setButtonStyle(ButtonStyle.TRANSPARENT, null);
            m_close.addClickHandler(new ClickHandler() {

                /**
                 * @see com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt.event.dom.client.ClickEvent)
                 */
                public void onClick(ClickEvent event) {

                    boolean cancelled = false;
                    try {
                        if (cmd != null) {
                            cmd.execute();
                        }
                    } catch (CmsCancelCloseException e) {
                        cancelled = true;
                    } finally {
                        if (!cancelled) {
                            hide();
                        }
                    }
                }
            });
            DOM.appendChild(m_containerElement, m_close.getElement());
            adopt(m_close);
        }
    }

    /**
     * Replaces current notification widget by an overlay.<p>
     */
    public void catchNotifications() {

        // remember current notification widget
        final I_CmsNotificationWidget widget = CmsNotification.get().getWidget();
        // create our own notification overlay
        final CmsDialogNotificationWidget notificationWidget = new CmsDialogNotificationWidget();
        add(notificationWidget);
        CmsNotification.get().setWidget(notificationWidget);

        // when closing the dialog
        addCloseHandler(new CloseHandler<PopupPanel>() {

            /**
             * @see CloseHandler#onClose(CloseEvent)
             */
            public void onClose(CloseEvent<PopupPanel> event) {

                // restore the previous notification widget
                CmsNotification.get().setWidget(widget);
                // remove the overlay notification widget
                remove(notificationWidget);
            }
        });
    }

    /**
     * @see com.google.gwt.user.client.ui.PopupPanel#center()
     */
    @Override
    public void center() {

        super.center();
        if (m_resizeHandlerRegistration == null) {
            m_resizeHandlerRegistration = Window.addResizeHandler(new ResizeHandler() {

                public void onResize(ResizeEvent event) {

                    m_windowWidth = event.getWidth();
                }
            });
        }
    }

    /**
     * Shows the dialog and centers it horizontally, but positions it at a fixed vertical position.<p>
     * 
     * @param top the top position
     */
    public void centerHorizontally(int top) {

        show();
        int left = (Window.getClientWidth() - getOffsetWidth()) >> 1;
        setPopupPosition(Math.max(Window.getScrollLeft() + left, 0), Math.max(Window.getScrollTop() + top, 0));
    }

    /**
     * @see com.google.gwt.user.client.ui.Panel#clear()
     */
    @Override
    public void clear() {

        for (Widget w : this) {
            // Orphan.
            try {
                orphan(w);
            } finally {
                // Physical detach.
                Element elem = w.getElement();
                DOM.removeChild(DOM.getParent(elem), elem);
            }
        }
        m_children = new WidgetCollection(this);
    }

    /**
     * Returns the dialog caption text.<p>
     * 
     * @return the dialog caption
     */
    public String getCaption() {

        return m_caption.getText();
    }

    /**
     * Returns the child widget with the given index.<p>
     * 
     * @param index the index
     * 
     * @return the child widget
     */
    public Widget getWidget(int index) {

        return getChildren().get(index);
    }

    /**
     * Returns the number of child widgets.<p>
     * 
     * @return the number of child widgets
     */
    public int getWidgetCount() {

        return getChildren().size();
    }

    /**
     * Returns the index of the given widget.<p>
     * 
     * @param child the child widget
     * 
     * @return the index of the child widget
     */
    public int getWidgetIndex(IsWidget child) {

        return getWidgetIndex(asWidgetOrNull(child));
    }

    /**
     * Returns the index of the given child widget.<p>
     * 
     * @param child the child widget
     * 
     * @return the index
     */
    public int getWidgetIndex(Widget child) {

        return getChildren().indexOf(child);
    }

    /**
     * Returns <code>true</code> if a caption is set for this popup <code>false</code> otherwise.<p>
     * 
     * @return <code>true</code> if a caption is set for this popup <code>false</code> otherwise
     */
    public boolean hasCaption() {

        return CmsStringUtil.isNotEmptyOrWhitespaceOnly(m_caption.getText());
    }

    /**
     * @see com.google.gwt.user.client.ui.PopupPanel#hide()
     */
    @Override
    public void hide() {

        if (m_resizeHandlerRegistration != null) {
            m_resizeHandlerRegistration.removeHandler();
            m_resizeHandlerRegistration = null;
        }
        super.hide();
    }

    /**
     * Inserts a child widget before the given index.<p>
     * 
     * @param w the child widget
     * @param beforeIndex the index
     * 
     * @throws IndexOutOfBoundsException if the index is out of bounds
     */
    public void insert(Widget w, int beforeIndex) throws IndexOutOfBoundsException {

        insert(w, m_main, beforeIndex, true);
    }

    /**
     * Inserts a widget as the first widget in the popup.<p>
     * 
     * @param widget the widget to insert 
     */
    public void insertFront(Widget widget) {

        insert(widget, 0);
    }

    /**
     * @see com.google.gwt.user.client.ui.SimplePanel#iterator()
     */
    @Override
    public Iterator<Widget> iterator() {

        return getChildren().iterator();
    }

    /**
     * @see com.google.gwt.user.client.ui.Widget#onBrowserEvent(com.google.gwt.user.client.Event)
     */
    @Override
    public void onBrowserEvent(Event event) {

        // If we're not yet dragging, only trigger mouse events if the event occurs
        // in the caption wrapper
        switch (event.getTypeInt()) {
        case Event.ONMOUSEDOWN:
        case Event.ONMOUSEUP:
        case Event.ONMOUSEMOVE:
        case Event.ONMOUSEOVER:
        case Event.ONMOUSEOUT:
            if (!m_dragging && !isCaptionEvent(event)) {
                return;
            }
            break;
        default:
        }

        super.onBrowserEvent(event);
    }

    /**
     * Removes a child widget.<p>
     * 
     * @param index the index of the widget to remove
     * 
     * @return <code>true</code> if the there was a widget at the given index to remove
     */
    public boolean remove(int index) {

        Widget w = getWidget(index);
        if (w != null) {
            return remove(getWidget(index));
        }
        return false;
    }

    /**
     * @see com.google.gwt.user.client.ui.SimplePanel#remove(com.google.gwt.user.client.ui.Widget)
     */
    @Override
    public boolean remove(Widget w) {

        // Validate.
        if (w.getParent() != this) {
            return false;
        }
        // Orphan.
        try {
            orphan(w);
        } finally {
            // Physical detach.
            Element elem = w.getElement();
            DOM.removeChild(DOM.getParent(elem), elem);

            // Logical detach.
            getChildren().remove(w);
        }
        return true;
    }

    /**
     * Removes all buttons.<p>
     */
    public void removeAllButtons() {

        m_buttonPanel.clear();
    }

    /**
     * Removes the given button widget from the button panel.<p>
     * 
     * @param button the button widget to remove
     */
    public void removeButton(Widget button) {

        m_buttonPanel.remove(button);
        if (m_buttonPanel.getWidgetCount() == 0) {
            m_buttonPanel.setStyleName(I_CmsLayoutBundle.INSTANCE.dialogCss().hideButtonPanel());
        }
    }

    /**
     * Removes the padding from the popup's content.<p>
     */
    public void removePadding() {

        m_main.removeClassName(I_CmsLayoutBundle.INSTANCE.dialogCss().contentPadding());
        m_contentHeightCorrection = -6;
    }

    /**
     * Sets the popup's content background.<p>
     * 
     * @param color the color to set
     */
    public void setBackgroundColor(String color) {

        m_main.getStyle().setBackgroundColor(color);
    }

    /**
     * Sets the captions text.<p>
     * 
     * @param caption the text to set
     */
    public void setCaption(String caption) {

        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(caption)) {
            getElement().removeClassName(I_CmsLayoutBundle.INSTANCE.dialogCss().hideCaption());
            m_caption.setText(caption);
        } else {
            getElement().addClassName(I_CmsLayoutBundle.INSTANCE.dialogCss().hideCaption());
        }
    }

    /**
     * Sets the height for the popup content.<p>
     * 
     * @param height the height in pixels
     */
    public void setHeight(int height) {

        if (height <= 0) {
            m_containerElement.getStyle().clearWidth();
        } else {
            int contentHeight = height;
            if (hasCaption()) {
                contentHeight = contentHeight - 36;
            }
            if (hasButtons()) {
                contentHeight = contentHeight - 34;
            }
            contentHeight = contentHeight - m_contentHeightCorrection;
            m_containerElement.getStyle().setProperty("height", height + Unit.PX.toString());
            m_main.getStyle().setProperty("height", contentHeight + Unit.PX.toString());
        }
    }

    /**
     * @see com.google.gwt.user.client.ui.PopupPanel#setHeight(java.lang.String)
     */
    @Override
    @Deprecated
    public void setHeight(String height) {

        throw new UnsupportedOperationException();
    }

    /**
     * Replaces the content from the main widget.<p>
     * 
     * @param w the widget that should replace the main content
     */
    public void setMainContent(Widget w) {

        clear();
        add(w);
    }

    /**
     * @see com.google.gwt.user.client.ui.UIObject#setSize(java.lang.String, java.lang.String)
     */
    @Override
    @Deprecated
    public void setPixelSize(int width, int height) {

        throw new UnsupportedOperationException();
    }

    /**
     * Sets the popup's dialog position to 'fixed'.<p>
     */
    public void setPositionFixed() {

        getElement().getStyle().setPosition(Position.FIXED);
    }

    /**
     * @see com.google.gwt.user.client.ui.UIObject#setSize(java.lang.String, java.lang.String)
     */
    @Override
    @Deprecated
    public void setSize(String width, String height) {

        throw new UnsupportedOperationException();
    }

    /**
     * Sets the use animation flag.<p>
     * 
     * @param use <code>true</code> if the animation should be used, default is <code>true</code>
     */
    public void setUseAnimation(boolean use) {

        m_useAnimation = use;
    }

    /**
     * Unsupported operation.<p>
     * 
     * @see com.google.gwt.user.client.ui.PopupPanel#setWidget(com.google.gwt.user.client.ui.Widget)
     */
    @Override
    @Deprecated
    public void setWidget(Widget w) {

        throw new UnsupportedOperationException();
    }

    /**
     * Sets the width for the popup content.<p>
     * 
     * @param width the width in pixels
     */
    public void setWidth(int width) {

        if (width <= 0) {
            m_containerElement.getStyle().clearWidth();
        } else {
            m_containerElement.getStyle().setProperty("width", width + Unit.PX.toString());
        }
    }

    /**
     * @see com.google.gwt.user.client.ui.PopupPanel#setWidth(java.lang.String)
     */
    @Override
    @Deprecated
    public void setWidth(String width) {

        throw new UnsupportedOperationException();
    }

    /**
     * @see com.google.gwt.user.client.ui.PopupPanel#show()
     */
    @Override
    public void show() {

        super.show();
        if (m_useAnimation) {
            CmsFadeAnimation.fadeIn(getElement(), null, 500);
        }
        if (m_resizeHandlerRegistration == null) {
            m_resizeHandlerRegistration = Window.addResizeHandler(new ResizeHandler() {

                public void onResize(ResizeEvent event) {

                    m_windowWidth = event.getWidth();
                }
            });
        }
    }

    /**
     * Adds a new child widget to the panel, attaching its Element to the
     * specified container Element.
     * 
     * @param child the child widget to be added
     * @param container the element within which the child will be contained
     */
    protected void add(Widget child, Element container) {

        // Detach new child.
        child.removeFromParent();

        // Logical attach.
        getChildren().add(child);

        // Physical attach.
        DOM.appendChild(container, child.getElement());

        // Adopt.
        adopt(child);
    }

    /**
     * Adjusts beforeIndex to account for the possibility that the given widget is
     * already a child of this panel.
     * 
     * @param child the widget that might be an existing child
     * @param beforeIndex the index at which it will be added to this panel
     * @return the modified index
     */
    protected int adjustIndex(Widget child, int beforeIndex) {

        checkIndexBoundsForInsertion(beforeIndex);

        // Check to see if this widget is already a direct child.
        if (child.getParent() == this) {
            // If the Widget's previous position was left of the desired new position
            // shift the desired position left to reflect the removal
            int idx = getWidgetIndex(child);
            if (idx < beforeIndex) {
                beforeIndex--;
            }
        }

        return beforeIndex;
    }

    /**
     * Called on mouse down in the caption area, begins the dragging loop by
     * turning on event capture.
     * 
     * @see DOM#setCapture
     * @see #continueDragging
     * @param event the mouse down event that triggered dragging
     */
    protected void beginDragging(MouseDownEvent event) {

        m_dragging = true;
        m_windowWidth = Window.getClientWidth();
        m_clientLeft = Document.get().getBodyOffsetLeft();
        m_clientTop = Document.get().getBodyOffsetTop();
        DOM.setCapture(getElement());
        m_dragStartX = event.getX();
        m_dragStartY = event.getY();
        addStyleName(I_CmsLayoutBundle.INSTANCE.dialogCss().dragging());
    }

    /**
     * Checks that <code>index</code> is in the range [0, getWidgetCount()), which
     * is the valid range on accessible indexes.
     * 
     * @param index the index being accessed
     */
    protected void checkIndexBoundsForAccess(int index) {

        if ((index < 0) || (index >= getWidgetCount())) {
            throw new IndexOutOfBoundsException();
        }
    }

    /**
     * Checks that <code>index</code> is in the range [0, getWidgetCount()], which
     * is the valid range for indexes on an insertion.
     * 
     * @param index the index where insertion will occur
     */
    protected void checkIndexBoundsForInsertion(int index) {

        if ((index < 0) || (index > getWidgetCount())) {
            throw new IndexOutOfBoundsException();
        }
    }

    /**
     * Called on mouse move in the caption area, continues dragging if it was
     * started by {@link #beginDragging}.
     * 
     * @see #beginDragging
     * @see #endDragging
     * @param event the mouse move event that continues dragging
     */
    protected void continueDragging(MouseMoveEvent event) {

        if (m_dragging) {
            int absX = event.getX() + getAbsoluteLeft();
            int absY = event.getY() + getAbsoluteTop();

            // if the mouse is off the screen to the left, right, or top, don't
            // move the dialog box. This would let users lose dialog boxes, which
            // would be bad for modal popups.
            if ((absX < m_clientLeft) || (absX >= m_windowWidth) || (absY < m_clientTop)) {
                return;
            }

            setPopupPosition(absX - m_dragStartX, absY - m_dragStartY);
        }
    }

    /**
     * @see com.google.gwt.user.client.ui.Panel#doAttachChildren()
     */
    @Override
    protected void doAttachChildren() {

        try {
            super.doAttachChildren();
        } finally {
            // See comment in doDetachChildren for an explanation of this call
            m_caption.onAttach();
            m_buttonPanel.onAttach();
            if (m_close != null) {
                m_close.onAttach();
            }
        }
    }

    /**
     * @see com.google.gwt.user.client.ui.Panel#doDetachChildren()
     */
    @Override
    protected void doDetachChildren() {

        try {
            super.doDetachChildren();
        } finally {
            // We need to detach the caption specifically because it is not part of the
            // iterator of Widgets that the {@link SimplePanel} super class returns.
            // This is similar to a {@link ComplexPanel}, but we do not want to expose
            // the caption widget, as its just an internal implementation.
            m_caption.onDetach();
            m_buttonPanel.onDetach();
            if (m_close != null) {
                m_close.onDetach();
            }
        }
    }

    /**
     * Called on mouse up in the caption area, ends dragging by ending event
     * capture.
     * 
     * @param event the mouse up event that ended dragging
     * 
     * @see DOM#releaseCapture
     * @see #beginDragging
     * @see #endDragging
     */
    protected void endDragging(MouseUpEvent event) {

        m_dragging = false;
        DOM.releaseCapture(getElement());
        removeStyleName(I_CmsLayoutBundle.INSTANCE.dialogCss().dragging());
    }

    /**
     * Gets the list of children contained in this panel.
     * 
     * @return a collection of child widgets
     */
    protected WidgetCollection getChildren() {

        return m_children;
    }

    /**
     * @see com.google.gwt.user.client.ui.PopupPanel#getContainerElement()
     */
    @Override
    protected com.google.gwt.user.client.Element getContainerElement() {

        if (m_containerElement == null) {
            m_containerElement = super.getContainerElement();
        }
        return m_containerElement;
    }

    /**
     * Insert a new child Widget into this Panel at a specified index, attaching
     * its Element to the specified container Element. The child Element will
     * either be attached to the container at the same index, or simply appended
     * to the container, depending on the value of <code>domInsert</code>.
     * 
     * @param child the child Widget to be added
     * @param container the Element within which <code>child</code> will be
     *          contained
     * @param beforeIndex the index before which <code>child</code> will be
     *          inserted
     * @param domInsert if <code>true</code>, insert <code>child</code> into
     *          <code>container</code> at <code>beforeIndex</code>; otherwise
     *          append <code>child</code> to the end of <code>container</code>.
     */
    protected void insert(Widget child, Element container, int beforeIndex, boolean domInsert) {

        // Validate index; adjust if the widget is already a child of this panel.
        beforeIndex = adjustIndex(child, beforeIndex);

        // Detach new child.
        child.removeFromParent();

        // Logical attach.
        getChildren().insert(child, beforeIndex);

        // Physical attach.
        if (domInsert) {
            DOM.insertChild(container, child.getElement(), beforeIndex);
        } else {
            DOM.appendChild(container, child.getElement());
        }

        // Adopt.
        adopt(child);
    }

    /**
     * Override to work around the glass overlay still showing after dialog hide.<p>
     * 
     * @see com.google.gwt.user.client.ui.Widget#onDetach()
     */
    @Override
    protected void onDetach() {

        super.onDetach();
        if (getGlassElement() != null) {
            getGlassElement().removeFromParent();
        }
    }

    /**
     * @see com.google.gwt.user.client.ui.PopupPanel#onPreviewNativeEvent(com.google.gwt.user.client.Event.NativePreviewEvent)
     */
    @Override
    protected void onPreviewNativeEvent(NativePreviewEvent event) {

        // We need to preventDefault() on mouseDown events (outside of the
        // DialogBox content) to keep text from being selected when it
        // is dragged.
        NativeEvent nativeEvent = event.getNativeEvent();

        if (!event.isCanceled() && (event.getTypeInt() == Event.ONMOUSEDOWN) && isCaptionEvent(nativeEvent)) {
            nativeEvent.preventDefault();
        }
        super.onPreviewNativeEvent(event);
    }

    /**
     * Appends the arrow element to the popup's dialog.<p>
     * 
     * @param arrow the arrow element to add
     */
    protected void showArrow(Element arrow) {

        getElement().appendChild(arrow);
    }

    /**
     * Returns <code>true</code> if this popup has buttons <code>false</code> otherwise.<p>
     * 
     * @return <code>true</code> if this popup has buttons <code>false</code> otherwise
     */
    private boolean hasButtons() {

        return m_buttonPanel.getWidgetCount() != 0;
    }

    /**
     * Checks if the target of the given event is the caption or a child of the caption.<p>
     * 
     * @param event the event to check
     * 
     * @return <code>true</code> if the target of the given event is the caption <code>false</code> otherwise
     */
    private boolean isCaptionEvent(NativeEvent event) {

        EventTarget target = event.getEventTarget();
        if (com.google.gwt.dom.client.Element.is(target)) {
            return m_caption.getElement().isOrHasChild(com.google.gwt.dom.client.Element.as(target));
        }
        return false;
    }

    /**
     * Returns the maximum available height inside the popup.<p>
     * 
     * @param fixedContentHeight fixed content height to deduct from the available height
     * 
     * @return the maximum available height
     */
    public int getAvailableHeight(int fixedContentHeight) {

        if (m_buttonPanel.isVisible()) {
            fixedContentHeight += m_buttonPanel.getOffsetHeight();
        }
        return Window.getClientHeight() - 150 - fixedContentHeight;
    }
}