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;
029
030import org.opencms.ade.containerpage.client.ui.CmsContainerPageContainer;
031import org.opencms.ade.containerpage.client.ui.CmsContainerPageElementPanel;
032import org.opencms.ade.containerpage.client.ui.CmsDroppedElementModeSelectionDialog;
033import org.opencms.ade.containerpage.client.ui.CmsElementOptionBar;
034import org.opencms.ade.containerpage.client.ui.CmsGroupContainerElementPanel;
035import org.opencms.ade.containerpage.client.ui.CmsToolbarAllGalleriesMenu;
036import org.opencms.ade.containerpage.client.ui.I_CmsDropContainer;
037import org.opencms.ade.containerpage.client.ui.css.I_CmsLayoutBundle;
038import org.opencms.ade.containerpage.shared.CmsCntPageData.ElementReuseMode;
039import org.opencms.ade.containerpage.shared.CmsContainer;
040import org.opencms.ade.containerpage.shared.CmsContainerElement;
041import org.opencms.ade.containerpage.shared.CmsContainerElementData;
042import org.opencms.ade.contenteditor.shared.CmsEditorConstants;
043import org.opencms.ade.galleries.client.ui.CmsResultListItem;
044import org.opencms.gwt.client.dnd.CmsDNDHandler;
045import org.opencms.gwt.client.dnd.CmsDNDHandler.Orientation;
046import org.opencms.gwt.client.dnd.I_CmsDNDController;
047import org.opencms.gwt.client.dnd.I_CmsDraggable;
048import org.opencms.gwt.client.dnd.I_CmsDropTarget;
049import org.opencms.gwt.client.ui.CmsErrorDialog;
050import org.opencms.gwt.client.ui.CmsList;
051import org.opencms.gwt.client.ui.CmsListItem;
052import org.opencms.gwt.client.util.CmsDebugLog;
053import org.opencms.gwt.client.util.CmsDomUtil;
054import org.opencms.gwt.client.util.CmsPositionBean;
055import org.opencms.gwt.client.util.I_CmsSimpleCallback;
056import org.opencms.gwt.shared.CmsTemplateContextInfo;
057import org.opencms.util.CmsStringUtil;
058import org.opencms.util.CmsUUID;
059
060import java.util.ArrayList;
061import java.util.HashMap;
062import java.util.List;
063import java.util.Map;
064import java.util.Map.Entry;
065import java.util.Set;
066
067import com.google.common.base.Objects;
068import com.google.gwt.core.client.Scheduler;
069import com.google.gwt.core.client.Scheduler.ScheduledCommand;
070import com.google.gwt.dom.client.Document;
071import com.google.gwt.dom.client.Element;
072import com.google.gwt.dom.client.Style;
073import com.google.gwt.dom.client.Style.Display;
074import com.google.gwt.dom.client.Style.Position;
075import com.google.gwt.dom.client.Style.Unit;
076import com.google.gwt.user.client.DOM;
077import com.google.gwt.user.client.rpc.AsyncCallback;
078import com.google.gwt.user.client.ui.Widget;
079
080/**
081 * The container-page editor drag and drop controller.<p>
082 *
083 * @since 8.0.0
084 */
085public class CmsContainerpageDNDController implements I_CmsDNDController {
086
087    /** The container highlighting offset. */
088    public static final int HIGHLIGHTING_OFFSET = 4;
089
090    /** The minimum margin set to empty containers. */
091    private static final int MINIMUM_CONTAINER_MARGIN = 10;
092
093    /** The container page controller. */
094    protected CmsContainerpageController m_controller;
095
096    /** The id of the dragged element. */
097    protected String m_draggableId;
098
099    /** The id of the container from which an element was dragged. */
100    String m_originalContainerId;
101
102    /** The copy group id. */
103    private String m_copyGroupId;
104
105    /** The current place holder index. */
106    private int m_currentIndex = -1;
107
108    /** The current drop target. */
109    private I_CmsDropTarget m_currentTarget;
110
111    /** Map of current drag info beans. */
112    private Map<I_CmsDropTarget, Element> m_dragInfos;
113
114    /** The drag overlay. */
115    private Element m_dragOverlay;
116
117    /** DND controller for images. We set this in onDragStart if we are dragging an image, and then delegate most of the other method calls to it if it is set.*/
118    private CmsImageDndController m_imageDndController;
119
120    /** The ionitial drop target. */
121    private I_CmsDropTarget m_initialDropTarget;
122
123    /** Creating new flag. */
124    private boolean m_isNew;
125
126    /** The original position of the draggable. */
127    private int m_originalIndex;
128
129    /**
130     * Constructor.<p>
131     *
132     * @param controller the container page controller
133     */
134    public CmsContainerpageDNDController(CmsContainerpageController controller) {
135
136        m_controller = controller;
137        m_dragInfos = new HashMap<I_CmsDropTarget, Element>();
138    }
139
140    /**
141     * @see org.opencms.gwt.client.dnd.I_CmsDNDController#onAnimationStart(org.opencms.gwt.client.dnd.I_CmsDraggable, org.opencms.gwt.client.dnd.I_CmsDropTarget, org.opencms.gwt.client.dnd.CmsDNDHandler)
142     */
143    public void onAnimationStart(I_CmsDraggable draggable, I_CmsDropTarget target, CmsDNDHandler handler) {
144
145        // remove highlighting
146        for (I_CmsDropTarget dropTarget : m_dragInfos.keySet()) {
147            if (dropTarget instanceof I_CmsDropContainer) {
148                ((I_CmsDropContainer)dropTarget).removeHighlighting();
149            }
150        }
151    }
152
153    /**
154     * @see org.opencms.gwt.client.dnd.I_CmsDNDController#onBeforeDrop(org.opencms.gwt.client.dnd.I_CmsDraggable, org.opencms.gwt.client.dnd.I_CmsDropTarget, org.opencms.gwt.client.dnd.CmsDNDHandler)
155     */
156    public boolean onBeforeDrop(I_CmsDraggable draggable, I_CmsDropTarget target, CmsDNDHandler handler) {
157
158        return true;
159    }
160
161    /**
162    * @see org.opencms.gwt.client.dnd.I_CmsDNDController#onDragCancel(org.opencms.gwt.client.dnd.I_CmsDraggable, org.opencms.gwt.client.dnd.I_CmsDropTarget, org.opencms.gwt.client.dnd.CmsDNDHandler)
163    */
164    public void onDragCancel(I_CmsDraggable draggable, I_CmsDropTarget target, CmsDNDHandler handler) {
165
166        if (m_imageDndController != null) {
167            m_imageDndController.onDragCancel(draggable, target, handler);
168            removeDragOverlay();
169            m_imageDndController = null;
170            return;
171        }
172        stopDrag(handler);
173    }
174
175    /**
176     * @see org.opencms.gwt.client.dnd.I_CmsDNDController#onDragStart(org.opencms.gwt.client.dnd.I_CmsDraggable, org.opencms.gwt.client.dnd.I_CmsDropTarget, org.opencms.gwt.client.dnd.CmsDNDHandler)
177     */
178    public boolean onDragStart(final I_CmsDraggable draggable, I_CmsDropTarget target, final CmsDNDHandler handler) {
179
180        installDragOverlay();
181        m_currentTarget = null;
182        m_currentIndex = -1;
183        m_draggableId = draggable.getId();
184        m_isNew = false;
185        m_originalIndex = -1;
186        m_initialDropTarget = target;
187        handler.setOrientation(Orientation.ALL);
188        m_controller.hideEditableListButtons();
189
190        if (isImage(draggable)) {
191            if (m_controller.isGroupcontainerEditing()) {
192                // don't allow image DND while editing group containers
193                return false;
194            }
195            // We are going to delegate further method calls to this, and set it to null again if the image is dragged or the
196            // DND operation is cancelled
197            m_imageDndController = new CmsImageDndController(CmsContainerpageController.get());
198            return m_imageDndController.onDragStart(draggable, target, handler);
199
200        }
201
202        if (!m_controller.isGroupcontainerEditing()) {
203            m_controller.lockContainerpage(new I_CmsSimpleCallback<Boolean>() {
204
205                public void execute(Boolean arg) {
206
207                    if (!arg.booleanValue()) {
208                        handler.cancel();
209                    }
210                }
211            });
212        }
213
214        String containerId = null;
215        if (target instanceof CmsContainerPageContainer) {
216            containerId = ((CmsContainerPageContainer)target).getContainerId();
217        } else {
218            // set marker id
219            containerId = CmsContainerElement.MENU_CONTAINER_ID;
220        }
221        m_originalContainerId = containerId;
222
223        if (target != null) {
224            handler.addTarget(target);
225            if (target instanceof I_CmsDropContainer) {
226                prepareTargetContainer((I_CmsDropContainer)target, draggable, handler.getPlaceholder());
227                Element helper = handler.getDragHelper();
228                handler.setCursorOffsetX(helper.getOffsetWidth() - 15);
229                handler.setCursorOffsetY(20);
230            }
231        }
232
233        m_dragInfos.put(target, handler.getPlaceholder());
234        m_controller.getHandler().hideMenu();
235        String clientId = draggable.getId();
236        if (CmsStringUtil.isEmptyOrWhitespaceOnly(clientId)) {
237            CmsDebugLog.getInstance().printLine("draggable has no id, canceling drop");
238            return false;
239        }
240        final I_CmsSimpleCallback<CmsContainerElementData> callback = new I_CmsSimpleCallback<CmsContainerElementData>() {
241
242            /**
243             * Execute on success.<p>
244             *
245             * @param arg the container element data
246             */
247            public void execute(CmsContainerElementData arg) {
248
249                prepareHelperElements(arg, handler, draggable);
250                handler.updatePosition();
251            }
252        };
253        if (isNewId(clientId)) {
254            // for new content elements dragged from the gallery menu, the given id contains the resource type name
255            m_isNew = true;
256            m_controller.getNewElement(clientId, callback);
257        } else {
258            m_controller.getElementForDragAndDropFromContainer(clientId, m_originalContainerId, false, callback);
259        }
260        if (!(target instanceof CmsContainerPageContainer)) {
261            handler.setStartPosition(-1, 0);
262        }
263        return true;
264
265    }
266
267    /**
268    * @see org.opencms.gwt.client.dnd.I_CmsDNDController#onDrop(org.opencms.gwt.client.dnd.I_CmsDraggable, org.opencms.gwt.client.dnd.I_CmsDropTarget, org.opencms.gwt.client.dnd.CmsDNDHandler)
269    */
270    public void onDrop(final I_CmsDraggable draggable, final I_CmsDropTarget target, CmsDNDHandler handler) {
271
272        if (m_imageDndController != null) {
273            m_imageDndController.onDrop(draggable, target, handler);
274            removeDragOverlay();
275            m_imageDndController = null;
276            return;
277        }
278        if (target != m_initialDropTarget) {
279            if (target instanceof I_CmsDropContainer) {
280                final I_CmsDropContainer container = (I_CmsDropContainer)target;
281                final int index = container.getPlaceholderIndex();
282                final String modelReplaceId = container instanceof CmsContainerPageContainer
283                ? ((CmsContainerPageContainer)container).getCopyModelReplaceId()
284                : null;
285                if (!m_isNew && (draggable instanceof CmsListItem)) {
286                    final String copyGroupId = m_copyGroupId;
287                    AsyncCallback<String> modeCallback = new AsyncCallback<String>() {
288
289                        public void onFailure(Throwable caught) {
290
291                            if (caught != null) {
292                                CmsErrorDialog.handleException(caught);
293                            }
294                        }
295
296                        public void onSuccess(String result) {
297
298                            if (Objects.equal(result, CmsEditorConstants.MODE_COPY)) {
299                                final CmsContainerpageController controller = CmsContainerpageController.get();
300                                if (copyGroupId == null) {
301
302                                    CmsContainerElementData data = controller.getCachedElement(m_draggableId);
303                                    final Map<String, String> settings = data.getSettings();
304
305                                    controller.copyElement(
306                                        CmsContainerpageController.getServerId(m_draggableId),
307                                        new I_CmsSimpleCallback<CmsUUID>() {
308
309                                            public void execute(CmsUUID resultId) {
310
311                                                controller.getElementWithSettings(
312                                                    "" + resultId,
313                                                    settings,
314                                                    new I_CmsSimpleCallback<CmsContainerElementData>() {
315
316                                                        public void execute(CmsContainerElementData newData) {
317
318                                                            insertDropElement(
319                                                                newData,
320                                                                container,
321                                                                index,
322                                                                draggable instanceof CmsContainerPageElementPanel
323                                                                ? (CmsContainerPageElementPanel)draggable
324                                                                : null,
325                                                                modelReplaceId);
326                                                            container.removePlaceholder();
327                                                        }
328                                                    });
329                                            }
330                                        });
331                                } else {
332                                    controller.getElementForDragAndDropFromContainer(
333                                        copyGroupId,
334                                        m_originalContainerId,
335                                        true,
336                                        new I_CmsSimpleCallback<CmsContainerElementData>() {
337
338                                            public void execute(CmsContainerElementData arg) {
339
340                                                insertDropElement(
341                                                    arg,
342                                                    container,
343                                                    index,
344                                                    draggable instanceof CmsContainerPageElementPanel
345                                                    ? (CmsContainerPageElementPanel)draggable
346                                                    : null,
347                                                    modelReplaceId);
348                                                container.removePlaceholder();
349                                            }
350                                        });
351                                }
352
353                            } else if (Objects.equal(result, CmsEditorConstants.MODE_REUSE)) {
354                                insertDropElement(
355                                    m_controller.getCachedElement(m_draggableId),
356                                    container,
357                                    index,
358                                    draggable instanceof CmsContainerPageElementPanel
359                                    ? (CmsContainerPageElementPanel)draggable
360                                    : null,
361                                    modelReplaceId);
362                                container.removePlaceholder();
363                            }
364                        }
365                    };
366
367                    CmsUUID structureId = new CmsUUID(CmsContainerpageController.getServerId(m_draggableId));
368                    CmsContainerElementData cachedElementData = m_controller.getCachedElement(m_draggableId);
369                    ElementReuseMode reuseMode = isCopyModel(draggable)
370                    ? ElementReuseMode.copy
371                    : (((cachedElementData != null) && !cachedElementData.isCopyInModels())
372                    ? ElementReuseMode.reuse
373                    : CmsContainerpageController.get().getData().getElementReuseMode());
374                    if (handler.hasModifierCTRL()) {
375                        reuseMode = ElementReuseMode.ask;
376                    }
377                    if (reuseMode != ElementReuseMode.reuse) {
378
379                        if ((cachedElementData != null)
380                            && (!cachedElementData.hasWritePermission()
381                                || cachedElementData.isModelGroup()
382                                || cachedElementData.isCopyDisabled()
383                                || cachedElementData.isWasModelGroup())) {
384                            // User is not allowed to create this element in current view, so reuse the element instead
385                            reuseMode = ElementReuseMode.reuse;
386                        }
387                    }
388                    switch (reuseMode) {
389                        case ask:
390                            // when dropping elements from the into the page, we ask the user if the dropped element should
391                            // be used, or a copy of it. If the user wants a copy, we copy the corresponding resource and replace the element
392                            // in the page
393                            CmsDroppedElementModeSelectionDialog.showDialog(structureId, modeCallback);
394                            break;
395                        case copy:
396                            modeCallback.onSuccess(CmsEditorConstants.MODE_COPY);
397                            break;
398                        case reuse:
399                        default:
400                            modeCallback.onSuccess(CmsEditorConstants.MODE_REUSE);
401                            break;
402                    }
403                } else {
404                    insertDropElement(
405                        m_controller.getCachedElement(m_draggableId),
406                        container,
407                        index,
408                        draggable instanceof CmsContainerPageElementPanel
409                        ? (CmsContainerPageElementPanel)draggable
410                        : null,
411                        null);
412                }
413            } else if (target instanceof CmsList<?>) {
414                m_controller.addToFavoriteList(m_draggableId);
415            }
416        } else if ((target instanceof I_CmsDropContainer)
417            && (draggable instanceof CmsContainerPageElementPanel)
418            && isChangedPosition(target)) {
419                CmsDomUtil.showOverlay(draggable.getElement(), false);
420                I_CmsDropContainer container = (I_CmsDropContainer)target;
421                int count = container.getWidgetCount();
422                handler.getPlaceholder().getStyle().setDisplay(Display.NONE);
423                if (container.getPlaceholderIndex() >= count) {
424                    container.add((CmsContainerPageElementPanel)draggable);
425                } else {
426                    container.insert((CmsContainerPageElementPanel)draggable, container.getPlaceholderIndex());
427                }
428                m_controller.addToRecentList(m_draggableId, null);
429                // changes are only relevant to the container page if not group-container editing
430                if (!m_controller.isGroupcontainerEditing()) {
431                    m_controller.setPageChanged();
432                }
433            } else if (draggable instanceof CmsContainerPageElementPanel) {
434                CmsDomUtil.showOverlay(draggable.getElement(), false);
435                // to reset mouse over state remove and attach the option bar
436                CmsContainerPageElementPanel containerElement = (CmsContainerPageElementPanel)draggable;
437                CmsElementOptionBar optionBar = containerElement.getElementOptionBar();
438                optionBar.removeFromParent();
439                containerElement.setElementOptionBar(optionBar);
440            }
441        stopDrag(handler);
442    }
443
444    /**
445     * @see org.opencms.gwt.client.dnd.I_CmsDNDController#onPositionedPlaceholder(org.opencms.gwt.client.dnd.I_CmsDraggable, org.opencms.gwt.client.dnd.I_CmsDropTarget, org.opencms.gwt.client.dnd.CmsDNDHandler)
446     */
447    public void onPositionedPlaceholder(I_CmsDraggable draggable, I_CmsDropTarget target, CmsDNDHandler handler) {
448
449        if (m_imageDndController != null) {
450            m_imageDndController.onPositionedPlaceholder(draggable, target, handler);
451            return;
452        }
453
454        if (hasChangedPosition(target)) {
455            checkPlaceholderVisibility(target);
456            updateHighlighting(false);
457        }
458    }
459
460    /**
461     * @see org.opencms.gwt.client.dnd.I_CmsDNDController#onTargetEnter(org.opencms.gwt.client.dnd.I_CmsDraggable, org.opencms.gwt.client.dnd.I_CmsDropTarget, org.opencms.gwt.client.dnd.CmsDNDHandler)
462     */
463    public boolean onTargetEnter(I_CmsDraggable draggable, I_CmsDropTarget target, CmsDNDHandler handler) {
464
465        if (m_imageDndController != null) {
466            return m_imageDndController.onTargetEnter(draggable, target, handler);
467        }
468
469        if (target instanceof CmsContainerPageContainer) {
470            CmsContainerPageContainer container = (CmsContainerPageContainer)target;
471            if (container.isShowingEmptyContainerElement()) {
472                CmsContainerPageContainer.newResizeHelper(container).initHeight();
473            }
474        }
475
476        Element placeholder = m_dragInfos.get(target);
477        if (placeholder != null) {
478            handler.getPlaceholder().getStyle().setDisplay(Display.NONE);
479            handler.setPlaceholder(placeholder);
480            placeholder.getStyle().clearDisplay();
481            if ((target != m_initialDropTarget) && (target instanceof I_CmsDropContainer)) {
482                ((I_CmsDropContainer)target).checkMaxElementsOnEnter();
483            }
484        }
485        return true;
486    }
487
488    /**
489     * @see org.opencms.gwt.client.dnd.I_CmsDNDController#onTargetLeave(org.opencms.gwt.client.dnd.I_CmsDraggable, org.opencms.gwt.client.dnd.I_CmsDropTarget, org.opencms.gwt.client.dnd.CmsDNDHandler)
490     */
491    public void onTargetLeave(I_CmsDraggable draggable, I_CmsDropTarget target, CmsDNDHandler handler) {
492
493        if (m_imageDndController != null) {
494            m_imageDndController.onTargetLeave(draggable, target, handler);
495            return;
496        }
497        CmsContainerPageContainer.clearResizeHelper();
498        m_currentTarget = null;
499        m_currentIndex = -1;
500        Element placeholder = m_dragInfos.get(m_initialDropTarget);
501        if (placeholder != null) {
502            handler.getPlaceholder().getStyle().setDisplay(Display.NONE);
503            handler.setPlaceholder(placeholder);
504            placeholder.getStyle().setDisplay(Display.NONE);
505            if ((target != m_initialDropTarget) && (target instanceof I_CmsDropContainer)) {
506                ((I_CmsDropContainer)target).checkMaxElementsOnLeave();
507            }
508        }
509        updateHighlighting(false);
510    }
511
512    /**
513     * Prepares all helper elements for the different drop targets.<p>
514     *
515     * @param elementData the element data
516     * @param handler the drag and drop handler
517     * @param draggable the draggable
518     */
519    protected void prepareHelperElements(
520        CmsContainerElementData elementData,
521        CmsDNDHandler handler,
522        I_CmsDraggable draggable) {
523
524        if (elementData == null) {
525            CmsDebugLog.getInstance().printLine("elementData == null!");
526            handler.cancel();
527            return;
528        }
529        if (!handler.isDragging()) {
530            return;
531        }
532        if (elementData.isGroup()) {
533            m_copyGroupId = m_draggableId;
534        } else {
535            m_copyGroupId = null;
536        }
537
538        if ((elementData.getDndId() != null) && (m_controller.getCachedElement(elementData.getDndId()) != null)) {
539            m_draggableId = elementData.getDndId();
540            elementData = m_controller.getCachedElement(m_draggableId);
541        } else {
542            m_draggableId = elementData.getClientId();
543        }
544        if (m_controller.isGroupcontainerEditing()) {
545            CmsGroupContainerElementPanel groupContainer = m_controller.getGroupcontainer();
546            if ((groupContainer != m_initialDropTarget)
547                && !(elementData.isGroupContainer() || elementData.isInheritContainer())
548                && (elementData.getContents().get(groupContainer.getContainerId()) != null)) {
549                Element placeholder = null;
550                Set<String> cssResources = elementData.getCssResources(groupContainer.getContainerId());
551                if ((cssResources != null) && !cssResources.isEmpty()) {
552                    // the element requires certain CSS resources, check if present and include if necessary
553                    for (String cssResourceLink : cssResources) {
554                        CmsDomUtil.ensureStyleSheetIncluded(cssResourceLink);
555                    }
556                }
557                try {
558                    String htmlContent = elementData.getContents().get(groupContainer.getContainerId());
559                    placeholder = CmsDomUtil.createElement(htmlContent);
560                    // ensure any embedded flash players are set opaque so UI elements may be placed above them
561                    CmsDomUtil.fixFlashZindex(placeholder);
562                    placeholder.addClassName(I_CmsLayoutBundle.INSTANCE.dragdropCss().dragPlaceholder());
563                } catch (Exception e) {
564                    CmsDebugLog.getInstance().printLine(e.getMessage());
565                }
566
567                if (placeholder != null) {
568                    prepareDragInfo(placeholder, groupContainer, handler);
569                    groupContainer.highlightContainer();
570                }
571            }
572            return;
573        }
574        if (!m_controller.isEditingDisabled()) {
575            for (CmsContainerPageContainer container : m_controller.getContainerTargets().values()) {
576                if (draggable.getElement().isOrHasChild(container.getElement())) {
577                    // skip containers that are children of the draggable element
578                    continue;
579                }
580                if ((container != m_initialDropTarget)
581                    && !container.isDetailView()
582                    && (m_controller.getData().isModelGroup() || !container.hasModelGroupParent())
583                    && (elementData.getContents().get(container.getContainerId()) != null)) {
584
585                    Element placeholder = null;
586                    if (elementData.isGroupContainer() || elementData.isInheritContainer()) {
587                        placeholder = DOM.createDiv();
588                        String content = "";
589                        for (String groupId : elementData.getSubItems()) {
590                            CmsContainerElementData subData = m_controller.getCachedElement(groupId);
591                            if (subData != null) {
592                                if (subData.isShowInContext(
593                                    CmsContainerpageController.get().getData().getTemplateContextInfo().getCurrentContext())) {
594                                    if ((subData.getContents().get(container.getContainerId()) != null)) {
595                                        content += subData.getContents().get(container.getContainerId());
596                                    }
597                                } else {
598                                    content += "<div class='"
599                                        + CmsTemplateContextInfo.DUMMY_ELEMENT_MARKER
600                                        + "' style='display: none !important;'></div>";
601                                }
602                            }
603                        }
604                        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(content)) {
605                            placeholder.setInnerHTML(content);
606                            // ensure any embedded flash players are set opaque so UI elements may be placed above them
607                            CmsDomUtil.fixFlashZindex(placeholder);
608                        } else {
609                            placeholder.addClassName(
610                                I_CmsLayoutBundle.INSTANCE.containerpageCss().emptyGroupContainer());
611                        }
612                    } else {
613                        Set<String> cssResources = elementData.getCssResources(container.getContainerId());
614                        if ((cssResources != null) && !cssResources.isEmpty()) {
615                            // the element requires certain CSS resources, check if present and include if necessary
616                            for (String cssResourceLink : cssResources) {
617                                CmsDomUtil.ensureStyleSheetIncluded(cssResourceLink);
618                            }
619                        }
620                        try {
621                            String htmlContent = elementData.getContents().get(container.getContainerId());
622                            placeholder = CmsDomUtil.createElement(htmlContent);
623                            // ensure any embedded flash players are set opaque so UI elements may be placed above them
624                            CmsDomUtil.fixFlashZindex(placeholder);
625                            placeholder.addClassName(I_CmsLayoutBundle.INSTANCE.dragdropCss().dragPlaceholder());
626                        } catch (Exception e) {
627                            CmsDebugLog.getInstance().printLine(e.getMessage());
628                        }
629                    }
630                    if (placeholder != null) {
631                        prepareDragInfo(placeholder, container, handler);
632                    }
633                }
634            }
635            initNestedContainers();
636            // add highlighting after all drag targets have been initialized
637            updateHighlighting(true);
638        }
639    }
640
641    /**
642     * Inserts e new container element into the container once the drag and drop is finished.<p>
643     *
644     * @param elementData the element data to create the element from
645     * @param container the target container
646     * @param index the element index
647     * @param draggable the original drag element
648     * @param modelReplaceId the model replace id
649     */
650    void insertDropElement(
651        CmsContainerElementData elementData,
652        I_CmsDropContainer container,
653        int index,
654        CmsContainerPageElementPanel draggable,
655        String modelReplaceId) {
656
657        try {
658            CmsContainerPageElementPanel containerElement = m_controller.getContainerpageUtil().createElement(
659                elementData,
660                container,
661                m_isNew);
662            if (m_isNew) {
663                containerElement.setNewType(CmsContainerpageController.getServerId(m_draggableId));
664            } else {
665                m_controller.addToRecentList(elementData.getClientId(), null);
666            }
667
668            if (index >= container.getWidgetCount()) {
669                container.add(containerElement);
670            } else {
671                container.insert(containerElement, index);
672            }
673            if (draggable != null) {
674                draggable.removeFromParent();
675            }
676            m_controller.initializeSubContainers(containerElement);
677
678            if (modelReplaceId != null) {
679                m_controller.executeCopyModelReplace(
680                    modelReplaceId,
681                    ((CmsContainerPageContainer)container).getFormerModelGroupParent(),
682                    m_controller.getCachedElement(m_draggableId));
683            }
684
685            if (!m_controller.isGroupcontainerEditing()) {
686                if (containerElement.hasReloadMarker()) {
687                    m_controller.setPageChanged(new Runnable() {
688
689                        public void run() {
690
691                            CmsContainerpageController.get().reloadPage();
692                        }
693                    });
694                } else {
695                    m_controller.setPageChanged();
696                    String clientId = elementData.getClientId();
697                    String containerId = container.getContainerId();
698                    Map<String, String> settings = elementData.getSettings();
699                    m_controller.updateServerElementFormatter(clientId, containerId, settings);
700                }
701            }
702            if (m_controller.isGroupcontainerEditing()) {
703                container.getElement().removeClassName(
704                    I_CmsLayoutBundle.INSTANCE.containerpageCss().emptyGroupContainer());
705            }
706
707        } catch (Exception e) {
708            CmsErrorDialog.handleException(e.getMessage(), e);
709        }
710    }
711
712    /**
713     * Checks and sets if the place holder should be visible.<p>
714     *
715     * @param target the target container
716     */
717    private void checkPlaceholderVisibility(I_CmsDropTarget target) {
718
719        if (target instanceof I_CmsDropContainer) {
720            I_CmsDropContainer container = (I_CmsDropContainer)target;
721            container.setPlaceholderVisibility(
722                (container != m_initialDropTarget)
723                    || ((m_currentIndex - m_originalIndex) > 1)
724                    || ((m_originalIndex - m_currentIndex) > 0));
725        }
726    }
727
728    /**
729     * Checks if the placeholder position has changed.<p>
730     *
731     * @param target the current drop target
732     *
733     * @return <code>true</code> if the placeholder position has changed
734     */
735    private boolean hasChangedPosition(I_CmsDropTarget target) {
736
737        if ((m_currentTarget != target) || (m_currentIndex != target.getPlaceholderIndex())) {
738            m_currentTarget = target;
739            m_currentIndex = target.getPlaceholderIndex();
740            return true;
741        }
742        return false;
743    }
744
745    /**
746     * Initializes the nested container infos.<p>
747     */
748    private void initNestedContainers() {
749
750        for (CmsContainer container : m_controller.getContainers().values()) {
751            if (container.isSubContainer()) {
752                CmsContainerPageContainer containerWidget = m_controller.m_targetContainers.get(container.getName());
753                // check if the sub container is a valid drop targets
754                if (m_dragInfos.keySet().contains(containerWidget)) {
755                    CmsContainer parentContainer = m_controller.getContainers().get(container.getParentContainerName());
756                    // add the container to all it's ancestors as a dnd child
757                    while (parentContainer != null) {
758                        if (m_dragInfos.keySet().contains(
759                            m_controller.m_targetContainers.get(parentContainer.getName()))) {
760                            m_controller.m_targetContainers.get(parentContainer.getName()).addDndChild(containerWidget);
761                        }
762                        if (parentContainer.isSubContainer()) {
763                            parentContainer = m_controller.getContainers().get(
764                                parentContainer.getParentContainerName());
765                        } else {
766                            parentContainer = null;
767                        }
768                    }
769                }
770            }
771
772        }
773    }
774
775    /**
776     * Installs the drag overlay to avoid any mouse over issues or similar.<p>
777     */
778    private void installDragOverlay() {
779
780        if (m_dragOverlay != null) {
781            m_dragOverlay.removeFromParent();
782        }
783        m_dragOverlay = DOM.createDiv();
784        m_dragOverlay.addClassName(I_CmsLayoutBundle.INSTANCE.dragdropCss().dragOverlay());
785        Document.get().getBody().appendChild(m_dragOverlay);
786    }
787
788    /**
789     * Checks whether the current placeholder position represents a change to the original draggable position within the tree.<p>
790     *
791     * @param target the current drop target
792     *
793     * @return <code>true</code> if the position changed
794     */
795    private boolean isChangedPosition(I_CmsDropTarget target) {
796
797        // if the new index is not next to the old one, the position has changed
798        if ((target != m_initialDropTarget)
799            || !((target.getPlaceholderIndex() == (m_originalIndex + 1))
800                || (target.getPlaceholderIndex() == m_originalIndex))) {
801            return true;
802        }
803        return false;
804    }
805
806    /**
807     * Checks if the draggable item is a copy model.<p>
808     *
809     * @param draggable the draggable item
810     *
811     * @return true if the item is a copy model
812     */
813    private boolean isCopyModel(I_CmsDraggable draggable) {
814
815        if (!(draggable instanceof CmsResultListItem)) {
816            return false;
817        }
818        return ((CmsResultListItem)draggable).getResult().isCopyModel();
819    }
820
821    /**
822     * Checks if the given draggable item is an image.<p>
823     *
824     * @param draggable the item to check
825     *
826     * @return true if the given item is an image
827     */
828    private boolean isImage(I_CmsDraggable draggable) {
829
830        return (draggable instanceof CmsResultListItem)
831            && CmsToolbarAllGalleriesMenu.DND_MARKER.equals(((CmsResultListItem)draggable).getData());
832
833    }
834
835    /**
836     * Checks if the given id is a new id.<p>
837     *
838     * @param id the id
839     *
840     * @return <code>true</code> if the id is a new id
841     */
842    private boolean isNewId(String id) {
843
844        if (id.contains("#")) {
845            id = id.substring(0, id.indexOf("#"));
846        }
847        return !CmsUUID.isValidUUID(id);
848    }
849
850    /**
851     * Sets styles of helper elements, appends the to the drop target and puts them into a drag info bean.<p>
852     *
853     * @param placeholder the placeholder element
854     * @param target the drop target
855     * @param handler the drag and drop handler
856     */
857    private void prepareDragInfo(Element placeholder, I_CmsDropContainer target, CmsDNDHandler handler) {
858
859        target.getElement().addClassName(I_CmsLayoutBundle.INSTANCE.dragdropCss().dragging());
860        String positioning = CmsDomUtil.getCurrentStyle(
861            target.getElement(),
862            org.opencms.gwt.client.util.CmsDomUtil.Style.position);
863        // set target relative, if not absolute or fixed
864        if (!Position.ABSOLUTE.getCssName().equals(positioning) && !Position.FIXED.getCssName().equals(positioning)) {
865            target.getElement().getStyle().setPosition(Position.RELATIVE);
866            // check for empty containers that don't have a minimum top and bottom margin to avoid containers overlapping
867            if (target.getElement().getFirstChildElement() == null) {
868                if (CmsDomUtil.getCurrentStyleInt(
869                    target.getElement(),
870                    CmsDomUtil.Style.marginTop) < MINIMUM_CONTAINER_MARGIN) {
871                    target.getElement().getStyle().setMarginTop(MINIMUM_CONTAINER_MARGIN, Unit.PX);
872                }
873                if (CmsDomUtil.getCurrentStyleInt(
874                    target.getElement(),
875                    CmsDomUtil.Style.marginBottom) < MINIMUM_CONTAINER_MARGIN) {
876                    target.getElement().getStyle().setMarginBottom(MINIMUM_CONTAINER_MARGIN, Unit.PX);
877                }
878            }
879        }
880        m_dragInfos.put(target, placeholder);
881        handler.addTarget(target);
882    }
883
884    /**
885     * Prepares the target container.<p>
886     *
887     * @param targetContainer the container
888     * @param draggable the draggable
889     * @param placeholder the placeholder
890     */
891    private void prepareTargetContainer(
892        I_CmsDropContainer targetContainer,
893        I_CmsDraggable draggable,
894        Element placeholder) {
895
896        String positioning = CmsDomUtil.getCurrentStyle(
897            targetContainer.getElement(),
898            org.opencms.gwt.client.util.CmsDomUtil.Style.position);
899        // set target relative, if not absolute or fixed
900        if (!Position.ABSOLUTE.getCssName().equals(positioning) && !Position.FIXED.getCssName().equals(positioning)) {
901            targetContainer.getElement().getStyle().setPosition(Position.RELATIVE);
902        }
903        m_originalIndex = targetContainer.getWidgetIndex((Widget)draggable);
904        targetContainer.getElement().insertBefore(placeholder, draggable.getElement());
905        targetContainer.getElement().addClassName(I_CmsLayoutBundle.INSTANCE.dragdropCss().dragging());
906        CmsDomUtil.showOverlay(draggable.getElement(), true);
907        targetContainer.highlightContainer();
908    }
909
910    /**
911     * Removes the drag overlay.<p>
912     */
913    private void removeDragOverlay() {
914
915        if (m_dragOverlay != null) {
916            m_dragOverlay.removeFromParent();
917            m_dragOverlay = null;
918        }
919    }
920
921    /**
922     * Function which is called when the drag process is stopped, either by cancelling or dropping.<p>
923     *
924     * @param handler the drag and drop handler
925     */
926    private void stopDrag(final CmsDNDHandler handler) {
927
928        removeDragOverlay();
929        CmsContainerPageContainer.clearResizeHelper();
930        for (I_CmsDropTarget target : m_dragInfos.keySet()) {
931            target.getElement().removeClassName(I_CmsLayoutBundle.INSTANCE.dragdropCss().dragging());
932            target.getElement().removeClassName(I_CmsLayoutBundle.INSTANCE.dragdropCss().clearFix());
933            Style targetStyle = target.getElement().getStyle();
934            if (!(target instanceof CmsGroupContainerElementPanel)) {
935                targetStyle.clearPosition();
936            }
937            targetStyle.clearMarginTop();
938            targetStyle.clearMarginBottom();
939            if (target instanceof I_CmsDropContainer) {
940                ((I_CmsDropContainer)target).removeHighlighting();
941            }
942        }
943        handler.getDragHelper().removeFromParent();
944        m_copyGroupId = null;
945        m_currentTarget = null;
946        m_currentIndex = -1;
947        m_isNew = false;
948        m_controller.getHandler().deactivateMenuButton();
949        final List<I_CmsDropTarget> dragTargets = new ArrayList<I_CmsDropTarget>(m_dragInfos.keySet());
950        m_dragInfos.clear();
951        Scheduler.get().scheduleDeferred(new ScheduledCommand() {
952
953            /**
954             * @see com.google.gwt.user.client.Command#execute()
955             */
956            public void execute() {
957
958                handler.clearTargets();
959                m_controller.resetEditButtons();
960                // in case of group editing, this will refresh the container position and size
961                for (I_CmsDropTarget target : dragTargets) {
962                    if (target instanceof I_CmsDropContainer) {
963                        ((I_CmsDropContainer)target).refreshHighlighting();
964                        // reset the nested container infos
965                        ((I_CmsDropContainer)target).clearDnDChildren();
966                    }
967                }
968            }
969        });
970        if (handler.getDraggable() instanceof CmsContainerPageElementPanel) {
971            ((CmsContainerPageElementPanel)(handler.getDraggable())).removeHighlighting();
972        }
973    }
974
975    /**
976     * Updates the drag target highlighting.<p>
977     *
978     * @param initial <code>true</code> when initially highlighting the drop containers
979     */
980    private void updateHighlighting(boolean initial) {
981
982        Map<I_CmsDropContainer, CmsPositionBean> containers = new HashMap<I_CmsDropContainer, CmsPositionBean>();
983        for (I_CmsDropTarget target : m_dragInfos.keySet()) {
984            if ((target instanceof I_CmsDropContainer)
985                && !Display.NONE.getCssName().equalsIgnoreCase(
986                    CmsDomUtil.getCurrentStyle(target.getElement(), CmsDomUtil.Style.display))) {
987                if (initial && (target != m_initialDropTarget)) {
988                    ((I_CmsDropContainer)target).highlightContainer();
989                } else {
990                    ((I_CmsDropContainer)target).updatePositionInfo();
991                }
992                containers.put((I_CmsDropContainer)target, ((I_CmsDropContainer)target).getPositionInfo());
993            }
994        }
995        List<I_CmsDropContainer> containersToMatch = new ArrayList<I_CmsDropContainer>(containers.keySet());
996        for (I_CmsDropContainer contA : containers.keySet()) {
997            containersToMatch.remove(contA);
998            for (I_CmsDropContainer contB : containersToMatch) {
999                CmsPositionBean posA = containers.get(contA);
1000                CmsPositionBean posB = containers.get(contB);
1001                if (CmsPositionBean.checkCollision(posA, posB, HIGHLIGHTING_OFFSET * 3)) {
1002                    if (contA.hasDnDChildren() && contA.getDnDChildren().contains(contB)) {
1003                        if (!posA.isInside(posB, HIGHLIGHTING_OFFSET)) {
1004                            // the nested container is not completely inside the other
1005                            // increase the size of the outer container
1006                            posA.ensureSurrounds(posB, HIGHLIGHTING_OFFSET);
1007                        }
1008                    } else if (contB.hasDnDChildren() && contB.getDnDChildren().contains(contA)) {
1009                        if (!posB.isInside(posA, HIGHLIGHTING_OFFSET)) {
1010                            // the nested container is not completely inside the other
1011                            // increase the size of the outer container
1012                            posB.ensureSurrounds(posA, HIGHLIGHTING_OFFSET);
1013                        }
1014                    } else {
1015                        CmsPositionBean.avoidCollision(posA, posB, HIGHLIGHTING_OFFSET * 3);
1016                    }
1017                }
1018            }
1019        }
1020
1021        for (Entry<I_CmsDropContainer, CmsPositionBean> containerEntry : containers.entrySet()) {
1022            containerEntry.getKey().refreshHighlighting(containerEntry.getValue());
1023        }
1024    }
1025}