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