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.ade.containerpage.client.ui;
029
030import org.opencms.ade.containerpage.client.CmsContainerpageDNDController;
031import org.opencms.ade.containerpage.client.ui.css.I_CmsLayoutBundle;
032import org.opencms.ade.containerpage.shared.CmsContainer;
033import org.opencms.gwt.client.dnd.CmsDNDHandler.Orientation;
034import org.opencms.gwt.client.dnd.I_CmsDraggable;
035import org.opencms.gwt.client.dnd.I_CmsDropTarget;
036import org.opencms.gwt.client.ui.CmsHighlightingBorder;
037import org.opencms.gwt.client.util.CmsDebugLog;
038import org.opencms.gwt.client.util.CmsDomUtil;
039import org.opencms.gwt.client.util.CmsDomUtil.Style;
040import org.opencms.gwt.client.util.CmsPositionBean;
041import org.opencms.gwt.shared.CmsGwtConstants;
042import org.opencms.gwt.shared.CmsTemplateContextInfo;
043import org.opencms.util.CmsStringUtil;
044
045import java.util.ArrayList;
046import java.util.HashMap;
047import java.util.Iterator;
048import java.util.List;
049import java.util.Map;
050
051import com.google.gwt.dom.client.Element;
052import com.google.gwt.dom.client.Node;
053import com.google.gwt.dom.client.NodeList;
054import com.google.gwt.dom.client.Style.Display;
055import com.google.gwt.dom.client.Style.Position;
056import com.google.gwt.user.client.ui.ComplexPanel;
057import com.google.gwt.user.client.ui.RootPanel;
058import com.google.gwt.user.client.ui.Widget;
059
060/**
061 * Container page container.<p>
062 *
063 *
064 *
065 * @since 8.0.0
066 */
067public class CmsContainerPageContainer extends ComplexPanel implements I_CmsDropContainer {
068
069    /**
070     * Helper class for resizing containers in the drag/drop process when an element is dropped into them that is of lower height than the empty container HTML.
071     */
072    public static class ContainerResizeHelper {
073
074        /** The container. */
075        private CmsContainerPageContainer m_container;
076
077        /** Minimum height. */
078        private int m_origHeight;
079
080        /**
081         * Creates a new instance.
082         *
083         * @param container the container
084         */
085        public ContainerResizeHelper(CmsContainerPageContainer container) {
086
087            m_container = container;
088
089        }
090
091        /**
092         * Initializes the minimum height.
093         */
094        public void initHeight() {
095
096            int h = m_container.getElement().getOffsetHeight();
097            m_origHeight = h;
098        }
099
100        /**
101         * Called when the placeholder is shown.
102         *
103         * @param placeholder the placeholder
104         */
105        public void onShowPlaceholder(Element placeholder) {
106
107            int curHeight = m_container.getElement().getOffsetHeight();
108            if (curHeight < m_origHeight) {
109                // We want an artificial element to 'blow up' the container to its original size,
110                // but adding a normal element would interfere with drag and drop, so we use the ::after pseudoelement
111                // of the container. We can't just set its style directly, so we modify a stylesheet with
112                // a fixed ID to set the height.
113                m_container.getElement().addClassName(CmsGwtConstants.CLASS_CONTAINER_INFLATED);
114                String styleText = "." + CmsGwtConstants.CLASS_CONTAINER_INFLATED + "::after { ";
115                styleText += "height: " + (m_origHeight - curHeight) + "px;";
116                styleText += " } ";
117                CmsDomUtil.setStylesheetText("oc-dnd-inflated-container-style", styleText);
118            }
119        }
120
121        /**
122         * Resets the style changes.
123         */
124        public void reset() {
125
126            m_container.getElement().removeClassName(CmsGwtConstants.CLASS_CONTAINER_INFLATED);
127        }
128    }
129
130    /**
131     * Element position info class.<p>
132     */
133    protected class ElementPositionInfo {
134
135        /** The DOM element. */
136        private Element m_element;
137
138        /** The element position bean. */
139        private CmsPositionBean m_elementPosition;
140
141        /** The float CSS property. */
142        private String m_float;
143
144        /** Flag indicating the element is positioned absolute. */
145        private boolean m_isAbsolute;
146
147        /** Flag indicating if the given element is visible. */
148        private boolean m_isVisible;
149
150        /**
151         * Constructor.<p>
152         *
153         * @param element the DOM element
154         */
155        public ElementPositionInfo(Element element) {
156
157            m_element = element;
158            String positioning = CmsDomUtil.getCurrentStyle(m_element, Style.position);
159            m_isAbsolute = Position.ABSOLUTE.getCssName().equals(positioning)
160                || Position.FIXED.getCssName().equals(positioning);
161
162            if (!m_isAbsolute) {
163                m_isVisible = !Display.NONE.getCssName().equals(m_element.getStyle().getDisplay());
164                if (m_isVisible) {
165                    m_elementPosition = CmsPositionBean.getBoundingClientRect(element);
166                    m_float = CmsDomUtil.getCurrentStyle(m_element, Style.floatCss);
167                }
168            }
169        }
170
171        /**
172         * Returns the DOM element.<p>
173         *
174         * @return the DOM element
175         */
176        public Element getElement() {
177
178            return m_element;
179        }
180
181        /**
182         * Returns the element position bean.<p>
183         *
184         * @return the element position bean
185         */
186        public CmsPositionBean getElementPosition() {
187
188            return m_elementPosition;
189        }
190
191        /**
192         * Returns the x distance of the cursor to the element left.<p>
193         *
194         * @param x the cursor x position
195         * @param documentScrollLeft the document scroll left position
196         *
197         * @return the y distance of the cursor to the element top
198         */
199        public int getRelativeLeft(int x, int documentScrollLeft) {
200
201            return (x + documentScrollLeft) - m_elementPosition.getLeft();
202        }
203
204        /**
205         * Returns the y distance of the cursor to the element top.<p>
206         *
207         * @param y the cursor y position
208         * @param documentScrollTop the document scroll top position
209         *
210         * @return the y distance of the cursor to the element top
211         */
212        public int getRelativeTop(int y, int documentScrollTop) {
213
214            return (y + documentScrollTop) - m_elementPosition.getTop();
215        }
216
217        /**
218         * Returns if the element is positioned absolute.<p>
219         *
220         * @return <code>true</code> if the element is positioned absolute
221         */
222        public boolean isAbsolute() {
223
224            return m_isAbsolute;
225        }
226
227        /**
228         * Returns if the element is floated.<p>
229         *
230         * @return <code>true</code> if the element is floated
231         */
232        public boolean isFloating() {
233
234            return isFloatLeft() || isFloatRight();
235        }
236
237        /**
238         * Returns if the element is floated to the left.<p>
239         *
240         * @return <code>true</code> if the element is floated to the left
241         */
242        public boolean isFloatLeft() {
243
244            return "left".equals(m_float);
245        }
246
247        /**
248         * Returns if the element is floated to the right.<p>
249         *
250         * @return <code>true</code> if the element is floated to the right
251         */
252        public boolean isFloatRight() {
253
254            return "right".equals(m_float);
255        }
256
257        /**
258         * Returns if the given element is visible.<p>
259         *
260         * @return <code>true</code> if the given element is visible
261         */
262        public boolean isVisible() {
263
264            return m_isVisible;
265        }
266
267    }
268
269    /** Name of a special property for the container id. */
270    public static final String PROP_CONTAINER_MARKER = "opencmsContainerId";
271
272    /** Static variable for storing the container layout change helper for the current drag/drop process. */
273    private static ContainerResizeHelper RESIZE_HELPER;
274
275    /** The container data. */
276    private CmsContainer m_containerData;
277
278    /** The container level. */
279    private int m_containerLevel;
280
281    /** The list of nested sub containers that are also valid drop targets during the current drag and drop. */
282    private List<I_CmsDropTarget> m_dnDChildren;
283
284    /** The element position info cache. */
285    private List<ElementPositionInfo> m_elementPositions;
286
287    /** The element to display in case the container is empty. */
288    private Element m_emptyContainerElement;
289
290    /** Highlighting border for this container. */
291    private CmsHighlightingBorder m_highlighting;
292
293    /** The overflowing element. */
294    private Widget m_overflowingElement;
295
296    /** The cached highlighting position. */
297    private CmsPositionBean m_ownPosition;
298
299    /** The drag and drop placeholder. */
300    private Element m_placeholder;
301
302    /** The drag and drop placeholder position index. */
303    private int m_placeholderIndex = -1;
304
305    /** Flag indicating the current place holder visibility. */
306    private boolean m_placeholderVisible;
307
308    /** Flag indicating the element positions need to be re-evaluated. */
309    private boolean m_requiresPositionUpdate = true;
310
311    /**
312     * Constructor.<p>
313     *
314     * @param containerData the container data
315     * @param element the container element
316     */
317    public CmsContainerPageContainer(CmsContainer containerData, Element element) {
318
319        setElement(element);
320
321        if (!containerData.isSubContainer()) {
322            RootPanel.detachOnWindowClose(this);
323        }
324        m_containerData = containerData;
325        element.setPropertyString(PROP_CONTAINER_MARKER, containerData.getName());
326        if (m_containerData.isEditable()) {
327            addStyleName(I_CmsLayoutBundle.INSTANCE.dragdropCss().dragTarget());
328        }
329        onAttach();
330    }
331
332    /**
333     * Clears the static layout change object, resetting it if it's not null.
334     */
335    public static void clearResizeHelper() {
336
337        if (RESIZE_HELPER != null) {
338            try {
339                RESIZE_HELPER.reset();
340            } finally {
341                RESIZE_HELPER = null;
342            }
343        }
344    }
345
346    /**
347     * Measures the height of the container's element.
348     *
349     * This sets the overflow-y style property to auto to prevent margin collapsing.
350     *
351     * @param elem the element
352     * @return the height
353     */
354    public static int measureHeight(Element elem) {
355
356        Map<String, String> props = new HashMap<>();
357        props.put("overflowY", "auto");
358        Map<String, String> old = CmsDomUtil.updateStyle(elem.getStyle(), props);
359        int result = elem.getOffsetHeight();
360        CmsDomUtil.updateStyle(elem.getStyle(), old);
361        return result;
362
363    }
364
365    /**
366     * Creates a new layout helper for resizing containers.<p>
367     *
368     * The previously created layout changes object (if any) will be reset.
369     *
370     * @param container the container
371     * @return the new layout helper
372     */
373    public static ContainerResizeHelper newResizeHelper(CmsContainerPageContainer container) {
374
375        clearResizeHelper();
376        RESIZE_HELPER = new ContainerResizeHelper(container);
377        return RESIZE_HELPER;
378    }
379
380    /**
381     * @see com.google.gwt.user.client.ui.Panel#add(com.google.gwt.user.client.ui.Widget)
382     */
383    @Override
384    public void add(Widget w) {
385
386        add(w, (Element)getElement());
387    }
388
389    /**
390     * @see org.opencms.ade.containerpage.client.ui.I_CmsDropContainer#addDndChild(org.opencms.gwt.client.dnd.I_CmsDropTarget)
391     */
392    public void addDndChild(I_CmsDropTarget child) {
393
394        if (m_dnDChildren == null) {
395            m_dnDChildren = new ArrayList<I_CmsDropTarget>();
396        }
397        m_dnDChildren.add(child);
398    }
399
400    /**
401     * @see org.opencms.ade.containerpage.client.ui.I_CmsDropContainer#adoptElement(org.opencms.ade.containerpage.client.ui.CmsContainerPageElementPanel)
402     */
403    public void adoptElement(CmsContainerPageElementPanel containerElement) {
404
405        assert getElement().equals(containerElement.getElement().getParentElement());
406        getChildren().add(containerElement);
407        adopt(containerElement);
408    }
409
410    /**
411     * Check if the empty container content should be displayed or removed.<p>
412     */
413    public void checkEmptyContainers() {
414
415        if (getWidgetCount() == 0) {
416            if (m_emptyContainerElement != null) {
417                m_emptyContainerElement.getStyle().clearDisplay();
418            } else if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(m_containerData.getEmptyContainerContent())) {
419                // add empty container element
420                try {
421                    m_emptyContainerElement = CmsDomUtil.createElement(m_containerData.getEmptyContainerContent());
422                    getElement().appendChild(m_emptyContainerElement);
423                } catch (Exception e) {
424                    CmsDebugLog.getInstance().printLine(e.getMessage());
425                }
426            }
427        } else if (m_emptyContainerElement != null) {
428            m_emptyContainerElement.removeFromParent();
429            m_emptyContainerElement = null;
430        }
431    }
432
433    /**
434     * @see org.opencms.ade.containerpage.client.ui.I_CmsDropContainer#checkMaxElementsOnEnter()
435     */
436    public void checkMaxElementsOnEnter() {
437
438        int count = getWidgetCount();
439        if (count >= m_containerData.getMaxElements()) {
440            Widget overflowElement = null;
441            int index = 0;
442            for (Widget widget : this) {
443                boolean isDummy = widget.getStyleName().contains(CmsTemplateContextInfo.DUMMY_ELEMENT_MARKER);
444                if (!isDummy) {
445                    index++;
446                    if (index >= m_containerData.getMaxElements()) {
447                        if (overflowElement == null) {
448                            overflowElement = widget;
449                        }
450                    }
451                }
452            }
453            if (overflowElement != null) {
454                m_overflowingElement = overflowElement;
455                m_overflowingElement.removeFromParent();
456            }
457        }
458        if (count == 0) {
459            if (m_emptyContainerElement != null) {
460                m_emptyContainerElement.getStyle().setDisplay(Display.NONE);
461            }
462        }
463    }
464
465    /**
466     * @see org.opencms.ade.containerpage.client.ui.I_CmsDropContainer#checkMaxElementsOnLeave()
467     */
468    public void checkMaxElementsOnLeave() {
469
470        if (m_overflowingElement != null) {
471            add(m_overflowingElement);
472        }
473        if (m_emptyContainerElement != null) {
474            m_emptyContainerElement.getStyle().clearDisplay();
475        }
476    }
477
478    /**
479     * @see org.opencms.gwt.client.dnd.I_CmsDropTarget#checkPosition(int, int, Orientation)
480     */
481    public boolean checkPosition(int x, int y, Orientation orientation) {
482
483        if (m_ownPosition != null) {
484            // ignore orientation
485            int scrollTop = getElement().getOwnerDocument().getScrollTop();
486            // use cached position
487            int relativeTop = (y + scrollTop) - m_ownPosition.getTop();
488            if ((relativeTop > 0) && (m_ownPosition.getHeight() > relativeTop)) {
489                // cursor is inside the height of the element, check horizontal position
490                int scrollLeft = getElement().getOwnerDocument().getScrollLeft();
491                int relativeLeft = (x + scrollLeft) - m_ownPosition.getLeft();
492                return (relativeLeft > 0) && (m_ownPosition.getWidth() > relativeLeft);
493            }
494        }
495        return false;
496    }
497
498    /**
499     * @see org.opencms.ade.containerpage.client.ui.I_CmsDropContainer#clearDnDChildren()
500     */
501    public void clearDnDChildren() {
502
503        if (m_dnDChildren != null) {
504            m_dnDChildren.clear();
505        }
506    }
507
508    /**
509     * Returns all contained drag elements.<p>
510     *
511     * @return the drag elements
512     */
513    public List<CmsContainerPageElementPanel> getAllDragElements() {
514
515        List<CmsContainerPageElementPanel> elements = new ArrayList<CmsContainerPageElementPanel>();
516        Iterator<Widget> it = iterator();
517        while (it.hasNext()) {
518            Widget w = it.next();
519            if (w instanceof CmsContainerPageElementPanel) {
520                elements.add((CmsContainerPageElementPanel)w);
521            } else {
522                if (CmsDomUtil.hasClass(
523                    org.opencms.ade.containerpage.client.ui.css.I_CmsLayoutBundle.INSTANCE.containerpageCss().groupcontainerPlaceholder(),
524                    w.getElement())) {
525                    CmsDebugLog.getInstance().printLine("Ignoring group container placeholder.");
526                } else {
527                    CmsDebugLog.getInstance().printLine(
528                        "WARNING: " + w.toString() + " is no instance of CmsDragContainerElement");
529                }
530            }
531        }
532        return elements;
533    }
534
535    /**
536     * Returns the configured width for this container.<p>
537     *
538     * @return the configured width
539     */
540    public int getConfiguredWidth() {
541
542        return m_containerData.getWidth();
543    }
544
545    /**
546     * Returns the container id.<p>
547     *
548     * @return the container id
549     */
550    public String getContainerId() {
551
552        return m_containerData.getName();
553    }
554
555    /**
556     * Returns the container level.<p>
557     *
558     * @return the container level
559     */
560    public int getContainerLevel() {
561
562        return m_containerLevel;
563    }
564
565    /**
566     * Returns the container type.<p>
567     *
568     * @return the container type
569     */
570    public String getContainerType() {
571
572        return m_containerData.getType();
573    }
574
575    /**
576     * In case of a former copy model, and a max elements setting of one, the id of the overflowing element is returned.<p>
577     *
578     * @return the overflowing element id or <code>null</code>
579     */
580    public String getCopyModelReplaceId() {
581
582        String result = null;
583        if ((m_containerData.getMaxElements() == 1)
584            && (m_overflowingElement != null)
585            && (m_overflowingElement instanceof CmsContainerPageElementPanel)
586            && (getFormerModelGroupParent() != null)) {
587            result = ((CmsContainerPageElementPanel)m_overflowingElement).getId();
588        }
589        return result;
590    }
591
592    /**
593     * @see org.opencms.gwt.client.dnd.I_CmsNestedDropTarget#getDnDChildren()
594     */
595    public List<I_CmsDropTarget> getDnDChildren() {
596
597        return m_dnDChildren;
598    }
599
600    /**
601     * Returns whether this container has a model group parent.<p>
602     *
603     * @return <code>true</code> if this container has a model group parent
604     */
605    public Element getFormerModelGroupParent() {
606
607        Element result = null;
608        Element parent = getElement().getParentElement();
609        while (parent != null) {
610            if (parent.getPropertyBoolean(CmsContainerPageElementPanel.PROP_WAS_MODEL_GROUP)) {
611                result = parent;
612                break;
613            }
614            parent = parent.getParentElement();
615        }
616        return result;
617    }
618
619    /**
620     * Returns the parent container id.<p>
621     *
622     * @return the container parent id
623     */
624    public String getParentContainerId() {
625
626        return m_containerData.getParentContainerName();
627    }
628
629    /**
630     * @see org.opencms.gwt.client.dnd.I_CmsDropTarget#getPlaceholderIndex()
631     */
632    public int getPlaceholderIndex() {
633
634        return m_placeholderIndex;
635    }
636
637    /**
638     * @see org.opencms.ade.containerpage.client.ui.I_CmsDropContainer#getPositionInfo()
639     */
640    public CmsPositionBean getPositionInfo() {
641
642        return m_ownPosition;
643    }
644
645    /**
646     * Returns the settings presets.<p>
647     *
648     * @return the presets
649     */
650    public Map<String, String> getSettingPresets() {
651
652        return m_containerData.getSettingPresets();
653    }
654
655    /**
656     * @see org.opencms.gwt.client.dnd.I_CmsNestedDropTarget#hasDnDChildren()
657     */
658    public boolean hasDnDChildren() {
659
660        return (m_dnDChildren != null) && !m_dnDChildren.isEmpty();
661    }
662
663    /**
664     * Returns whether this container has a model group parent.<p>
665     *
666     * @return <code>true</code> if this container has a model group parent
667     */
668    public boolean hasModelGroupParent() {
669
670        boolean result = false;
671        Element parent = getElement();
672        while (parent != null) {
673            if (parent.getPropertyBoolean(CmsContainerPageElementPanel.PROP_IS_MODEL_GROUP)) {
674                result = true;
675                break;
676            }
677            parent = parent.getParentElement();
678        }
679        return result;
680    }
681
682    /**
683     * @see org.opencms.ade.containerpage.client.ui.I_CmsDropContainer#hideEditableListButtons()
684     */
685    public void hideEditableListButtons() {
686
687        Iterator<Widget> it = iterator();
688        while (it.hasNext()) {
689            Widget child = it.next();
690            if (child instanceof CmsContainerPageElementPanel) {
691                ((CmsContainerPageElementPanel)child).hideEditableListButtons();
692            }
693        }
694    }
695
696    /**
697     * Puts a highlighting border around the container content.<p>
698     */
699    public void highlightContainer() {
700
701        highlightContainer(CmsPositionBean.getBoundingClientRect(getElement()));
702    }
703
704    /**
705     * @see org.opencms.ade.containerpage.client.ui.I_CmsDropContainer#highlightContainer(org.opencms.gwt.client.util.CmsPositionBean)
706     */
707    public void highlightContainer(CmsPositionBean positionInfo) {
708
709        // remove any remaining highlighting
710        if (m_highlighting != null) {
711            m_highlighting.removeFromParent();
712        }
713        // cache the position info, to be used during drag and drop
714        m_ownPosition = positionInfo;
715        m_highlighting = new CmsHighlightingBorder(
716            m_ownPosition,
717            CmsHighlightingBorder.BorderColor.red,
718            CmsContainerpageDNDController.HIGHLIGHTING_OFFSET);
719        RootPanel.get().add(m_highlighting);
720    }
721
722    /**
723     * @see org.opencms.ade.containerpage.client.ui.I_CmsDropContainer#insert(com.google.gwt.user.client.ui.Widget, int)
724     */
725    public void insert(Widget w, int beforeIndex) {
726
727        // in case an option bar as a direct child is present it may disturb the insert order
728        // it needs to be detached first
729        Element optionBar = null;
730        NodeList<Node> children = getElement().getChildNodes();
731        for (int i = 0; i < children.getLength(); i++) {
732            Node child = children.getItem(i);
733            if ((child.getNodeType() == Node.ELEMENT_NODE)
734                && ((Element)child).hasClassName(I_CmsLayoutBundle.INSTANCE.containerpageCss().optionBar())) {
735                optionBar = (Element)child;
736                optionBar.removeFromParent();
737                break;
738            }
739
740        }
741
742        insert(w, (Element)getElement(), beforeIndex, true);
743        if (optionBar != null) {
744            getElement().insertFirst(optionBar);
745        }
746    }
747
748    /**
749     * @see org.opencms.gwt.client.dnd.I_CmsDropTarget#insertPlaceholder(com.google.gwt.dom.client.Element, int, int, Orientation)
750     */
751    public void insertPlaceholder(Element placeholder, int x, int y, Orientation orientation) {
752
753        m_placeholder = placeholder;
754        m_placeholderVisible = false;
755        m_placeholder.getStyle().setDisplay(Display.NONE);
756        m_requiresPositionUpdate = true;
757        repositionPlaceholder(x, y, orientation);
758    }
759
760    /**
761     * @see org.opencms.ade.containerpage.client.ui.I_CmsDropContainer#isDetailOnly()
762     */
763    public boolean isDetailOnly() {
764
765        return m_containerData.isDetailOnly();
766    }
767
768    /**
769     * Returns true if this is a detail view container, being actually used for detail content.<p>
770     *
771     * @return true if this is a detail view container
772     */
773    public boolean isDetailView() {
774
775        return m_containerData.isDetailView();
776    }
777
778    /**
779     * Checks if this is a detail view container.
780     *
781     * @return true if this is a detail view container
782     */
783    public boolean isDetailViewContainer() {
784
785        return m_containerData.isDetailViewContainer();
786    }
787
788    /**
789     * @see org.opencms.ade.containerpage.client.ui.I_CmsDropContainer#isEditable()
790     */
791    public boolean isEditable() {
792
793        return m_containerData.isEditable();
794    }
795
796    /**
797     * Checks if the container is showing the empty container element.
798     *
799     * @return true if the empty container element is shown in the container
800     */
801    public boolean isShowingEmptyContainerElement() {
802
803        return (m_emptyContainerElement != null) && (m_emptyContainerElement.getParentElement() == getElement());
804    }
805
806    /**
807     * @see org.opencms.ade.containerpage.client.ui.I_CmsDropContainer#onConsumeChildren(java.util.List)
808     */
809    public void onConsumeChildren(List<CmsContainerPageElementPanel> children) {
810
811        // nothing to do
812    }
813
814    /**
815     * @see org.opencms.gwt.client.dnd.I_CmsDropTarget#onDrop(org.opencms.gwt.client.dnd.I_CmsDraggable)
816     */
817    public void onDrop(I_CmsDraggable draggable) {
818
819        m_overflowingElement = null;
820    }
821
822    /**
823     * Refreshes position and dimension of the highlighting border. Call when anything changed during the drag process.<p>
824     */
825    public void refreshHighlighting() {
826
827        if (m_highlighting != null) {
828            refreshHighlighting(CmsPositionBean.getBoundingClientRect(getElement()));
829        }
830    }
831
832    /**
833     * @see org.opencms.ade.containerpage.client.ui.I_CmsDropContainer#refreshHighlighting(org.opencms.gwt.client.util.CmsPositionBean)
834     */
835    public void refreshHighlighting(CmsPositionBean positionInfo) {
836
837        // cache the position info, to be used during drag and drop
838        m_ownPosition = positionInfo;
839        if (m_highlighting != null) {
840            m_highlighting.setPosition(m_ownPosition);
841        }
842    }
843
844    /**
845     * Removes the highlighting border.<p>
846     */
847    public void removeHighlighting() {
848
849        if (m_highlighting != null) {
850            m_highlighting.removeFromParent();
851            m_highlighting = null;
852        }
853    }
854
855    /**
856     * @see org.opencms.gwt.client.dnd.I_CmsDropTarget#removePlaceholder()
857     */
858    public void removePlaceholder() {
859
860        if (m_placeholder != null) {
861            m_placeholder.removeFromParent();
862            m_placeholder = null;
863        }
864        m_placeholderIndex = -1;
865        m_requiresPositionUpdate = true;
866
867        // check if the empty container content should be displayed or removed
868        checkEmptyContainers();
869    }
870
871    /**
872     * @see org.opencms.gwt.client.dnd.I_CmsDropTarget#repositionPlaceholder(int, int, Orientation)
873     */
874    public void repositionPlaceholder(int x, int y, Orientation orientation) {
875
876        if (m_requiresPositionUpdate) {
877            updatePositionsList();
878        }
879        int newPlaceholderIndex = internalRepositionPlaceholder(x, y);
880        m_requiresPositionUpdate = newPlaceholderIndex != m_placeholderIndex;
881        m_placeholderIndex = newPlaceholderIndex;
882    }
883
884    /**
885     * Sets the container level.<p>
886     *
887     * @param level the container level
888     */
889    public void setContainerLevel(int level) {
890
891        m_containerLevel = level;
892    }
893
894    /**
895     * Sets the empty container element.<p>
896     *
897     * @param emptyContainerElement the empty container element
898     */
899    public void setEmptyContainerElement(Element emptyContainerElement) {
900
901        m_emptyContainerElement = emptyContainerElement;
902    }
903
904    /**
905     * Measures the height of the container and sets its min-height to that value.
906     *
907     * @return a runnable used to undo the style changes
908     */
909    public Runnable setMinHeightToCurrentHeight() {
910
911        int h1 = measureHeight(getElement());
912        Map<String, String> props = new HashMap<>();
913        props.put("minHeight", h1 + "px");
914        props.put(
915            "background",
916            "repeating-linear-gradient(45deg, transparent, transparent 10px, rgba(240, 0, 242, 1) 10px, rgba(240, 0, 242, 1) 20px)");
917        com.google.gwt.dom.client.Style style = getElement().getStyle();
918        final Map<String, String> oldVals = CmsDomUtil.updateStyle(style, props);
919        return () -> {
920            CmsDomUtil.updateStyle(style, oldVals);
921        };
922    }
923
924    /**
925     * @see org.opencms.ade.containerpage.client.ui.I_CmsDropContainer#setPlaceholderVisibility(boolean)
926     */
927    public void setPlaceholderVisibility(boolean visible) {
928
929        if (m_placeholderVisible != visible) {
930            m_placeholderVisible = visible;
931            m_requiresPositionUpdate = true;
932            if (m_placeholderVisible) {
933                m_placeholder.getStyle().clearDisplay();
934                if (!m_placeholder.hasClassName(CmsGwtConstants.CLASS_PLACEHOLDER_TOO_BIG)) {
935                    int height = m_placeholder.getOffsetHeight();
936                    if (height > CmsGwtConstants.MAX_PLACEHOLDER_HEIGHT) {
937                        m_placeholder.addClassName(CmsGwtConstants.CLASS_PLACEHOLDER_TOO_BIG);
938                    }
939                }
940                if (RESIZE_HELPER != null) {
941                    RESIZE_HELPER.onShowPlaceholder(m_placeholder);
942                }
943            } else {
944                m_placeholder.getStyle().setDisplay(Display.NONE);
945            }
946        }
947    }
948
949    /**
950     * @see org.opencms.ade.containerpage.client.ui.I_CmsDropContainer#showEditableListButtons()
951     */
952    public void showEditableListButtons() {
953
954        for (Widget child : this) {
955            if (child instanceof CmsContainerPageElementPanel) {
956                ((CmsContainerPageElementPanel)child).showEditableListButtons();
957            }
958        }
959    }
960
961    /**
962     * Updates the option bar positions of the child elements.<p>
963     */
964    public void updateOptionBars() {
965
966        for (Widget widget : this) {
967            if (widget instanceof CmsContainerPageElementPanel) {
968                ((CmsContainerPageElementPanel)widget).updateOptionBarPosition();
969            }
970        }
971    }
972
973    /**
974     * @see org.opencms.ade.containerpage.client.ui.I_CmsDropContainer#updatePositionInfo()
975     */
976    public void updatePositionInfo() {
977
978        m_ownPosition = CmsPositionBean.getBoundingClientRect(getElement());
979    }
980
981    /**
982     * Repositions the drag and drop placeholder.<p>
983     *
984     * @param x the x cursor position
985     * @param y the y cursor position
986     *
987     * @return the placeholder position index
988     */
989    private int internalRepositionPlaceholder(int x, int y) {
990
991        int indexCorrection = 0;
992        int previousTop = 0;
993        int documentScrollTop = getElement().getOwnerDocument().getScrollTop();
994        int documentScrollLeft = getElement().getOwnerDocument().getScrollLeft();
995        for (int index = 0; index < m_elementPositions.size(); index++) {
996            ElementPositionInfo info = m_elementPositions.get(index);
997            if (info.getElement() == m_placeholder) {
998                indexCorrection = 1;
999            }
1000            if (info.isAbsolute() || !info.isVisible()) {
1001                continue;
1002            }
1003
1004            int top = info.getRelativeTop(y, documentScrollTop);
1005
1006            if ((top <= 0) || (top >= info.getElementPosition().getHeight())) {
1007                previousTop = top;
1008                continue;
1009            }
1010            int left = info.getRelativeLeft(x, documentScrollLeft);
1011            if ((left <= 0) || (left >= info.getElementPosition().getWidth())) {
1012                previousTop = top;
1013                continue;
1014            }
1015            boolean floatSort = info.isFloating() && (top != 0) && (top == previousTop);
1016            previousTop = top;
1017            if (info.getElement() != m_placeholder) {
1018                if (floatSort) {
1019                    boolean insertBefore = false;
1020                    if (left < (info.getElementPosition().getWidth() / 2)) {
1021                        if (info.isFloatLeft()) {
1022                            insertBefore = true;
1023                        }
1024                    } else if (info.isFloatRight()) {
1025                        insertBefore = true;
1026                    }
1027                    if (insertBefore) {
1028                        getElement().insertBefore(m_placeholder, info.getElement());
1029                        return index - indexCorrection;
1030                    } else {
1031                        getElement().insertAfter(m_placeholder, info.getElement());
1032                        return (index + 1) - indexCorrection;
1033                    }
1034                } else {
1035                    if (top < (info.getElementPosition().getHeight() / 2)) {
1036                        getElement().insertBefore(m_placeholder, info.getElement());
1037                        return index - indexCorrection;
1038                    } else {
1039                        getElement().insertAfter(m_placeholder, info.getElement());
1040                        return (index + 1) - indexCorrection;
1041                    }
1042                }
1043            } else {
1044                return index;
1045            }
1046        }
1047
1048        // not over any child position
1049        if ((m_placeholderIndex >= 0) && (m_placeholder.getParentElement() == getElement())) {
1050            // element is already attached to this parent and no new position available
1051            // don't do anything
1052            return m_placeholderIndex;
1053        }
1054        int top = CmsDomUtil.getRelativeY(y, getElement());
1055        int offsetHeight = getElement().getOffsetHeight();
1056        if ((top >= (offsetHeight / 2))) {
1057            // over top half, insert as first child
1058            getElement().insertFirst(m_placeholder);
1059            return 0;
1060        }
1061        // over bottom half, insert as last child
1062        getElement().appendChild(m_placeholder);
1063        return getElement().getChildCount() - 1;
1064    }
1065
1066    /**
1067     * Updates the element position cache during drag and drop.<p>
1068     */
1069    private void updatePositionsList() {
1070
1071        CmsDebugLog.getInstance().printLine("Updating positions");
1072        if (m_elementPositions != null) {
1073            m_elementPositions.clear();
1074        } else {
1075            m_elementPositions = new ArrayList<ElementPositionInfo>();
1076        }
1077        for (int index = 0; index < getElement().getChildCount(); index++) {
1078            Node node = getElement().getChild(index);
1079            // in some cases the container element may have an option bar as a direct child, ignore it
1080            if ((node.getNodeType() != Node.ELEMENT_NODE)
1081                || ((Element)node).hasClassName(I_CmsLayoutBundle.INSTANCE.containerpageCss().optionBar())) {
1082                continue;
1083            }
1084            m_elementPositions.add(new ElementPositionInfo((Element)node));
1085        }
1086        m_requiresPositionUpdate = false;
1087        m_ownPosition = CmsPositionBean.getBoundingClientRect(getElement());
1088    }
1089}