001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com)
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * For further information about Alkacon Software, please see the
018 * company website: http://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: http://www.opencms.org
022 *
023 * You should have received a copy of the GNU Lesser General Public
024 * License along with this library; if not, write to the Free Software
025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
026 */
027
028package org.opencms.gwt.client.ui;
029
030import org.opencms.gwt.client.Messages;
031import org.opencms.gwt.client.ui.I_CmsButton.ButtonStyle;
032import org.opencms.gwt.client.ui.css.I_CmsLayoutBundle;
033import org.opencms.gwt.client.util.CmsFadeAnimation;
034import org.opencms.util.CmsStringUtil;
035
036import java.util.Iterator;
037import java.util.List;
038
039import com.google.common.collect.Lists;
040import com.google.gwt.core.client.GWT;
041import com.google.gwt.dom.client.Document;
042import com.google.gwt.dom.client.Element;
043import com.google.gwt.dom.client.EventTarget;
044import com.google.gwt.dom.client.NativeEvent;
045import com.google.gwt.dom.client.Style.Position;
046import com.google.gwt.dom.client.Style.Unit;
047import com.google.gwt.event.dom.client.ClickEvent;
048import com.google.gwt.event.dom.client.ClickHandler;
049import com.google.gwt.event.dom.client.MouseDownEvent;
050import com.google.gwt.event.dom.client.MouseDownHandler;
051import com.google.gwt.event.dom.client.MouseMoveEvent;
052import com.google.gwt.event.dom.client.MouseMoveHandler;
053import com.google.gwt.event.dom.client.MouseUpEvent;
054import com.google.gwt.event.dom.client.MouseUpHandler;
055import com.google.gwt.event.logical.shared.CloseEvent;
056import com.google.gwt.event.logical.shared.CloseHandler;
057import com.google.gwt.event.logical.shared.ResizeEvent;
058import com.google.gwt.event.logical.shared.ResizeHandler;
059import com.google.gwt.event.logical.shared.ValueChangeEvent;
060import com.google.gwt.event.logical.shared.ValueChangeHandler;
061import com.google.gwt.event.shared.HandlerRegistration;
062import com.google.gwt.user.client.Command;
063import com.google.gwt.user.client.DOM;
064import com.google.gwt.user.client.Event;
065import com.google.gwt.user.client.Event.NativePreviewEvent;
066import com.google.gwt.user.client.History;
067import com.google.gwt.user.client.Window;
068import com.google.gwt.user.client.ui.FlowPanel;
069import com.google.gwt.user.client.ui.HTML;
070import com.google.gwt.user.client.ui.IsWidget;
071import com.google.gwt.user.client.ui.PopupPanel;
072import com.google.gwt.user.client.ui.SimplePanel;
073import com.google.gwt.user.client.ui.Widget;
074import com.google.gwt.user.client.ui.WidgetCollection;
075
076/**
077 * Provides a pop up dialog base.
078 *
079 * @since 8.0.0
080 */
081public class CmsPopup extends PopupPanel implements I_CmsAutoHider {
082
083    /**
084     * Handles fragment changes by closing the active popups.<p>
085     *
086     * Only used for GWT dialogs opened from Vaadin.
087     */
088    public static class HistoryHandler implements ValueChangeHandler<String> {
089
090        /** The list of active popups. */
091        private List<CmsPopup> m_popups = Lists.newArrayList();
092
093        /**
094         * Adds a popup to the list of active popups.<p>
095         *
096         * @param popup the popup
097         */
098        public void addPopup(CmsPopup popup) {
099
100            m_popups.add(popup);
101        }
102
103        /**
104         * @see com.google.gwt.event.logical.shared.ValueChangeHandler#onValueChange(com.google.gwt.event.logical.shared.ValueChangeEvent)
105         */
106        public void onValueChange(ValueChangeEvent<String> event) {
107
108            if (GWT.getModuleName().equals("org.opencms.ui.WidgetSet")) {
109                // only do this when popup is opened from Vaadin
110                // (copying the list to avoid ConcurrentModificationExceptions)
111                for (CmsPopup popup : Lists.newArrayList(m_popups)) {
112                    try {
113                        if (popup.isAttached()) {
114                            popup.hide();
115                        }
116                    } catch (Exception e) {
117                        // ignore
118                    }
119                }
120            }
121            m_popups.clear();
122        }
123
124        /**
125         * Removes a popup from the list of active popups.<p>
126         *
127         * @param popup the popup to remove
128         */
129        public void removePopup(CmsPopup popup) {
130
131            m_popups.remove(popup);
132        }
133
134    }
135
136    /**
137     * The dialog button panel.<p>
138     */
139    private class ButtonPanel extends FlowPanel {
140
141        /**
142         * Default constructor.<p>
143         */
144        protected ButtonPanel() {
145
146            // nothing to do
147        }
148
149        /**
150         * Making function visible.<p>
151         *
152         * @see com.google.gwt.user.client.ui.Widget#onAttach()
153         */
154        @Override
155        protected void onAttach() {
156
157            super.onAttach();
158        }
159
160        /**
161         * Making function visible.<p>
162         *
163         * @see com.google.gwt.user.client.ui.Widget#onDetach()
164         */
165        @Override
166        protected void onDetach() {
167
168            super.onDetach();
169        }
170    }
171
172    /**
173     * The dialog caption.<p>
174     */
175    private class Caption extends HTML {
176
177        /**
178         * Default constructor.<p>
179         */
180        protected Caption() {
181
182            // nothing to do
183        }
184
185        /**
186         * Making function visible.<p>
187         *
188         * @see com.google.gwt.user.client.ui.Widget#onAttach()
189         */
190        @Override
191        protected void onAttach() {
192
193            super.onAttach();
194        }
195
196        /**
197         * Making function visible.<p>
198         *
199         * @see com.google.gwt.user.client.ui.Widget#onDetach()
200         */
201        @Override
202        protected void onDetach() {
203
204            super.onDetach();
205        }
206    }
207
208    /**
209     * The dialog close button.<p>
210     */
211    private class CloseButton extends CmsPushButton {
212
213        /**
214         * Default constructor.<p>
215         */
216        protected CloseButton() {
217
218            // nothing to do
219        }
220
221        /**
222         * Making function visible.<p>
223         *
224         * @see com.google.gwt.user.client.ui.Widget#onAttach()
225         */
226        @Override
227        protected void onAttach() {
228
229            super.onAttach();
230        }
231
232        /**
233         * Making function visible.<p>
234         *
235         * @see com.google.gwt.user.client.ui.Widget#onDetach()
236         */
237        @Override
238        protected void onDetach() {
239
240            super.onDetach();
241        }
242    }
243
244    /**
245     * The dialog mouse handler.<p>
246     */
247    private class MouseHandler implements MouseDownHandler, MouseUpHandler, MouseMoveHandler {
248
249        /**
250         * Default constructor.<p>
251         */
252        protected MouseHandler() {
253
254            // nothing to do
255        }
256
257        /**
258         * @see com.google.gwt.event.dom.client.MouseDownHandler#onMouseDown(com.google.gwt.event.dom.client.MouseDownEvent)
259         */
260        public void onMouseDown(MouseDownEvent event) {
261
262            beginDragging(event);
263        }
264
265        /**
266         * @see com.google.gwt.event.dom.client.MouseMoveHandler#onMouseMove(com.google.gwt.event.dom.client.MouseMoveEvent)
267         */
268        public void onMouseMove(MouseMoveEvent event) {
269
270            continueDragging(event);
271        }
272
273        /**
274         * @see com.google.gwt.event.dom.client.MouseUpHandler#onMouseUp(com.google.gwt.event.dom.client.MouseUpEvent)
275         */
276        public void onMouseUp(MouseUpEvent event) {
277
278            endDragging(event);
279        }
280    }
281
282    /** The default width of this dialog. */
283    public static final int DEFAULT_WIDTH = 600;
284
285    /** The wide dialog width. */
286    public static final int WIDE_WIDTH = 800;
287
288    /** The history handler used to remove popups when the fragment is changed. */
289    private static HistoryHandler m_historyHandler;
290
291    /** The close command. */
292    protected Command m_closeCommand;
293
294    /** Flag which indicates whether the notification widget has already been installed. */
295    protected boolean m_notificationWidgetInstalled;
296
297    /** The window width. */
298    protected int m_windowWidth;
299
300    /** The panel holding the dialog's buttons. */
301    private ButtonPanel m_buttonPanel;
302
303    /** The dialog caption. */
304    private Caption m_caption;
305
306    /** Flag indicating if the dialog should catch all notifications while visible. */
307    private boolean m_catchNotifications;
308
309    /** The child widgets. */
310    private WidgetCollection m_children;
311
312    /** Body offset left. */
313    private int m_clientLeft;
314
315    /** Body offset top. */
316    private int m_clientTop;
317
318    /** The panel for the close button. */
319    private CloseButton m_close;
320
321    /** The dialog closing handler registration used for organizing notifications. */
322    private HandlerRegistration m_closingHandlerRegistration;
323
324    /** The popup container element. */
325    private Element m_containerElement;
326
327    /** The content height correction, used when explicitly setting the dialog height. */
328    private int m_contentHeightCorrection = 6;
329
330    /** Flag if dragging. */
331    private boolean m_dragging;
332
333    /** Drag starting x position. */
334    private int m_dragStartX;
335
336    /** Drag starting y position. */
337    private int m_dragStartY;
338
339    /** The main widget of this dialog containing all others. */
340    private Element m_main;
341
342    /** The own notification widget. */
343    private CmsNotificationWidget m_ownNotificationWidget;
344
345    /** The parent notification widget. */
346    private I_CmsNotificationWidget m_parentNotificationWidget;
347
348    /** The resize handler registration .*/
349    private HandlerRegistration m_resizeHandlerRegistration;
350
351    /** Signals whether a animation should be used to show the popup or not. */
352    private boolean m_useAnimation = true;
353
354    /** The content width, -1 indicating the value was not set. */
355    private int m_width = -1;
356
357    /**
358     * Constructor.<p>
359     */
360    public CmsPopup() {
361
362        this(DEFAULT_WIDTH);
363    }
364
365    /**
366     * Constructor setting the width of the dialog.<p>
367     *
368     * @param width the width to set
369     */
370    public CmsPopup(int width) {
371
372        super(false, true);
373        // super(autoHide, modal);
374
375        m_containerElement = super.getContainerElement();
376        setStyleName(I_CmsLayoutBundle.INSTANCE.dialogCss().popup());
377        addStyleName(I_CmsLayoutBundle.INSTANCE.generalCss().opencms());
378        m_containerElement.setClassName(I_CmsLayoutBundle.INSTANCE.dialogCss().popupContent());
379        setGlassStyleName(
380            I_CmsLayoutBundle.INSTANCE.dialogCss().popupOverlay()
381                + " "
382                + I_CmsLayoutBundle.INSTANCE.generalCss().opencms());
383        Element dragOverlay = DOM.createDiv();
384        dragOverlay.setClassName(I_CmsLayoutBundle.INSTANCE.dialogCss().dragOverlay());
385        getElement().insertFirst(dragOverlay);
386
387        m_caption = new Caption();
388        m_caption.setStyleName(I_CmsLayoutBundle.INSTANCE.dialogCss().caption());
389        // Add the caption to the top of the popup-panel. We need to
390        // logically adopt the caption so we can catch mouse events.
391        DOM.appendChild(m_containerElement, m_caption.getElement());
392        adopt(m_caption);
393        m_children = new WidgetCollection(this);
394        m_main = DOM.createDiv();
395        m_main.addClassName(I_CmsLayoutBundle.INSTANCE.dialogCss().popupMainContent());
396        m_main.addClassName(I_CmsLayoutBundle.INSTANCE.dialogCss().contentPadding());
397        DOM.appendChild(m_containerElement, m_main);
398        m_buttonPanel = new ButtonPanel();
399        m_buttonPanel.setStyleName(I_CmsLayoutBundle.INSTANCE.dialogCss().hideButtonPanel());
400        // Add the caption to the top of the popup-panel. We need to
401        // logically adopt the caption so we can catch mouse events.
402        DOM.appendChild(m_containerElement, m_buttonPanel.getElement());
403        adopt(m_buttonPanel);
404
405        MouseHandler mouseHandler = new MouseHandler();
406        addDomHandler(mouseHandler, MouseDownEvent.getType());
407        addDomHandler(mouseHandler, MouseUpEvent.getType());
408        addDomHandler(mouseHandler, MouseMoveEvent.getType());
409
410        setWidth(width);
411        getElement().addClassName(I_CmsLayoutBundle.INSTANCE.dialogCss().hideCaption());
412
413        if (m_historyHandler == null) {
414            m_historyHandler = new HistoryHandler();
415            History.addValueChangeHandler(m_historyHandler);
416        }
417        m_historyHandler.addPopup(this);
418    }
419
420    /**
421     * Constructor setting the dialog caption.<p>
422     *
423     * @param caption the caption to set
424     */
425    public CmsPopup(String caption) {
426
427        this();
428        setCaption(caption);
429    }
430
431    /**
432     * Constructor setting caption and width.<p>
433     *
434     * @param caption the caption to set
435     * @param width the width to set
436     */
437    public CmsPopup(String caption, int width) {
438
439        this(width);
440        setCaption(caption);
441    }
442
443    /**
444     * The constructor.<p>
445     *
446     * @param title the title and heading of the dialog
447     * @param content the content widget
448     */
449    public CmsPopup(String title, Widget content) {
450
451        this(title);
452        setMainContent(content);
453    }
454
455    /**
456     * Wraps the given Widget with a cornered border, padding and margin.<p>
457     *
458     * @param w the widget to wrap
459     *
460     * @return a new widget that wraps the given one
461     */
462    public static Widget wrapWithBorderPadding(Widget w) {
463
464        w.addStyleName(I_CmsLayoutBundle.INSTANCE.dialogCss().borderPadding());
465        w.addStyleName(I_CmsLayoutBundle.INSTANCE.generalCss().cornerAll());
466        SimplePanel panel = new SimplePanel();
467        panel.add(w);
468        return panel;
469    }
470
471    /**
472     * Adds the given child widget.<p>
473     *
474     * @param w the widget
475     */
476    @Override
477    public void add(Widget w) {
478
479        add(w, m_main);
480    }
481
482    /**
483     * Adds a button widget to the button panel.<p>
484     *
485     * @param button the button widget
486     */
487    public void addButton(Widget button) {
488
489        addButton(button, 0);
490    }
491
492    /**
493     * Adds a button widget to the button panel before the given position.<p>
494     *
495     * @param button the button widget
496     * @param position the position to insert the button
497     */
498    public void addButton(Widget button, int position) {
499
500        m_buttonPanel.insert(button, position);
501        m_buttonPanel.setStyleName(I_CmsLayoutBundle.INSTANCE.dialogCss().popupButtonPanel());
502    }
503
504    /**
505     * Adds a close "button" to the top of the popup.<p>
506     *
507     * @param cmd the command that should be executed when the close button is clicked
508     */
509    public void addDialogClose(final Command cmd) {
510
511        m_closeCommand = cmd;
512        if (m_close == null) {
513            m_close = new CloseButton();
514            m_close.setTitle(Messages.get().key(Messages.GUI_CLOSE_0));
515            m_close.addStyleName(I_CmsLayoutBundle.INSTANCE.dialogCss().closePopup());
516            m_close.setImageClass(I_CmsLayoutBundle.INSTANCE.dialogCss().closePopupImage());
517            m_close.setButtonStyle(ButtonStyle.TRANSPARENT, null);
518            m_close.addClickHandler(new ClickHandler() {
519
520                /**
521                 * @see com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt.event.dom.client.ClickEvent)
522                 */
523                public void onClick(ClickEvent event) {
524
525                    boolean cancelled = false;
526                    try {
527                        if (m_closeCommand != null) {
528                            m_closeCommand.execute();
529                        }
530                    } catch (CmsCancelCloseException e) {
531                        cancelled = true;
532                    } finally {
533                        if (!cancelled) {
534                            hide();
535                        }
536                    }
537                }
538            });
539            DOM.appendChild(m_containerElement, m_close.getElement());
540            adopt(m_close);
541        }
542
543    }
544
545    /**
546     * Replaces current notification widget by an overlay.<p>
547     */
548    public void catchNotifications() {
549
550        m_catchNotifications = true;
551        if (isShowing()) {
552            installNotificationWidget();
553        }
554        if (m_closingHandlerRegistration == null) {
555            // when closing the dialog
556            m_closingHandlerRegistration = addCloseHandler(new CloseHandler<PopupPanel>() {
557
558                /**
559                 * @see CloseHandler#onClose(CloseEvent)
560                 */
561                public void onClose(CloseEvent<PopupPanel> event) {
562
563                    clearNotifications();
564                }
565            });
566        }
567    }
568
569    /**
570     * @see com.google.gwt.user.client.ui.PopupPanel#center()
571     */
572    @Override
573    public void center() {
574
575        show();
576        if (Position.FIXED.getCssName().equals(getElement().getStyle().getPosition())) {
577            // keep position fixed, as may have been set to absolute
578            setPositionFixed();
579            int left = (Window.getClientWidth() - getOffsetWidth()) >> 1;
580            int top = (Window.getClientHeight() - getOffsetHeight()) >> 1;
581            setPopupPosition(Math.max(left, 0), Math.max(top, 0));
582        } else {
583            super.center();
584        }
585    }
586
587    /**
588     * Shows the dialog and centers it horizontally, but positions it at a fixed vertical position.<p>
589     *
590     * @param top the top position
591     */
592    public void centerHorizontally(int top) {
593
594        if (Position.FIXED.getCssName().equals(getElement().getStyle().getPosition())) {
595            show();
596            // keep position fixed, as may have been set to absolute
597            setPositionFixed();
598            int left = (Window.getClientWidth() - getOffsetWidth()) >> 1;
599            setPopupPosition(Math.max(left, 0), Math.max(top, 0));
600        } else {
601            show();
602            int left = (Window.getClientWidth() - getOffsetWidth()) >> 1;
603            setPopupPosition(Math.max(Window.getScrollLeft() + left, 0), Math.max(Window.getScrollTop() + top, 0));
604        }
605    }
606
607    /**
608     * @see com.google.gwt.user.client.ui.Panel#clear()
609     */
610    @Override
611    public void clear() {
612
613        for (Widget w : this) {
614            // Orphan.
615            try {
616                orphan(w);
617            } finally {
618                // Physical detach.
619                Element elem = w.getElement();
620                elem.removeFromParent();
621            }
622        }
623        m_children = new WidgetCollection(this);
624    }
625
626    /**
627     * Returns the maximum available height inside the popup.<p>
628     *
629     * @param fixedContentHeight fixed content height to deduct from the available height
630     *
631     * @return the maximum available height
632     */
633    public int getAvailableHeight(int fixedContentHeight) {
634
635        if (m_buttonPanel.isVisible()) {
636            fixedContentHeight += m_buttonPanel.getOffsetHeight();
637        }
638        return Window.getClientHeight() - 150 - fixedContentHeight;
639    }
640
641    /**
642     * Returns the dialog caption text.<p>
643     *
644     * @return the dialog caption
645     */
646    public String getCaption() {
647
648        return m_caption.getText();
649    }
650
651    /**
652     * Returns the child widget with the given index.<p>
653     *
654     * @param index the index
655     *
656     * @return the child widget
657     */
658    public Widget getWidget(int index) {
659
660        return getChildren().get(index);
661    }
662
663    /**
664     * Returns the number of child widgets.<p>
665     *
666     * @return the number of child widgets
667     */
668    public int getWidgetCount() {
669
670        return getChildren().size();
671    }
672
673    /**
674     * Returns the index of the given widget.<p>
675     *
676     * @param child the child widget
677     *
678     * @return the index of the child widget
679     */
680    public int getWidgetIndex(IsWidget child) {
681
682        return getWidgetIndex(asWidgetOrNull(child));
683    }
684
685    /**
686     * Returns the index of the given child widget.<p>
687     *
688     * @param child the child widget
689     *
690     * @return the index
691     */
692    public int getWidgetIndex(Widget child) {
693
694        return getChildren().indexOf(child);
695    }
696
697    /**
698     * Returns the dialog content width, -1 if not set.<p>
699     *
700     * @return the dialog content width
701     */
702    public int getWidth() {
703
704        return m_width;
705    }
706
707    /**
708     * Returns <code>true</code> if a caption is set for this popup <code>false</code> otherwise.<p>
709     *
710     * @return <code>true</code> if a caption is set for this popup <code>false</code> otherwise
711     */
712    public boolean hasCaption() {
713
714        return CmsStringUtil.isNotEmptyOrWhitespaceOnly(m_caption.getText());
715    }
716
717    /**
718     * @see com.google.gwt.user.client.ui.PopupPanel#hide()
719     */
720    @Override
721    public void hide() {
722
723        if (m_resizeHandlerRegistration != null) {
724            m_resizeHandlerRegistration.removeHandler();
725            m_resizeHandlerRegistration = null;
726        }
727        super.hide();
728    }
729
730    /**
731     * @see com.google.gwt.user.client.ui.PopupPanel#hide(boolean)
732     */
733    @Override
734    public void hide(boolean autoClosed) {
735
736        super.hide(autoClosed);
737        m_historyHandler.removePopup(this);
738    }
739
740    /**
741     * Inserts a child widget before the given index.<p>
742     *
743     * @param w the child widget
744     * @param beforeIndex the index
745     *
746     * @throws IndexOutOfBoundsException if the index is out of bounds
747     */
748    public void insert(Widget w, int beforeIndex) throws IndexOutOfBoundsException {
749
750        insert(w, m_main, beforeIndex, true);
751    }
752
753    /**
754     * Inserts a widget as the first widget in the popup.<p>
755     *
756     * @param widget the widget to insert
757     */
758    public void insertFront(Widget widget) {
759
760        insert(widget, 0);
761    }
762
763    /**
764     * @see com.google.gwt.user.client.ui.SimplePanel#iterator()
765     */
766    @Override
767    public Iterator<Widget> iterator() {
768
769        return getChildren().iterator();
770    }
771
772    /**
773     * @see com.google.gwt.user.client.ui.Widget#onBrowserEvent(com.google.gwt.user.client.Event)
774     */
775    @Override
776    public void onBrowserEvent(Event event) {
777
778        // If we're not yet dragging, only trigger mouse events if the event occurs
779        // in the caption wrapper
780        switch (event.getTypeInt()) {
781            case Event.ONMOUSEDOWN:
782            case Event.ONMOUSEUP:
783            case Event.ONMOUSEMOVE:
784            case Event.ONMOUSEOVER:
785            case Event.ONMOUSEOUT:
786                if (!m_dragging && !isCaptionEvent(event)) {
787                    return;
788                }
789                break;
790            default:
791        }
792
793        super.onBrowserEvent(event);
794    }
795
796    /**
797     * Removes a child widget.<p>
798     *
799     * @param index the index of the widget to remove
800     *
801     * @return <code>true</code> if the there was a widget at the given index to remove
802     */
803    public boolean remove(int index) {
804
805        Widget w = getWidget(index);
806        if (w != null) {
807            return remove(getWidget(index));
808        }
809        return false;
810    }
811
812    /**
813     * @see com.google.gwt.user.client.ui.SimplePanel#remove(com.google.gwt.user.client.ui.Widget)
814     */
815    @Override
816    public boolean remove(Widget w) {
817
818        // Validate.
819        if (w.getParent() != this) {
820            return false;
821        }
822        // Orphan.
823        try {
824            orphan(w);
825        } finally {
826            // Physical detach.
827            Element elem = w.getElement();
828            elem.removeFromParent();
829
830            // Logical detach.
831            getChildren().remove(w);
832        }
833        return true;
834    }
835
836    /**
837     * Removes all buttons.<p>
838     */
839    public void removeAllButtons() {
840
841        m_buttonPanel.clear();
842    }
843
844    /**
845     * Removes the given button widget from the button panel.<p>
846     *
847     * @param button the button widget to remove
848     */
849    public void removeButton(Widget button) {
850
851        m_buttonPanel.remove(button);
852        if (m_buttonPanel.getWidgetCount() == 0) {
853            m_buttonPanel.setStyleName(I_CmsLayoutBundle.INSTANCE.dialogCss().hideButtonPanel());
854        }
855    }
856
857    /**
858     * Removes the padding from the popup's content.<p>
859     */
860    public void removePadding() {
861
862        m_main.removeClassName(I_CmsLayoutBundle.INSTANCE.dialogCss().contentPadding());
863        m_contentHeightCorrection = -6;
864    }
865
866    /**
867     * Sets the popup's content background.<p>
868     *
869     * @param color the color to set
870     */
871    public void setBackgroundColor(String color) {
872
873        m_main.getStyle().setBackgroundColor(color);
874    }
875
876    /**
877     * Sets the captions text.<p>
878     *
879     * @param caption the text to set
880     */
881    public void setCaption(String caption) {
882
883        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(caption)) {
884            getElement().removeClassName(I_CmsLayoutBundle.INSTANCE.dialogCss().hideCaption());
885            m_caption.setText(caption);
886        } else {
887            getElement().addClassName(I_CmsLayoutBundle.INSTANCE.dialogCss().hideCaption());
888        }
889    }
890
891    /**
892     * Sets the height for the popup content.<p>
893     *
894     * @param height the height in pixels
895     */
896    public void setHeight(int height) {
897
898        if (height <= 0) {
899            m_containerElement.getStyle().clearWidth();
900            m_main.getStyle().clearHeight();
901        } else {
902            int contentHeight = height - 6;
903            if (hasCaption()) {
904                contentHeight = contentHeight - 36;
905            }
906            if (hasButtons()) {
907                contentHeight = contentHeight - 34;
908            }
909            contentHeight = contentHeight - m_contentHeightCorrection;
910            m_main.getStyle().setHeight(contentHeight, Unit.PX);
911        }
912    }
913
914    /**
915     * @see com.google.gwt.user.client.ui.PopupPanel#setHeight(java.lang.String)
916     */
917    @Override
918    @Deprecated
919    public void setHeight(String height) {
920
921        throw new UnsupportedOperationException();
922    }
923
924    /**
925     * Replaces the content from the main widget.<p>
926     *
927     * @param w the widget that should replace the main content
928     */
929    public void setMainContent(Widget w) {
930
931        clear();
932        add(w);
933    }
934
935    /**
936     * @see com.google.gwt.user.client.ui.UIObject#setSize(java.lang.String, java.lang.String)
937     */
938    @Override
939    @Deprecated
940    public void setPixelSize(int width, int height) {
941
942        throw new UnsupportedOperationException();
943    }
944
945    /**
946     * Sets the popup's dialog position to 'fixed'.<p>
947     */
948    public void setPositionFixed() {
949
950        getElement().getStyle().setPosition(Position.FIXED);
951    }
952
953    /**
954     * @see com.google.gwt.user.client.ui.UIObject#setSize(java.lang.String, java.lang.String)
955     */
956    @Override
957    @Deprecated
958    public void setSize(String width, String height) {
959
960        throw new UnsupportedOperationException();
961    }
962
963    /**
964     * Sets an additional CSS class to the main content element.<p>
965     *
966     * @param cssClassName the CSS class to set
967     */
968    public void setSpecialBackgroundClass(String cssClassName) {
969
970        m_main.addClassName(cssClassName);
971    }
972
973    /**
974     * Sets the use animation flag.<p>
975     *
976     * @param use <code>true</code> if the animation should be used, default is <code>true</code>
977     */
978    public void setUseAnimation(boolean use) {
979
980        m_useAnimation = use;
981    }
982
983    /**
984     * Unsupported operation.<p>
985     *
986     * @see com.google.gwt.user.client.ui.PopupPanel#setWidget(com.google.gwt.user.client.ui.Widget)
987     */
988    @Override
989    @Deprecated
990    public void setWidget(Widget w) {
991
992        throw new UnsupportedOperationException();
993    }
994
995    /**
996     * Sets the width for the popup content.<p>
997     *
998     * @param width the width in pixels
999     */
1000    public void setWidth(int width) {
1001
1002        if (width <= 0) {
1003            m_containerElement.getStyle().clearWidth();
1004            m_width = -1;
1005        } else {
1006            m_containerElement.getStyle().setWidth(width, Unit.PX);
1007            m_width = width;
1008        }
1009    }
1010
1011    /**
1012     * @see com.google.gwt.user.client.ui.PopupPanel#setWidth(java.lang.String)
1013     */
1014    @Override
1015    @Deprecated
1016    public void setWidth(String width) {
1017
1018        throw new UnsupportedOperationException();
1019    }
1020
1021    /**
1022     * @see com.google.gwt.user.client.ui.PopupPanel#show()
1023     */
1024    @Override
1025    public void show() {
1026
1027        boolean fixed = Position.FIXED.getCssName().equals(getElement().getStyle().getPosition());
1028        boolean wasAlreadyShowing = isShowing();
1029        super.show();
1030        if (fixed) {
1031            // keep position fixed as it may have been set to absolute
1032            setPositionFixed();
1033        }
1034        if (m_useAnimation && !wasAlreadyShowing) {
1035            CmsFadeAnimation.fadeIn(getElement(), null, 250);
1036        }
1037        if (m_resizeHandlerRegistration == null) {
1038            m_resizeHandlerRegistration = Window.addResizeHandler(new ResizeHandler() {
1039
1040                public void onResize(ResizeEvent event) {
1041
1042                    m_windowWidth = event.getWidth();
1043                }
1044            });
1045        }
1046        if (m_catchNotifications) {
1047            catchNotifications();
1048        }
1049    }
1050
1051    /**
1052     * Adds a new child widget to the panel, attaching its Element to the
1053     * specified container Element.
1054     *
1055     * @param child the child widget to be added
1056     * @param container the element within which the child will be contained
1057     */
1058    protected void add(Widget child, Element container) {
1059
1060        // Detach new child.
1061        child.removeFromParent();
1062
1063        // Logical attach.
1064        getChildren().add(child);
1065
1066        // Physical attach.
1067        DOM.appendChild(container, child.getElement());
1068
1069        // Adopt.
1070        adopt(child);
1071    }
1072
1073    /**
1074     * Adjusts beforeIndex to account for the possibility that the given widget is
1075     * already a child of this panel.
1076     *
1077     * @param child the widget that might be an existing child
1078     * @param beforeIndex the index at which it will be added to this panel
1079     * @return the modified index
1080     */
1081    protected int adjustIndex(Widget child, int beforeIndex) {
1082
1083        checkIndexBoundsForInsertion(beforeIndex);
1084
1085        // Check to see if this widget is already a direct child.
1086        if (child.getParent() == this) {
1087            // If the Widget's previous position was left of the desired new position
1088            // shift the desired position left to reflect the removal
1089            int idx = getWidgetIndex(child);
1090            if (idx < beforeIndex) {
1091                beforeIndex--;
1092            }
1093        }
1094
1095        return beforeIndex;
1096    }
1097
1098    /**
1099     * Called on mouse down in the caption area, begins the dragging loop by
1100     * turning on event capture.
1101     *
1102     * @see DOM#setCapture
1103     * @see #continueDragging
1104     * @param event the mouse down event that triggered dragging
1105     */
1106    protected void beginDragging(MouseDownEvent event) {
1107
1108        m_dragging = true;
1109        m_windowWidth = Window.getClientWidth();
1110        m_clientLeft = Document.get().getBodyOffsetLeft();
1111        m_clientTop = Document.get().getBodyOffsetTop();
1112        DOM.setCapture(getElement());
1113        m_dragStartX = event.getX();
1114        m_dragStartY = event.getY();
1115        addStyleName(I_CmsLayoutBundle.INSTANCE.dialogCss().dragging());
1116    }
1117
1118    /**
1119     * Checks that <code>index</code> is in the range [0, getWidgetCount()), which
1120     * is the valid range on accessible indexes.
1121     *
1122     * @param index the index being accessed
1123     */
1124    protected void checkIndexBoundsForAccess(int index) {
1125
1126        if ((index < 0) || (index >= getWidgetCount())) {
1127            throw new IndexOutOfBoundsException();
1128        }
1129    }
1130
1131    /**
1132     * Checks that <code>index</code> is in the range [0, getWidgetCount()], which
1133     * is the valid range for indexes on an insertion.
1134     *
1135     * @param index the index where insertion will occur
1136     */
1137    protected void checkIndexBoundsForInsertion(int index) {
1138
1139        if ((index < 0) || (index > getWidgetCount())) {
1140            throw new IndexOutOfBoundsException();
1141        }
1142    }
1143
1144    /**
1145     * Called on mouse move in the caption area, continues dragging if it was
1146     * started by {@link #beginDragging}.
1147     *
1148     * @see #beginDragging
1149     * @see #endDragging
1150     * @param event the mouse move event that continues dragging
1151     */
1152    protected void continueDragging(MouseMoveEvent event) {
1153
1154        if (m_dragging) {
1155            int absX = event.getX() + getAbsoluteLeft();
1156            int absY = event.getY() + getAbsoluteTop();
1157
1158            // if the mouse is off the screen to the left, right, or top, don't
1159            // move the dialog box. This would let users lose dialog boxes, which
1160            // would be bad for modal popups.
1161            if ((absX < m_clientLeft) || (absX >= m_windowWidth) || (absY < m_clientTop)) {
1162                return;
1163            }
1164
1165            setPopupPosition(absX - m_dragStartX, absY - m_dragStartY);
1166        }
1167    }
1168
1169    /**
1170     * Creates a new notification widget for this dialog.<p>
1171     *
1172     * @return the notification widget for this dialog
1173     */
1174    protected CmsNotificationWidget createDialogNotificationWidget() {
1175
1176        return new CmsNotificationWidget();
1177    }
1178
1179    /**
1180     * @see com.google.gwt.user.client.ui.Panel#doAttachChildren()
1181     */
1182    @Override
1183    protected void doAttachChildren() {
1184
1185        try {
1186            super.doAttachChildren();
1187        } finally {
1188            // See comment in doDetachChildren for an explanation of this call
1189            m_caption.onAttach();
1190            m_buttonPanel.onAttach();
1191            if (m_close != null) {
1192                m_close.onAttach();
1193            }
1194        }
1195    }
1196
1197    /**
1198     * @see com.google.gwt.user.client.ui.Panel#doDetachChildren()
1199     */
1200    @Override
1201    protected void doDetachChildren() {
1202
1203        try {
1204            super.doDetachChildren();
1205        } finally {
1206            // We need to detach the caption specifically because it is not part of the
1207            // iterator of Widgets that the {@link SimplePanel} super class returns.
1208            // This is similar to a {@link ComplexPanel}, but we do not want to expose
1209            // the caption widget, as its just an internal implementation.
1210            m_caption.onDetach();
1211            m_buttonPanel.onDetach();
1212            if (m_close != null) {
1213                m_close.onDetach();
1214            }
1215        }
1216    }
1217
1218    /**
1219     * Called on mouse up in the caption area, ends dragging by ending event
1220     * capture.
1221     *
1222     * @param event the mouse up event that ended dragging
1223     *
1224     * @see DOM#releaseCapture
1225     * @see #beginDragging
1226     * @see #endDragging
1227     */
1228    protected void endDragging(MouseUpEvent event) {
1229
1230        m_dragging = false;
1231        DOM.releaseCapture(getElement());
1232        removeStyleName(I_CmsLayoutBundle.INSTANCE.dialogCss().dragging());
1233    }
1234
1235    /**
1236     * Gets the list of children contained in this panel.
1237     *
1238     * @return a collection of child widgets
1239     */
1240    protected WidgetCollection getChildren() {
1241
1242        return m_children;
1243    }
1244
1245    /**
1246     * @see com.google.gwt.user.client.ui.PopupPanel#getContainerElement()
1247     */
1248    @SuppressWarnings("deprecation")
1249    @Override
1250    protected com.google.gwt.user.client.Element getContainerElement() {
1251
1252        if (m_containerElement == null) {
1253            m_containerElement = super.getContainerElement();
1254        }
1255        return (com.google.gwt.user.client.Element)m_containerElement;
1256    }
1257
1258    /**
1259     * Insert a new child Widget into this Panel at a specified index, attaching
1260     * its Element to the specified container Element. The child Element will
1261     * either be attached to the container at the same index, or simply appended
1262     * to the container, depending on the value of <code>domInsert</code>.
1263     *
1264     * @param child the child Widget to be added
1265     * @param container the Element within which <code>child</code> will be
1266     *          contained
1267     * @param beforeIndex the index before which <code>child</code> will be
1268     *          inserted
1269     * @param domInsert if <code>true</code>, insert <code>child</code> into
1270     *          <code>container</code> at <code>beforeIndex</code>; otherwise
1271     *          append <code>child</code> to the end of <code>container</code>.
1272     */
1273    protected void insert(Widget child, Element container, int beforeIndex, boolean domInsert) {
1274
1275        // Validate index; adjust if the widget is already a child of this panel.
1276        beforeIndex = adjustIndex(child, beforeIndex);
1277
1278        // Detach new child.
1279        child.removeFromParent();
1280
1281        // Logical attach.
1282        getChildren().insert(child, beforeIndex);
1283
1284        // Physical attach.
1285        if (domInsert) {
1286            DOM.insertChild(container, child.getElement(), beforeIndex);
1287        } else {
1288            DOM.appendChild(container, child.getElement());
1289        }
1290
1291        // Adopt.
1292        adopt(child);
1293    }
1294
1295    /**
1296     * Sets the notification widget.<p>
1297     */
1298    protected void installNotificationWidget() {
1299
1300        if (m_notificationWidgetInstalled) {
1301            return;
1302        }
1303        // remember current notification widget
1304        m_parentNotificationWidget = CmsNotification.get().getWidget();
1305        // create our own notification overlay
1306        if (m_ownNotificationWidget == null) {
1307            m_ownNotificationWidget = createDialogNotificationWidget();
1308        }
1309        add(m_ownNotificationWidget);
1310        CmsNotification.get().setWidget(m_ownNotificationWidget);
1311        m_notificationWidgetInstalled = true;
1312    }
1313
1314    /**
1315     * Override to work around the glass overlay still showing after dialog hide.<p>
1316     *
1317     * @see com.google.gwt.user.client.ui.Widget#onDetach()
1318     */
1319    @Override
1320    protected void onDetach() {
1321
1322        super.onDetach();
1323        if (getGlassElement() != null) {
1324            getGlassElement().removeFromParent();
1325        }
1326    }
1327
1328    /**
1329     * @see com.google.gwt.user.client.ui.PopupPanel#onPreviewNativeEvent(com.google.gwt.user.client.Event.NativePreviewEvent)
1330     */
1331    @Override
1332    protected void onPreviewNativeEvent(NativePreviewEvent event) {
1333
1334        NativeEvent nativeEvent = event.getNativeEvent();
1335        int eventType = event.getTypeInt();
1336
1337        // We need to preventDefault() on mouseDown events (outside of the
1338        // DialogBox content) to keep text from being selected when it
1339        // is dragged.
1340
1341        if (!event.isCanceled() && (eventType == Event.ONMOUSEDOWN) && isCaptionEvent(nativeEvent)) {
1342            nativeEvent.preventDefault();
1343        }
1344
1345        super.onPreviewNativeEvent(event);
1346    }
1347
1348    /**
1349     * Appends the arrow element to the popup's dialog.<p>
1350     *
1351     * @param arrow the arrow element to add
1352     */
1353    protected void showArrow(Element arrow) {
1354
1355        getElement().appendChild(arrow);
1356    }
1357
1358    /**
1359     * Resets the notification to the parent notification widget and detaches the own notification widget.<p>
1360     */
1361    void clearNotifications() {
1362
1363        if (m_parentNotificationWidget != null) {
1364            // restore the previous notification widget
1365            CmsNotification.get().setWidget(m_parentNotificationWidget);
1366        }
1367        if (m_ownNotificationWidget != null) {
1368            // remove the overlay notification widget
1369            remove(m_ownNotificationWidget);
1370        }
1371    }
1372
1373    /**
1374     * Returns <code>true</code> if this popup has buttons <code>false</code> otherwise.<p>
1375     *
1376     * @return <code>true</code> if this popup has buttons <code>false</code> otherwise
1377     */
1378    private boolean hasButtons() {
1379
1380        return m_buttonPanel.getWidgetCount() != 0;
1381    }
1382
1383    /**
1384     * Checks if the target of the given event is the caption or a child of the caption.<p>
1385     *
1386     * @param event the event to check
1387     *
1388     * @return <code>true</code> if the target of the given event is the caption <code>false</code> otherwise
1389     */
1390    private boolean isCaptionEvent(NativeEvent event) {
1391
1392        EventTarget target = event.getEventTarget();
1393        if (com.google.gwt.dom.client.Element.is(target)) {
1394            return m_caption.getElement().isOrHasChild(com.google.gwt.dom.client.Element.as(target));
1395        }
1396        return false;
1397    }
1398}