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.CmsContainerpageEvent.EventType;
031import org.opencms.ade.containerpage.client.ui.CmsConfirmRemoveDialog;
032import org.opencms.ade.containerpage.client.ui.CmsContainerPageContainer;
033import org.opencms.ade.containerpage.client.ui.CmsContainerPageElementPanel;
034import org.opencms.ade.containerpage.client.ui.CmsGroupContainerElementPanel;
035import org.opencms.ade.containerpage.client.ui.CmsRemovedElementDeletionDialog;
036import org.opencms.ade.containerpage.client.ui.CmsSmallElementsHandler;
037import org.opencms.ade.containerpage.client.ui.I_CmsDropContainer;
038import org.opencms.ade.containerpage.client.ui.css.I_CmsLayoutBundle;
039import org.opencms.ade.containerpage.client.ui.groupeditor.A_CmsGroupEditor;
040import org.opencms.ade.containerpage.client.ui.groupeditor.CmsGroupContainerEditor;
041import org.opencms.ade.containerpage.client.ui.groupeditor.CmsInheritanceContainerEditor;
042import org.opencms.ade.containerpage.shared.CmsCntPageData;
043import org.opencms.ade.containerpage.shared.CmsCntPageData.ElementDeleteMode;
044import org.opencms.ade.containerpage.shared.CmsContainer;
045import org.opencms.ade.containerpage.shared.CmsContainerElement;
046import org.opencms.ade.containerpage.shared.CmsContainerElementData;
047import org.opencms.ade.containerpage.shared.CmsContainerPageGalleryData;
048import org.opencms.ade.containerpage.shared.CmsContainerPageRpcContext;
049import org.opencms.ade.containerpage.shared.CmsCreateElementData;
050import org.opencms.ade.containerpage.shared.CmsDialogOptionsAndInfo;
051import org.opencms.ade.containerpage.shared.CmsElementSettingsConfig;
052import org.opencms.ade.containerpage.shared.CmsElementViewInfo;
053import org.opencms.ade.containerpage.shared.CmsGroupContainer;
054import org.opencms.ade.containerpage.shared.CmsGroupContainerSaveResult;
055import org.opencms.ade.containerpage.shared.CmsInheritanceContainer;
056import org.opencms.ade.containerpage.shared.CmsRemovedElementStatus;
057import org.opencms.ade.containerpage.shared.rpc.I_CmsContainerpageService;
058import org.opencms.ade.containerpage.shared.rpc.I_CmsContainerpageServiceAsync;
059import org.opencms.ade.contenteditor.client.CmsContentEditor;
060import org.opencms.gwt.client.CmsCoreProvider;
061import org.opencms.gwt.client.I_CmsElementToolbarContext;
062import org.opencms.gwt.client.dnd.CmsCompositeDNDController;
063import org.opencms.gwt.client.dnd.CmsDNDHandler;
064import org.opencms.gwt.client.dnd.I_CmsDNDController;
065import org.opencms.gwt.client.rpc.CmsRpcAction;
066import org.opencms.gwt.client.rpc.CmsRpcPrefetcher;
067import org.opencms.gwt.client.ui.CmsErrorDialog;
068import org.opencms.gwt.client.ui.CmsNotification;
069import org.opencms.gwt.client.ui.CmsNotification.Type;
070import org.opencms.gwt.client.util.CmsDebugLog;
071import org.opencms.gwt.client.util.CmsDomUtil;
072import org.opencms.gwt.client.util.I_CmsSimpleCallback;
073import org.opencms.gwt.shared.CmsContextMenuEntryBean;
074import org.opencms.gwt.shared.CmsCoreData.AdeContext;
075import org.opencms.gwt.shared.CmsGwtConstants;
076import org.opencms.gwt.shared.CmsListInfoBean;
077import org.opencms.gwt.shared.CmsTemplateContextInfo;
078import org.opencms.gwt.shared.I_CmsUnlockData;
079import org.opencms.gwt.shared.I_CmsUnlockDataFactory;
080import org.opencms.gwt.shared.rpc.I_CmsCoreServiceAsync;
081import org.opencms.util.CmsDefaultSet;
082import org.opencms.util.CmsStringUtil;
083import org.opencms.util.CmsUUID;
084
085import java.util.ArrayList;
086import java.util.Collection;
087import java.util.HashMap;
088import java.util.HashSet;
089import java.util.Iterator;
090import java.util.List;
091import java.util.Map;
092import java.util.Map.Entry;
093import java.util.Set;
094
095import com.google.common.base.Optional;
096import com.google.common.collect.Lists;
097import com.google.gwt.core.client.GWT;
098import com.google.gwt.dom.client.AnchorElement;
099import com.google.gwt.dom.client.Element;
100import com.google.gwt.dom.client.EventTarget;
101import com.google.gwt.event.dom.client.KeyCodes;
102import com.google.gwt.event.logical.shared.ResizeEvent;
103import com.google.gwt.event.logical.shared.ResizeHandler;
104import com.google.gwt.event.logical.shared.ValueChangeEvent;
105import com.google.gwt.event.logical.shared.ValueChangeHandler;
106import com.google.gwt.user.client.Command;
107import com.google.gwt.user.client.Event;
108import com.google.gwt.user.client.Event.NativePreviewEvent;
109import com.google.gwt.user.client.Event.NativePreviewHandler;
110import com.google.gwt.user.client.History;
111import com.google.gwt.user.client.Timer;
112import com.google.gwt.user.client.Window;
113import com.google.gwt.user.client.rpc.AsyncCallback;
114import com.google.gwt.user.client.rpc.SerializationException;
115import com.google.gwt.user.client.rpc.ServiceDefTarget;
116import com.google.gwt.user.client.ui.RootPanel;
117import com.google.gwt.user.client.ui.Widget;
118import com.google.web.bindery.autobean.shared.AutoBean;
119import com.google.web.bindery.autobean.shared.AutoBeanCodex;
120
121/**
122 * Data provider for the container-page editor. All data concerning the container-page is requested and maintained by this provider.<p>
123 *
124 * @since 8.0.0
125 */
126public final class CmsContainerpageController {
127
128    /**
129     * Enum which is used to control how elements are removed from the page.<p>
130     */
131    public enum ElementRemoveMode {
132        /** Reference checks are performed and the user is asked for confirmation whether they really want to remove the element before the page is saved. */
133        confirmRemove,
134
135        /** Reference checks are only performed after the page or group has been saved. */
136        saveAndCheckReferences,
137
138        /** Element is just removed, no checks are performed. */
139        silent;
140    }
141
142    /**
143     * Visitor interface used to process the current container content on the page.<p>
144     */
145    public static interface I_PageContentVisitor {
146
147        /**
148         * This method is called before a container is processed.<p>
149         *
150         * If the method returns false, the container will be skipped.<p>
151         *
152         * @param name the container name
153         * @param container the container data object
154         *
155         * @return true if the container should be processed, true if it should be skipped
156         */
157        boolean beginContainer(String name, CmsContainer container);
158
159        /**
160         * This method is called after all elements of a container have been processed.<p>
161         */
162        void endContainer();
163
164        /**
165         * This method is called for each element of a container.<p>
166         *
167         * @param element the container element
168         */
169        void handleElement(CmsContainerPageElementPanel element);
170    }
171
172    /**
173     * This visitor implementation checks whether there are other elements in the current page
174     * which correspond to the same VFS resource as a given container element.
175     */
176    public static class ReferenceCheckVisitor implements I_PageContentVisitor {
177
178        /** The element for which we want to check whether there are other references to the same resource. */
179        private CmsContainerPageElementPanel m_elementPanel;
180
181        /** True if other references have been found. */
182        private boolean m_hasReferences;
183
184        /** The structure id of the element. */
185        private String m_structureId;
186
187        /**
188         * Creates a new instance.<p>
189         *
190         * @param elementPanel the element for which we want to check if there are other references
191         */
192        public ReferenceCheckVisitor(CmsContainerPageElementPanel elementPanel) {
193
194            m_elementPanel = elementPanel;
195            m_structureId = getServerId(elementPanel.getId());
196        }
197
198        /**
199         * @see org.opencms.ade.containerpage.client.CmsContainerpageController.I_PageContentVisitor#beginContainer(java.lang.String, org.opencms.ade.containerpage.shared.CmsContainer)
200         */
201        public boolean beginContainer(String name, CmsContainer container) {
202
203            return !container.isDetailView();
204        }
205
206        /**
207         * @see org.opencms.ade.containerpage.client.CmsContainerpageController.I_PageContentVisitor#endContainer()
208         */
209        public void endContainer() {
210
211            // do nothing
212        }
213
214        /**
215         * @see org.opencms.ade.containerpage.client.CmsContainerpageController.I_PageContentVisitor#handleElement(org.opencms.ade.containerpage.client.ui.CmsContainerPageElementPanel)
216         */
217        public void handleElement(CmsContainerPageElementPanel element) {
218
219            if (element != m_elementPanel) {
220                String id = getServerId(element.getId());
221                if (m_structureId.equals(id)) {
222                    m_hasReferences = true;
223                }
224            }
225        }
226
227        /**
228         * Checks if other references have been found.<p>
229         *
230         * @return true if other references have been found
231         */
232        public boolean hasReferences() {
233
234            return m_hasReferences;
235        }
236
237    }
238
239    /**
240     * Visitor implementation which is used to gather the container contents for saving.<p>
241     */
242    protected class PageStateVisitor implements I_PageContentVisitor {
243
244        /** The current container name. */
245        protected String m_containerName;
246
247        /** The contaienr which is currently being processed. */
248        protected CmsContainer m_currentContainer;
249
250        /** The list of collected containers. */
251        protected List<CmsContainer> m_resultContainers = new ArrayList<CmsContainer>();
252
253        /** The list of elements of the currently processed container which have already been processed. */
254        List<CmsContainerElement> m_currentElements;
255
256        /**
257         * @see org.opencms.ade.containerpage.client.CmsContainerpageController.I_PageContentVisitor#beginContainer(java.lang.String, org.opencms.ade.containerpage.shared.CmsContainer)
258         */
259        public boolean beginContainer(String name, CmsContainer container) {
260
261            m_currentContainer = container;
262            m_containerName = name;
263            m_currentElements = new ArrayList<CmsContainerElement>();
264            return true;
265        }
266
267        /**
268         * @see org.opencms.ade.containerpage.client.CmsContainerpageController.I_PageContentVisitor#endContainer()
269         */
270        public void endContainer() {
271
272            CmsContainer container = new CmsContainer(
273                m_containerName,
274                m_currentContainer.getType(),
275                null,
276                m_currentContainer.getWidth(),
277                m_currentContainer.getMaxElements(),
278                m_currentContainer.isDetailViewContainer(),
279                m_currentContainer.isDetailView(),
280                true,
281                m_currentElements,
282                m_currentContainer.getParentContainerName(),
283                m_currentContainer.getParentInstanceId(),
284                m_currentContainer.getSettingPresets());
285            container.setDetailOnly(m_currentContainer.isDetailOnly());
286            container.setRootContainer(isRootContainer(m_currentContainer));
287            m_resultContainers.add(container);
288        }
289
290        /**
291         * Gets the list of collected containers.<p>
292         *
293         * @return the list of containers
294         */
295        public List<CmsContainer> getContainers() {
296
297            return m_resultContainers;
298        }
299
300        /**
301         * @see org.opencms.ade.containerpage.client.CmsContainerpageController.I_PageContentVisitor#handleElement(org.opencms.ade.containerpage.client.ui.CmsContainerPageElementPanel)
302         */
303        public void handleElement(CmsContainerPageElementPanel elementWidget) {
304
305            CmsContainerElement element = new CmsContainerElement();
306            element.setClientId(elementWidget.getId());
307            element.setResourceType(elementWidget.getNewType());
308            element.setCreateNew(elementWidget.isCreateNew());
309            element.setModelGroupId(elementWidget.getModelGroupId());
310            element.setSitePath(elementWidget.getSitePath());
311            element.setNewEditorDisabled(elementWidget.isNewEditorDisabled());
312            m_currentElements.add(element);
313        }
314
315    }
316
317    /**
318     * Visitor implementation which is used to gather the container contents for saving.<p>
319     */
320    protected class SaveDataVisitor implements I_PageContentVisitor {
321
322        /** The current container name. */
323        protected String m_containerName;
324
325        /** The contaienr which is currently being processed. */
326        protected CmsContainer m_currentContainer;
327
328        /** The list of collected containers. */
329        protected List<CmsContainer> m_resultContainers = new ArrayList<CmsContainer>();
330
331        /** The list of elements of the currently processed container which have already been processed. */
332        List<CmsContainerElement> m_currentElements;
333
334        /**
335         * @see org.opencms.ade.containerpage.client.CmsContainerpageController.I_PageContentVisitor#beginContainer(java.lang.String, org.opencms.ade.containerpage.shared.CmsContainer)
336         */
337        public boolean beginContainer(String name, CmsContainer container) {
338
339            if (container.isDetailView() || ((getData().getDetailId() != null) && !container.isDetailOnly())) {
340                m_currentContainer = null;
341                return false;
342
343            } else {
344                m_currentContainer = container;
345                m_containerName = name;
346                m_currentElements = new ArrayList<CmsContainerElement>();
347                return true;
348            }
349        }
350
351        /**
352         * @see org.opencms.ade.containerpage.client.CmsContainerpageController.I_PageContentVisitor#endContainer()
353         */
354        public void endContainer() {
355
356            CmsContainer container = new CmsContainer(
357                m_containerName,
358                m_currentContainer.getType(),
359                null,
360                m_currentContainer.getWidth(),
361                m_currentContainer.getMaxElements(),
362                m_currentContainer.isDetailViewContainer(),
363                m_currentContainer.isDetailView(),
364                true,
365                m_currentElements,
366                m_currentContainer.getParentContainerName(),
367                m_currentContainer.getParentInstanceId(),
368                m_currentContainer.getSettingPresets());
369
370            container.setRootContainer(isRootContainer(m_currentContainer));
371            m_resultContainers.add(container);
372        }
373
374        /**
375         * Gets the list of collected containers.<p>
376         *
377         * @return the list of containers
378         */
379        public List<CmsContainer> getContainers() {
380
381            return m_resultContainers;
382        }
383
384        /**
385         * @see org.opencms.ade.containerpage.client.CmsContainerpageController.I_PageContentVisitor#handleElement(org.opencms.ade.containerpage.client.ui.CmsContainerPageElementPanel)
386         */
387        public void handleElement(CmsContainerPageElementPanel elementWidget) {
388
389            CmsContainerElement element = new CmsContainerElement();
390            element.setClientId(elementWidget.getId());
391            element.setResourceType(elementWidget.getNewType());
392            element.setCreateNew(elementWidget.isCreateNew());
393            element.setModelGroupId(elementWidget.getModelGroupId());
394            element.setSitePath(elementWidget.getSitePath());
395            element.setNewEditorDisabled(elementWidget.isNewEditorDisabled());
396            m_currentElements.add(element);
397        }
398
399    }
400
401    /**
402     * A type which indicates the locking status of the currently edited container page.<p>
403     */
404    enum LockStatus {
405        /** Locking the resource failed. */
406        failed,
407
408        /** The resource could be successfully locked. */
409        locked,
410
411        /** Locking the resource has not been tried. */
412        unknown
413    }
414
415    /**
416     * A RPC action implementation used to request the data for container-page elements.<p>
417     */
418    private class MultiElementAction extends CmsRpcAction<Map<String, CmsContainerElementData>> {
419
420        /** Call-back executed on response. */
421        private I_CmsSimpleCallback<Map<String, CmsContainerElementData>> m_callback;
422
423        /** The requested client id's. */
424        private Set<String> m_clientIds;
425
426        /**
427        "         * Constructor.<p>
428         *
429         * @param clientIds the client id's
430         * @param callback the call-back
431         */
432        public MultiElementAction(
433            Set<String> clientIds,
434            I_CmsSimpleCallback<Map<String, CmsContainerElementData>> callback) {
435
436            super();
437            m_clientIds = clientIds;
438            m_callback = callback;
439        }
440
441        /**
442         * @see org.opencms.gwt.client.rpc.CmsRpcAction#execute()
443         */
444        @Override
445        public void execute() {
446
447            Map<String, CmsContainerElementData> result = new HashMap<String, CmsContainerElementData>();
448            List<String> neededIds = new ArrayList<String>();
449            Iterator<String> it = m_clientIds.iterator();
450            while (it.hasNext()) {
451                String clientId = it.next();
452                if (m_elements.containsKey(clientId)) {
453                    result.put(clientId, m_elements.get(clientId));
454                } else {
455                    neededIds.add(clientId);
456                }
457            }
458            if (neededIds.size() == 0) {
459                m_callback.execute(result);
460            } else {
461                getContainerpageService().getElementsData(
462                    getData().getRpcContext(),
463                    getData().getDetailId(),
464                    getRequestParams(),
465                    m_clientIds,
466                    getPageState(),
467                    false,
468                    null,
469                    getLocale(),
470                    this);
471            }
472
473        }
474
475        /**
476         * @see org.opencms.gwt.client.rpc.CmsRpcAction#onResponse(java.lang.Object)
477         */
478        @Override
479        protected void onResponse(Map<String, CmsContainerElementData> result) {
480
481            if (result != null) {
482                addElements(result);
483                Map<String, CmsContainerElementData> elements = new HashMap<String, CmsContainerElementData>();
484                Iterator<String> it = m_clientIds.iterator();
485                while (it.hasNext()) {
486                    String clientId = it.next();
487                    elements.put(clientId, m_elements.get(clientId));
488                }
489                m_callback.execute(elements);
490            }
491        }
492
493    }
494
495    /**
496     * A RPC action implementation used to reload the data for a container-page element.<p>
497     */
498    private class ReloadElementAction extends CmsRpcAction<Map<String, CmsContainerElementData>> {
499
500        /** The callback to execute after the reload. */
501        private Runnable m_callback;
502
503        /** The requested client id's. */
504        private Set<String> m_clientIds;
505
506        /**
507         * Constructor.<p>
508         *
509         * @param clientIds the client id's to reload
510         * @param callback the callback to execute after the reload
511         */
512        public ReloadElementAction(Set<String> clientIds, Runnable callback) {
513
514            super();
515            m_clientIds = clientIds;
516            m_callback = callback;
517        }
518
519        /**
520         * @see org.opencms.gwt.client.rpc.CmsRpcAction#execute()
521         */
522        @Override
523        public void execute() {
524
525            getContainerpageService().getElementsData(
526                getData().getRpcContext(),
527                getData().getDetailId(),
528                getRequestParams(),
529                m_clientIds,
530                getPageState(),
531                false,
532                null,
533                getLocale(),
534                this);
535        }
536
537        /**
538         * @see org.opencms.gwt.client.rpc.CmsRpcAction#onResponse(java.lang.Object)
539         */
540        @Override
541        protected void onResponse(Map<String, CmsContainerElementData> result) {
542
543            if (result == null) {
544                return;
545            }
546            addElements(result);
547            Iterator<org.opencms.ade.containerpage.client.ui.CmsContainerPageElementPanel> it = getAllDragElements().iterator();
548            boolean reloadMarkerFound = false;
549            while (it.hasNext()) {
550                org.opencms.ade.containerpage.client.ui.CmsContainerPageElementPanel containerElement = it.next();
551                if (!m_clientIds.contains(containerElement.getId())) {
552                    continue;
553                }
554                try {
555                    CmsContainerPageElementPanel replacer = replaceContainerElement(
556                        containerElement,
557                        result.get(containerElement.getId()));
558                    if (replacer.getElement().getInnerHTML().contains(CmsGwtConstants.FORMATTER_RELOAD_MARKER)) {
559                        reloadMarkerFound = true;
560                    }
561                } catch (Exception e) {
562                    CmsDebugLog.getInstance().printLine("trying to replace");
563                    CmsDebugLog.getInstance().printLine(e.getLocalizedMessage());
564                }
565
566            }
567            if (isGroupcontainerEditing()) {
568                getGroupEditor().updateBackupElements(result);
569                getGroupcontainer().refreshHighlighting();
570            } else {
571                if (reloadMarkerFound) {
572                    CmsContainerpageController.get().reloadPage();
573                } else {
574                    long loadTime = result.values().iterator().next().getLoadTime();
575                    setLoadTime(Long.valueOf(loadTime));
576                }
577            }
578            m_handler.updateClipboard(result);
579            resetEditButtons();
580            CmsContainerpageController.get().fireEvent(new CmsContainerpageEvent(EventType.elementEdited));
581            if (m_callback != null) {
582                m_callback.run();
583            }
584        }
585    }
586
587    /**
588     * A RPC action implementation used to request the data for a single container-page element.<p>
589     */
590    private class SingleElementAction extends CmsRpcAction<Map<String, CmsContainerElementData>> {
591
592        /** Always copy createNew elements in case reading data for a clipboard element used as a copy group. */
593        private boolean m_alwaysCopy;
594
595        /** Call-back executed on response. */
596        private I_CmsSimpleCallback<CmsContainerElementData> m_callback;
597        /** The requested client id. */
598        private String m_clientId;
599
600        /** If this action was triggered by drag and drop from a container, this should contain the id of the origin container. */
601        private String m_dndContainer;
602
603        /**
604         * Constructor.<p>
605         *
606         * @param clientId the client id
607         * @param callback the call-back
608         * @param alwaysCopy <code>true</code> in case reading data for a clipboard element used as a copy group
609         */
610        public SingleElementAction(
611            String clientId,
612            boolean alwaysCopy,
613            I_CmsSimpleCallback<CmsContainerElementData> callback) {
614
615            super();
616            m_clientId = clientId;
617            m_callback = callback;
618            m_alwaysCopy = alwaysCopy;
619        }
620
621        /**
622         * @see org.opencms.gwt.client.rpc.CmsRpcAction#execute()
623         */
624        @Override
625        public void execute() {
626
627            List<String> clientIds = new ArrayList<String>();
628            clientIds.add(m_clientId);
629            getContainerpageService().getElementsData(
630                getData().getRpcContext(),
631                getData().getDetailId(),
632                getRequestParams(),
633                clientIds,
634                getPageState(),
635                m_alwaysCopy,
636                m_dndContainer,
637                getLocale(),
638                this);
639        }
640
641        /**
642         * Sets the origin container for the drag and drop case.<p>
643         *
644         * @param containerId the origin container name
645         */
646        public void setDndContainer(String containerId) {
647
648            m_dndContainer = containerId;
649        }
650
651        /**
652         * @see org.opencms.gwt.client.rpc.CmsRpcAction#onResponse(java.lang.Object)
653         */
654        @Override
655        protected void onResponse(Map<String, CmsContainerElementData> result) {
656
657            if (result != null) {
658                addElements(result);
659                long loadTime = result.get(m_clientId).getLoadTime();
660                setLoadTime(Long.valueOf(loadTime));
661                m_callback.execute(result.get(m_clientId));
662            }
663        }
664    }
665
666    /** The client side id/setting-hash seperator. */
667    public static final String CLIENT_ID_SEPERATOR = "#";
668
669    /** Parameter name. */
670    public static final String PARAM_REMOVEMODE = "removemode";
671
672    /** Instance of the data provider. */
673    private static CmsContainerpageController INSTANCE;
674
675    /** The container element data. All requested elements will be cached here.*/
676    protected Map<String, CmsContainerElementData> m_elements;
677
678    /** The new element data by resource type name. */
679    protected Map<String, CmsContainerElementData> m_newElements;
680
681    /** The gallery data update timer. */
682    Timer m_galleryUpdateTimer;
683
684    /** The currently editing group-container editor. */
685    A_CmsGroupEditor m_groupEditor;
686
687    /** The container-page handler. */
688    CmsContainerpageHandler m_handler;
689
690    /** The drag targets within this page. */
691    Map<String, org.opencms.ade.containerpage.client.ui.CmsContainerPageContainer> m_targetContainers;
692
693    /** The container page drag and drop controller. */
694    private I_CmsDNDController m_cntDndController;
695
696    /** The container-page RPC service. */
697    private I_CmsContainerpageServiceAsync m_containerpageService;
698
699    /** The container-page util instance. */
700    private CmsContainerpageUtil m_containerpageUtil;
701
702    /** The container data. */
703    private Map<String, CmsContainer> m_containers;
704
705    /** The XML content editor handler. */
706    private CmsContentEditorHandler m_contentEditorHandler;
707
708    /** The core RPC service instance. */
709    private I_CmsCoreServiceAsync m_coreSvc;
710
711    /** The current edit container level. */
712    private int m_currentEditLevel = -1;
713
714    /** The prefetched data. */
715    private CmsCntPageData m_data;
716
717    /** The DND controller. */
718    private CmsCompositeDNDController m_dndController;
719
720    /** The drag and drop handler. */
721    private CmsDNDHandler m_dndHandler;
722
723    /** Edit button position timer. */
724    private Timer m_editButtonsPositionTimer;
725
726    /** The current element view. */
727    private CmsElementViewInfo m_elementView;
728
729    /** Flag indicating that a content element is being edited. */
730    private boolean m_isContentEditing;
731
732    /** The container page load time. */
733    private long m_loadTime;
734
735    /** The lock error message. */
736    private String m_lockErrorMessage;
737
738    /** The current lock status for the page. */
739    private LockStatus m_lockStatus = LockStatus.unknown;
740
741    /** The max container level. */
742    private int m_maxContainerLevel;
743
744    /** The model group base element id. */
745    private String m_modelGroupElementId;
746
747    /** The browser location at the time the containerpage controller was initialized. */
748    private String m_originalUrl;
749
750    /** Flag if the container-page has changed. */
751    private boolean m_pageChanged;
752
753    /** The publish lock checker. */
754    private CmsPublishLockChecker m_publishLockChecker = new CmsPublishLockChecker(this);
755
756    /** Timer to handle window resize. */
757    private Timer m_resizeTimer;
758
759    /** Handler for small elements. */
760    private CmsSmallElementsHandler m_smallElementsHandler;
761
762    /**
763     * Constructor.<p>
764     */
765    public CmsContainerpageController() {
766
767        m_originalUrl = Window.Location.getHref();
768        INSTANCE = this;
769        try {
770            m_data = (CmsCntPageData)CmsRpcPrefetcher.getSerializedObjectFromDictionary(
771                getContainerpageService(),
772                CmsCntPageData.DICT_NAME);
773            m_elementView = m_data.getElementView();
774            m_modelGroupElementId = m_data.getModelGroupElementId();
775            m_loadTime = m_data.getLoadTime();
776        } catch (SerializationException e) {
777            CmsErrorDialog.handleException(
778                new Exception(
779                    "Deserialization of page data failed. This may be caused by expired java-script resources, please clear your browser cache and try again.",
780                    e));
781        }
782        m_smallElementsHandler = new CmsSmallElementsHandler(getContainerpageService());
783        if (m_data != null) {
784            m_smallElementsHandler.setEditSmallElements(m_data.isEditSmallElementsInitially(), false);
785            m_data.setRpcContext(
786                new CmsContainerPageRpcContext(
787                    CmsCoreProvider.get().getStructureId(),
788                    m_data.getTemplateContextInfo().getCurrentContext()));
789        }
790
791    }
792
793    /**
794     * Returns the data provider instance.<p>
795     *
796     * @return the data provider
797     */
798    public static CmsContainerpageController get() {
799
800        if (INSTANCE == null) {
801            CmsDebugLog.getInstance().printLine("WARNING: The data provider has not been initialized!");
802            return null;
803        }
804        return INSTANCE;
805    }
806
807    /**
808     * Returns the current URI.<p>
809     *
810     * @return the current URI
811     */
812    public static String getCurrentUri() {
813
814        return CmsCoreProvider.get().getUri();
815
816    }
817
818    /**
819     * Returns the server id for a given client element id.<p>
820     *
821     * @param clientId the client id including an optional element settings hash
822     *
823     * @return the server id
824     */
825    public static String getServerId(String clientId) {
826
827        String serverId = clientId;
828        if (clientId.contains(CLIENT_ID_SEPERATOR)) {
829            serverId = clientId.substring(0, clientId.lastIndexOf(CLIENT_ID_SEPERATOR));
830        }
831        return serverId;
832    }
833
834    /**
835     * Checks whether element removal should be confirmed.<p>
836     *
837     * @return true if element removal should be confirmed
838     */
839    public static boolean isConfirmRemove() {
840
841        Map<String, String> params = CmsCoreProvider.get().getAdeParameters();
842        String removeMode = params.get(PARAM_REMOVEMODE);
843        return (removeMode == null) || removeMode.equals("confirm");
844    }
845
846    /**
847     * Adds a handler for container page events.<p>
848     *
849     * @param handler the handler to add
850     */
851    public void addContainerpageEventHandler(I_CmsContainerpageEventHandler handler) {
852
853        CmsCoreProvider.get().getEventBus().addHandler(CmsContainerpageEvent.TYPE, handler);
854    }
855
856    /**
857     * Adds an element specified by it's id to the favorite list.<p>
858     *
859     * @param clientId the element id
860     */
861    public void addToFavoriteList(final String clientId) {
862
863        CmsRpcAction<Void> action = new CmsRpcAction<Void>() {
864
865            /**
866             * @see org.opencms.gwt.client.rpc.CmsRpcAction#execute()
867             */
868            @Override
869            public void execute() {
870
871                getContainerpageService().addToFavoriteList(getData().getRpcContext(), clientId, this);
872            }
873
874            /**
875             * @see org.opencms.gwt.client.rpc.CmsRpcAction#onResponse(java.lang.Object)
876             */
877            @Override
878            protected void onResponse(Void result) {
879
880                CmsNotification.get().send(
881                    Type.NORMAL,
882                    Messages.get().key(Messages.GUI_NOTIFICATION_ADD_TO_FAVORITES_0));
883            }
884        };
885        action.execute();
886    }
887
888    /**
889     * Adds an element specified by it's id to the recent list.<p>
890     *
891     * @param clientId the element id
892     * @param nextAction the action to execute after the element has been added
893     */
894    public void addToRecentList(final String clientId, final Runnable nextAction) {
895
896        CmsRpcAction<Void> action = new CmsRpcAction<Void>() {
897
898            /**
899             * @see org.opencms.gwt.client.rpc.CmsRpcAction#execute()
900             */
901            @Override
902            public void execute() {
903
904                getContainerpageService().addToRecentList(getData().getRpcContext(), clientId, this);
905            }
906
907            /**
908             * @see org.opencms.gwt.client.rpc.CmsRpcAction#onResponse(java.lang.Object)
909             */
910            @Override
911            protected void onResponse(Void result) {
912
913                if (nextAction != null) {
914                    nextAction.run();
915                }
916            }
917        };
918        action.execute();
919    }
920
921    /**
922     * Checks whether GWT widgets are available for all fields of a content.<p>
923     *
924     * @param structureId the structure id of the content
925     * @param resultCallback the callback for the result
926     */
927    public void checkNewWidgetsAvailable(final CmsUUID structureId, final AsyncCallback<Boolean> resultCallback) {
928
929        CmsRpcAction<Boolean> action = new CmsRpcAction<Boolean>() {
930
931            @Override
932            public void execute() {
933
934                start(200, false);
935                getContainerpageService().checkNewWidgetsAvailable(structureId, this);
936            }
937
938            @Override
939            protected void onResponse(Boolean result) {
940
941                stop(false);
942                resultCallback.onSuccess(result);
943            }
944
945            // empty
946        };
947        action.execute();
948
949    }
950
951    /**
952     * Checks for container elements that are no longer present within the DOM.<p>
953     */
954    public void cleanUpContainers() {
955
956        List<String> removed = new ArrayList<String>();
957        for (Entry<String, CmsContainerPageContainer> entry : m_targetContainers.entrySet()) {
958            if (!RootPanel.getBodyElement().isOrHasChild(entry.getValue().getElement())) {
959                removed.add(entry.getKey());
960            }
961        }
962        for (String containerId : removed) {
963            m_targetContainers.remove(containerId);
964            m_containers.remove(containerId);
965        }
966        if (removed.size() > 0) {
967            scheduleGalleryUpdate();
968        }
969    }
970
971    /**
972     * Copies an element and asynchronously returns the structure id of the copy.<p>
973     *
974     * @param id the element id
975     * @param callback the callback for the result
976     */
977    public void copyElement(final String id, final I_CmsSimpleCallback<CmsUUID> callback) {
978
979        CmsRpcAction<CmsUUID> action = new CmsRpcAction<CmsUUID>() {
980
981            @Override
982            public void execute() {
983
984                start(200, false);
985                getContainerpageService().copyElement(
986                    CmsCoreProvider.get().getStructureId(),
987                    new CmsUUID(id),
988                    getData().getLocale(),
989                    this);
990            }
991
992            @Override
993            protected void onResponse(CmsUUID result) {
994
995                stop(false);
996                callback.execute(result);
997            }
998
999        };
1000        action.execute();
1001    }
1002
1003    /**
1004     * Creates a new resource for crag container elements with the status new and opens the content editor.<p>
1005     *
1006     * @param element the container element
1007     * @param inline <code>true</code> to open the inline editor for the given element if available
1008     */
1009    public void createAndEditNewElement(
1010        final org.opencms.ade.containerpage.client.ui.CmsContainerPageElementPanel element,
1011        final boolean inline) {
1012
1013        if (!element.isNew()) {
1014            return;
1015        }
1016
1017        final CmsContainer container = m_containers.get(element.getParentTarget().getContainerId());
1018
1019        m_handler.showPageOverlay();
1020        CmsRpcAction<CmsCreateElementData> action = new CmsRpcAction<CmsCreateElementData>() {
1021
1022            /**
1023             * @see org.opencms.gwt.client.rpc.CmsRpcAction#execute()
1024             */
1025            @Override
1026            public void execute() {
1027
1028                getContainerpageService().checkCreateNewElement(
1029                    CmsCoreProvider.get().getStructureId(),
1030                    element.getId(),
1031                    element.getNewType(),
1032                    container,
1033                    getLocale(),
1034                    this);
1035
1036            }
1037
1038            /**
1039             * @see org.opencms.gwt.client.rpc.CmsRpcAction#onResponse(java.lang.Object)
1040             */
1041            @Override
1042            protected void onResponse(CmsCreateElementData result) {
1043
1044                if (result.needsModelSelection()) {
1045                    getHandler().openModelResourceSelect(element, result.getModelResources());
1046                } else {
1047                    openEditorForNewElement(element, result.getCreatedElement(), inline);
1048                }
1049            }
1050        };
1051        action.execute();
1052    }
1053
1054    /**
1055     * Creates a new resource for drag container elements with the status new and opens the content editor.<p>
1056     *
1057     * @param element the container element
1058     * @param modelResourceStructureId the model resource structure id
1059     */
1060    public void createAndEditNewElement(
1061        final org.opencms.ade.containerpage.client.ui.CmsContainerPageElementPanel element,
1062        final CmsUUID modelResourceStructureId) {
1063
1064        CmsRpcAction<CmsContainerElement> action = new CmsRpcAction<CmsContainerElement>() {
1065
1066            @Override
1067            public void execute() {
1068
1069                getContainerpageService().createNewElement(
1070                    CmsCoreProvider.get().getStructureId(),
1071                    element.getId(),
1072                    element.getNewType(),
1073                    modelResourceStructureId,
1074                    getLocale(),
1075                    this);
1076
1077            }
1078
1079            @Override
1080            protected void onResponse(CmsContainerElement result) {
1081
1082                openEditorForNewElement(element, result, false);
1083
1084            }
1085        };
1086        action.execute();
1087    }
1088
1089    /**
1090     * Creates a new element.<p>
1091     *
1092     * @param element the widget belonging to the element which is currently in memory only
1093     * @param callback the callback to call with the result
1094     */
1095    public void createNewElement(
1096        final CmsContainerPageElementPanel element,
1097        final AsyncCallback<CmsContainerElement> callback) {
1098
1099        CmsRpcAction<CmsContainerElement> action = new CmsRpcAction<CmsContainerElement>() {
1100
1101            @Override
1102            public void execute() {
1103
1104                getContainerpageService().createNewElement(
1105                    CmsCoreProvider.get().getStructureId(),
1106                    element.getId(),
1107                    element.getNewType(),
1108                    null,
1109                    getLocale(),
1110                    this);
1111
1112            }
1113
1114            @Override
1115            protected void onResponse(CmsContainerElement result) {
1116
1117                callback.onSuccess(result);
1118            }
1119        };
1120        action.execute();
1121    }
1122
1123    /**
1124     * Deletes an element from the VFS, removes it from all containers and the client side cache.<p>
1125     *
1126     * @param elementId the element to delete
1127     * @param relatedElementId related element to reload after the element has been deleted
1128     */
1129    public void deleteElement(String elementId, final String relatedElementId) {
1130
1131        elementId = getServerId(elementId);
1132        removeContainerElements(elementId);
1133        addToRecentList(elementId, null);
1134        reloadElements(new String[] {relatedElementId}, () -> {/*do nothing*/});
1135    }
1136
1137    /**
1138     * Disables the inline editing for all content elements but the given one.<p>
1139     *
1140     * @param notThisOne the content element not to disable
1141     */
1142    public void disableInlineEditing(CmsContainerPageElementPanel notThisOne) {
1143
1144        removeEditButtonsPositionTimer();
1145        if (isGroupcontainerEditing()) {
1146            for (Widget element : m_groupEditor.getGroupContainerWidget()) {
1147                if ((element instanceof CmsContainerPageElementPanel) && (element != notThisOne)) {
1148                    ((CmsContainerPageElementPanel)element).removeInlineEditor();
1149                }
1150            }
1151        } else {
1152            for (org.opencms.ade.containerpage.client.ui.CmsContainerPageContainer container : m_targetContainers.values()) {
1153                for (Widget element : container) {
1154                    if ((element instanceof CmsContainerPageElementPanel) && (element != notThisOne)) {
1155                        ((CmsContainerPageElementPanel)element).removeInlineEditor();
1156                    }
1157                }
1158            }
1159        }
1160    }
1161
1162    /**
1163     * Enables the favorites editing drag and drop controller.<p>
1164     *
1165     * @param enable if <code>true</code> favorites editing will enabled, otherwise disabled
1166     * @param dndController the favorites editing drag and drop controller
1167     */
1168    public void enableFavoriteEditing(boolean enable, I_CmsDNDController dndController) {
1169
1170        if (m_dndHandler.isDragging()) {
1171            // never switch drag and drop controllers while dragging
1172            return;
1173        }
1174        if (enable) {
1175            m_dndHandler.setController(dndController);
1176        } else {
1177            m_dndHandler.setController(m_cntDndController);
1178        }
1179
1180    }
1181
1182    /**
1183     * Replaces all element instances of the original element with the new element within the former copy model.<p>
1184     *
1185     * @param originalElementId the original element id
1186     * @param modelGroupParent the model group parent element
1187     * @param elementData the replace element data
1188     */
1189    public void executeCopyModelReplace(
1190        String originalElementId,
1191        Element modelGroupParent,
1192        CmsContainerElementData elementData) {
1193
1194        String serverId = getServerId(originalElementId);
1195        for (CmsContainerPageContainer cont : m_targetContainers.values()) {
1196            if (modelGroupParent.isOrHasChild(cont.getElement())) {
1197                // look for instances of the original element
1198                for (Widget child : cont) {
1199                    if ((child instanceof CmsContainerPageElementPanel)
1200                        && ((CmsContainerPageElementPanel)child).getId().startsWith(serverId)) {
1201                        CmsContainerPageElementPanel replacer = null;
1202                        String elementContent = elementData.getContents().get(cont.getContainerId());
1203                        if ((elementContent != null) && (elementContent.trim().length() > 0)) {
1204                            try {
1205                                replacer = getContainerpageUtil().createElement(elementData, cont, false);
1206                                cont.insert(replacer, cont.getWidgetIndex(child));
1207                                child.removeFromParent();
1208                                initializeSubContainers(replacer);
1209                            } catch (Exception e) {
1210                                //ignore
1211                            }
1212                        }
1213                    }
1214                }
1215            }
1216        }
1217    }
1218
1219    /**
1220     * Fires an event on the core event bus.<p>
1221     *
1222     * @param event the event to fire
1223     */
1224    public void fireEvent(CmsContainerpageEvent event) {
1225
1226        CmsCoreProvider.get().getEventBus().fireEvent(event);
1227
1228    }
1229
1230    /**
1231     * Returns all drag elements of the page.<p>
1232     *
1233     * @return the drag elements
1234     */
1235    public List<org.opencms.ade.containerpage.client.ui.CmsContainerPageElementPanel> getAllDragElements() {
1236
1237        List<org.opencms.ade.containerpage.client.ui.CmsContainerPageElementPanel> result = new ArrayList<org.opencms.ade.containerpage.client.ui.CmsContainerPageElementPanel>();
1238        Iterator<org.opencms.ade.containerpage.client.ui.CmsContainerPageContainer> it = m_targetContainers.values().iterator();
1239        while (it.hasNext()) {
1240            result.addAll(it.next().getAllDragElements());
1241        }
1242        if (isGroupcontainerEditing()) {
1243            Iterator<Widget> itSub = m_groupEditor.getGroupContainerWidget().iterator();
1244            while (itSub.hasNext()) {
1245                Widget w = itSub.next();
1246                if (w instanceof org.opencms.ade.containerpage.client.ui.CmsContainerPageElementPanel) {
1247                    result.add((org.opencms.ade.containerpage.client.ui.CmsContainerPageElementPanel)w);
1248                }
1249            }
1250        }
1251        return result;
1252    }
1253
1254    /**
1255     * Returns the data for the requested element, or <code>null</code> if the element has not been cached yet.<p>
1256     *
1257     * @param clientId the element id
1258     *
1259     * @return the element data
1260     */
1261    public CmsContainerElementData getCachedElement(String clientId) {
1262
1263        if (m_elements.containsKey(clientId)) {
1264            return m_elements.get(clientId);
1265        }
1266        return null;
1267    }
1268
1269    /**
1270     * Returns the data for the requested element, or <code>null</code> if the element has not been cached yet.<p>
1271     *
1272     * @param resourceTypeName the element resource type
1273     *
1274     * @return the element data
1275     */
1276    public CmsContainerElementData getCachedNewElement(String resourceTypeName) {
1277
1278        if (m_newElements.containsKey(resourceTypeName)) {
1279            return m_newElements.get(resourceTypeName);
1280        }
1281        return null;
1282    }
1283
1284    /**
1285     * Returns the container data of container with the given name.
1286     *
1287     * @param containerName the container name
1288     *
1289     * @return the container data
1290     */
1291    public CmsContainer getContainer(String containerName) {
1292
1293        return m_containers.get(containerName);
1294    }
1295
1296    /**
1297     * Gets the container element widget to which the given element belongs, or Optional.absent if none could be found.<p>
1298     *
1299     * @param element the element for which the container element widget should be found
1300     *
1301     * @return the container element widget, or Optional.absent if none can be found
1302     */
1303    public Optional<CmsContainerPageElementPanel> getContainerElementWidgetForElement(Element element) {
1304
1305        final Element parentContainerElement = CmsDomUtil.getAncestor(
1306            element,
1307            I_CmsLayoutBundle.INSTANCE.dragdropCss().dragElement());
1308        if (parentContainerElement == null) {
1309            return Optional.absent();
1310        }
1311        final List<CmsContainerPageElementPanel> result = Lists.newArrayList();
1312        processPageContent(new I_PageContentVisitor() {
1313
1314            public boolean beginContainer(String name, CmsContainer container) {
1315
1316                // we don't need to look into the container if we have already found our container element
1317                return result.isEmpty();
1318            }
1319
1320            public void endContainer() {
1321
1322                // do nothing
1323            }
1324
1325            public void handleElement(CmsContainerPageElementPanel current) {
1326
1327                if ((current.getElement() == parentContainerElement) && result.isEmpty()) {
1328                    result.add(current);
1329                }
1330            }
1331        });
1332        if (result.isEmpty()) {
1333            return Optional.absent();
1334        } else {
1335            return Optional.fromNullable(result.get(0));
1336        }
1337    }
1338
1339    /**
1340     * Returns the container-page RPC service.<p>
1341     *
1342     * @return the container-page service
1343     */
1344    public I_CmsContainerpageServiceAsync getContainerpageService() {
1345
1346        if (m_containerpageService == null) {
1347            m_containerpageService = GWT.create(I_CmsContainerpageService.class);
1348            String serviceUrl = CmsCoreProvider.get().link("org.opencms.ade.containerpage.CmsContainerpageService.gwt");
1349            ((ServiceDefTarget)m_containerpageService).setServiceEntryPoint(serviceUrl);
1350        }
1351        return m_containerpageService;
1352    }
1353
1354    /**
1355     * Returns the {@link org.opencms.ade.containerpage.client.CmsContainerpageUtil}.<p>
1356     *
1357     * @return the containerpage-util
1358     */
1359    public CmsContainerpageUtil getContainerpageUtil() {
1360
1361        return m_containerpageUtil;
1362    }
1363
1364    /**
1365     * Returns the containers.<p>
1366     *
1367     * @return the containers
1368     */
1369    public Map<String, CmsContainer> getContainers() {
1370
1371        return m_containers;
1372    }
1373
1374    /**
1375     * Returns the container drag target by name (HTML id attribute).<p>
1376     *
1377     * @param containerName the container name
1378     * @return the drag target
1379     */
1380    public org.opencms.ade.containerpage.client.ui.CmsContainerPageContainer getContainerTarget(String containerName) {
1381
1382        return m_targetContainers.get(containerName);
1383    }
1384
1385    /**
1386     * Returns a map of the container drag targets.<p>
1387     *
1388     * @return the drag targets
1389     */
1390    public Map<String, org.opencms.ade.containerpage.client.ui.CmsContainerPageContainer> getContainerTargets() {
1391
1392        Map<String, org.opencms.ade.containerpage.client.ui.CmsContainerPageContainer> result = new HashMap<String, org.opencms.ade.containerpage.client.ui.CmsContainerPageContainer>();
1393        for (Entry<String, org.opencms.ade.containerpage.client.ui.CmsContainerPageContainer> entry : m_targetContainers.entrySet()) {
1394            if (entry.getValue().isEditable()
1395                && (!isDetailPage() || (entry.getValue().isDetailOnly() || entry.getValue().isDetailView()))) {
1396                result.put(entry.getKey(), entry.getValue());
1397            }
1398        }
1399        return result;
1400    }
1401
1402    /**
1403     * Returns the type of container with the given name.<p>
1404     *
1405     * @param containerName the container name
1406     *
1407     * @return the container type
1408     */
1409    public String getContainerType(String containerName) {
1410
1411        return getContainer(containerName).getType();
1412    }
1413
1414    /**
1415     * Returns the XML content editor handler.<p>
1416     *
1417     * @return the XML content editor handler
1418     */
1419    public CmsContentEditorHandler getContentEditorHandler() {
1420
1421        return m_contentEditorHandler;
1422    }
1423
1424    /**
1425     * Returns the prefetched data.<p>
1426     *
1427     * @return the prefetched data
1428     */
1429    public CmsCntPageData getData() {
1430
1431        return m_data;
1432    }
1433
1434    /**
1435     * Returns the delete options for the given content element.<p>
1436     *
1437     * @param clientId the content id
1438     * @param callback the callback to execute
1439     */
1440    public void getDeleteOptions(final String clientId, final I_CmsSimpleCallback<CmsDialogOptionsAndInfo> callback) {
1441
1442        CmsRpcAction<CmsDialogOptionsAndInfo> action = new CmsRpcAction<CmsDialogOptionsAndInfo>() {
1443
1444            @Override
1445            public void execute() {
1446
1447                getContainerpageService().getDeleteOptions(
1448                    clientId,
1449                    getData().getRpcContext().getPageStructureId(),
1450                    getData().getRequestParams(),
1451                    this);
1452            }
1453
1454            @Override
1455            protected void onResponse(CmsDialogOptionsAndInfo result) {
1456
1457                callback.execute(result);
1458            }
1459        };
1460        action.execute();
1461    }
1462
1463    /**
1464     * Gets the DND controller.<p>
1465     *
1466     * @return the DND controller
1467     */
1468    public CmsCompositeDNDController getDndController() {
1469
1470        return m_dndController;
1471    }
1472
1473    /**
1474     * Returns the drag and drop handler.<p>
1475     *
1476     * @return the drag and drop handler
1477     */
1478    public CmsDNDHandler getDndHandler() {
1479
1480        return m_dndHandler;
1481    }
1482
1483    /**
1484     * Returns the edit options for the given content element.<p>
1485     *
1486     * @param clientId the content id
1487     * @param isListElement in case a list element, not a container element is about to be edited
1488     * @param callback the callback to execute
1489     */
1490    public void getEditOptions(
1491        final String clientId,
1492        final boolean isListElement,
1493        final I_CmsSimpleCallback<CmsDialogOptionsAndInfo> callback) {
1494
1495        CmsRpcAction<CmsDialogOptionsAndInfo> action = new CmsRpcAction<CmsDialogOptionsAndInfo>() {
1496
1497            @Override
1498            public void execute() {
1499
1500                getContainerpageService().getEditOptions(
1501                    clientId,
1502                    getData().getRpcContext().getPageStructureId(),
1503                    getData().getRequestParams(),
1504                    isListElement,
1505                    this);
1506            }
1507
1508            @Override
1509            protected void onResponse(CmsDialogOptionsAndInfo result) {
1510
1511                callback.execute(result);
1512            }
1513        };
1514        action.execute();
1515    }
1516
1517    /**
1518     * Requests the data for a container element specified by the client id for drag and drop from a container. The data will be provided to the given call-back function.<p>
1519     *
1520     * @param clientId the element id
1521     * @param containerId the id of the container from which the element is being dragged
1522     * @param alwaysCopy <code>true</code> in case reading data for a clipboard element used as a copy group
1523     * @param callback the call-back to execute with the requested data
1524     */
1525    public void getElementForDragAndDropFromContainer(
1526        final String clientId,
1527        final String containerId,
1528        boolean alwaysCopy,
1529        final I_CmsSimpleCallback<CmsContainerElementData> callback) {
1530
1531        SingleElementAction action = new SingleElementAction(clientId, alwaysCopy, callback);
1532        action.setDndContainer(containerId);
1533        action.execute();
1534    }
1535
1536    /**
1537     * Requests the data for container elements specified by the client id. The data will be provided to the given call-back function.<p>
1538     *
1539     * @param clientIds the element id's
1540     * @param callback the call-back to execute with the requested data
1541     */
1542    public void getElements(Set<String> clientIds, I_CmsSimpleCallback<Map<String, CmsContainerElementData>> callback) {
1543
1544        MultiElementAction action = new MultiElementAction(clientIds, callback);
1545        action.execute();
1546    }
1547
1548    /**
1549     * Requests the element settings config data for a container element specified by the client id. The data will be provided to the given call-back function.<p>
1550     *
1551     * @param clientId the element id
1552     * @param containerId the parent container id
1553     * @param callback the call-back to execute with the requested data
1554     */
1555    public void getElementSettingsConfig(
1556        final String clientId,
1557        final String containerId,
1558        final I_CmsSimpleCallback<CmsElementSettingsConfig> callback) {
1559
1560        CmsRpcAction<CmsElementSettingsConfig> action = new CmsRpcAction<CmsElementSettingsConfig>() {
1561
1562            /**
1563             * @see org.opencms.gwt.client.rpc.CmsRpcAction#execute()
1564             */
1565            @Override
1566            public void execute() {
1567
1568                start(100, true);
1569                getContainerpageService().getElementSettingsConfig(
1570                    getData().getRpcContext(),
1571                    clientId,
1572                    containerId,
1573                    getPageState(),
1574                    getLocale(),
1575                    this);
1576
1577            }
1578
1579            /**
1580             * @see org.opencms.gwt.client.rpc.CmsRpcAction#onResponse(java.lang.Object)
1581             */
1582            @Override
1583            protected void onResponse(CmsElementSettingsConfig result) {
1584
1585                if (result != null) {
1586                    callback.execute(result);
1587                }
1588                stop(false);
1589            }
1590        };
1591        action.execute();
1592    }
1593
1594    /**
1595     * Returns the current element view.<p>
1596     *
1597     * @return the current element view
1598     */
1599    public CmsElementViewInfo getElementView() {
1600
1601        return m_elementView;
1602    }
1603
1604    /**
1605     * Retrieves a container element with a given set of settings.<p>
1606     *
1607     * @param clientId the id of the container element
1608     * @param settings the set of settings
1609     *
1610     * @param callback the callback which should be executed when the element has been loaded
1611     */
1612    public void getElementWithSettings(
1613        final String clientId,
1614        final Map<String, String> settings,
1615        final I_CmsSimpleCallback<CmsContainerElementData> callback) {
1616
1617        CmsRpcAction<CmsContainerElementData> action = new CmsRpcAction<CmsContainerElementData>() {
1618
1619            /**
1620             * @see org.opencms.gwt.client.rpc.CmsRpcAction#execute()
1621             */
1622            @Override
1623            public void execute() {
1624
1625                start(200, false);
1626                getContainerpageService().getElementWithSettings(
1627                    getData().getRpcContext(),
1628                    getData().getDetailId(),
1629                    getRequestParams(),
1630                    clientId,
1631                    settings,
1632                    getPageState(),
1633                    getLocale(),
1634                    this);
1635
1636            }
1637
1638            /**
1639             * @see org.opencms.gwt.client.rpc.CmsRpcAction#onResponse(java.lang.Object)
1640             */
1641            @Override
1642            protected void onResponse(CmsContainerElementData result) {
1643
1644                stop(false);
1645                if (result != null) {
1646                    // cache the loaded element
1647                    m_elements.put(result.getClientId(), result);
1648                }
1649                callback.execute(result);
1650            }
1651
1652        };
1653        action.execute();
1654
1655    }
1656
1657    /**
1658     * Returns the group-container element being edited.<p>
1659     *
1660     * @return the group-container
1661     */
1662    public CmsGroupContainerElementPanel getGroupcontainer() {
1663
1664        return m_groupEditor.getGroupContainerWidget();
1665    }
1666
1667    /**
1668     * Returns the id of the currently edited group-container.<p>
1669     *
1670     * @return the group-container id, or <code>null</code> if no editing is taking place
1671     */
1672    public String getGroupcontainerId() {
1673
1674        if (m_groupEditor != null) {
1675            return m_groupEditor.getGroupContainerWidget().getContainerId();
1676        }
1677        return null;
1678    }
1679
1680    /**
1681     * Returns the container-page handler.<p>
1682     *
1683     * @return the container-page handler
1684     */
1685    public CmsContainerpageHandler getHandler() {
1686
1687        return m_handler;
1688    }
1689
1690    /**
1691     * Returns the time off page load.<p>
1692     *
1693     * @return the time stamp
1694     */
1695    public long getLoadTime() {
1696
1697        return m_loadTime;
1698    }
1699
1700    /**
1701     * Gets the lock error message.<p>
1702     *
1703     * @return the lock error message
1704     */
1705    public String getLockErrorMessage() {
1706
1707        return m_lockErrorMessage;
1708    }
1709
1710    /**
1711     * Returns the model group base element id.<p>
1712     *
1713     * @return the model group base element id
1714     */
1715    public String getModelGroupElementId() {
1716
1717        return m_modelGroupElementId;
1718    }
1719
1720    /**
1721     * Collects all container elements which are model groups.<p>
1722     *
1723     * @return the list of model group container elements
1724     */
1725    public List<CmsContainerPageElementPanel> getModelGroups() {
1726
1727        final List<CmsContainerPageElementPanel> result = Lists.newArrayList();
1728
1729        processPageContent(new I_PageContentVisitor() {
1730
1731            public boolean beginContainer(String name, CmsContainer container) {
1732
1733                return true;
1734            }
1735
1736            public void endContainer() {
1737
1738                // do nothing
1739            }
1740
1741            public void handleElement(CmsContainerPageElementPanel element) {
1742
1743                if (element.isModelGroup()) {
1744                    result.add(element);
1745                }
1746            }
1747        });
1748        return result;
1749    }
1750
1751    /**
1752     * Returns the element data for a resource type representing a new element.<p>
1753     *
1754     * @param resourceType the resource type name
1755     * @param callback the callback to execute with the new element data
1756     */
1757    public void getNewElement(final String resourceType, final I_CmsSimpleCallback<CmsContainerElementData> callback) {
1758
1759        CmsRpcAction<CmsContainerElementData> action = new CmsRpcAction<CmsContainerElementData>() {
1760
1761            @Override
1762            public void execute() {
1763
1764                getContainerpageService().getNewElementData(
1765                    getData().getRpcContext(),
1766                    getData().getDetailId(),
1767                    getRequestParams(),
1768                    resourceType,
1769                    getPageState(),
1770                    getLocale(),
1771                    this);
1772            }
1773
1774            @Override
1775            protected void onResponse(CmsContainerElementData result) {
1776
1777                m_elements.put(result.getClientId(), result);
1778                callback.execute(result);
1779            }
1780        };
1781        action.execute();
1782    }
1783
1784    /**
1785     * Fetches the options for creating a new element from the edit handler.<p>
1786     *
1787     * @param clientId the client id of the element
1788     * @param callback the callback which is called with the result
1789     */
1790    public void getNewOptions(final String clientId, final I_CmsSimpleCallback<CmsDialogOptionsAndInfo> callback) {
1791
1792        CmsRpcAction<CmsDialogOptionsAndInfo> action = new CmsRpcAction<CmsDialogOptionsAndInfo>() {
1793
1794            @Override
1795            public void execute() {
1796
1797                getContainerpageService().getNewOptions(
1798                    clientId,
1799                    getData().getRpcContext().getPageStructureId(),
1800                    getData().getRequestParams(),
1801                    this);
1802            }
1803
1804            @Override
1805            protected void onResponse(CmsDialogOptionsAndInfo result) {
1806
1807                callback.execute(result);
1808            }
1809        };
1810
1811        action.execute();
1812    }
1813
1814    /**
1815     * Produces the "return code", which is needed to return to the current page from the sitemap.<p>
1816     *
1817     * @return the return code
1818     */
1819    public String getReturnCode() {
1820
1821        CmsUUID ownId = CmsCoreProvider.get().getStructureId();
1822        CmsUUID detailId = m_data.getDetailId();
1823        if (detailId != null) {
1824            return "" + ownId + ":" + detailId;
1825        } else {
1826            return "" + ownId;
1827        }
1828    }
1829
1830    /**
1831     * Returns the deserialized element data.<p>
1832     *
1833     * @param data the data to deserialize
1834     *
1835     * @return the container element
1836     * @throws SerializationException if deserialization fails
1837     */
1838    public CmsContainer getSerializedContainer(String data) throws SerializationException {
1839
1840        return (CmsContainer)CmsRpcPrefetcher.getSerializedObjectFromString(getContainerpageService(), data);
1841    }
1842
1843    /**
1844     * Returns the deserialized element data.<p>
1845     *
1846     * @param data the data to deserialize
1847     *
1848     * @return the container element
1849     * @throws SerializationException if deserialization fails
1850     */
1851    public CmsContainerElement getSerializedElement(String data) throws SerializationException {
1852
1853        return (CmsContainerElement)CmsRpcPrefetcher.getSerializedObjectFromString(getContainerpageService(), data);
1854    }
1855
1856    /**
1857     * Gets the handler for small elements.<p>
1858     *
1859     * @return the small elements handler
1860     */
1861    public CmsSmallElementsHandler getSmallElementsHandler() {
1862
1863        return m_smallElementsHandler;
1864    }
1865
1866    /**
1867     * Gets the view with the given id.<p>
1868     *
1869     * @param value the view id as a string
1870     *
1871     * @return the view with the given id, or null if no such view is available
1872     */
1873    public CmsElementViewInfo getView(String value) {
1874
1875        for (CmsElementViewInfo info : m_data.getElementViews()) {
1876            if (info.getElementViewId().toString().equals(value)) {
1877                return info;
1878            }
1879        }
1880        return null;
1881    }
1882
1883    /**
1884     * Handler that gets called when the template context setting of an element was changed by the user.<p>
1885     *
1886     * @param element the element whose template context setting was changed
1887     *
1888     * @param newValue the new value of the setting
1889     */
1890    public void handleChangeTemplateContext(final CmsContainerPageElementPanel element, final String newValue) {
1891
1892        if (CmsStringUtil.isEmptyOrWhitespaceOnly(newValue) || CmsTemplateContextInfo.EMPTY_VALUE.equals(newValue)) {
1893            if (CmsInheritanceContainerEditor.getInstance() != null) {
1894                CmsInheritanceContainerEditor.getInstance().removeElement(element);
1895            } else {
1896                removeElement(element, ElementRemoveMode.silent);
1897            }
1898        }
1899    }
1900
1901    /**
1902     * Asks the user for confirmation before removing a container page element.<p>
1903     *
1904     * @param element the element for which the user should confirm the removal
1905     */
1906    public void handleConfirmRemove(final CmsContainerPageElementPanel element) {
1907
1908        if (element.isNew()) {
1909            element.removeFromParent();
1910            cleanUpContainers();
1911            setPageChanged();
1912            return;
1913        }
1914        checkElementReferences(element, new AsyncCallback<CmsRemovedElementStatus>() {
1915
1916            public void onFailure(Throwable caught) {
1917
1918                // ignore, will never be executed
1919
1920            }
1921
1922            public void onSuccess(CmsRemovedElementStatus status) {
1923
1924                boolean showDeleteCheckbox = status.isDeletionCandidate();
1925                ElementDeleteMode deleteMode = status.getElementDeleteMode();
1926                if (deleteMode == null) {
1927                    deleteMode = getData().getDeleteMode();
1928                }
1929                CmsConfirmRemoveDialog removeDialog = new CmsConfirmRemoveDialog(
1930                    status.getElementInfo(),
1931                    showDeleteCheckbox,
1932                    deleteMode,
1933                    new AsyncCallback<Boolean>() {
1934
1935                        public void onFailure(Throwable caught) {
1936
1937                            element.removeHighlighting();
1938                        }
1939
1940                        public void onSuccess(Boolean shouldDeleteResource) {
1941
1942                            Runnable[] nextActions = new Runnable[] {};
1943
1944                            if (shouldDeleteResource.booleanValue()) {
1945                                final CmsRpcAction<Void> deleteAction = new CmsRpcAction<Void>() {
1946
1947                                    @Override
1948                                    public void execute() {
1949
1950                                        start(200, true);
1951
1952                                        CmsUUID id = new CmsUUID(getServerId(element.getId()));
1953                                        CmsCoreProvider.getVfsService().deleteResource(id, this);
1954                                    }
1955
1956                                    @Override
1957                                    public void onResponse(Void result) {
1958
1959                                        stop(true);
1960                                    }
1961                                };
1962                                nextActions = new Runnable[] {new Runnable() {
1963
1964                                    public void run() {
1965
1966                                        deleteAction.execute();
1967                                    }
1968                                }};
1969                            }
1970                            I_CmsDropContainer container = element.getParentTarget();
1971                            element.removeFromParent();
1972                            if (container instanceof CmsContainerPageContainer) {
1973                                ((CmsContainerPageContainer)container).checkEmptyContainers();
1974                            }
1975                            cleanUpContainers();
1976                            setPageChanged(nextActions);
1977                        }
1978                    });
1979                removeDialog.center();
1980            }
1981
1982        });
1983    }
1984
1985    /**
1986     * Calls the edit handler to handle the delete action.<p>
1987     *
1988     * @param clientId the content client id
1989     * @param deleteOption the selected delete option
1990     * @param callback the callback to execute after the delete
1991     */
1992    public void handleDelete(
1993        final String clientId,
1994        final String deleteOption,
1995        final I_CmsSimpleCallback<Void> callback) {
1996
1997        CmsRpcAction<Void> action = new CmsRpcAction<Void>() {
1998
1999            @Override
2000            public void execute() {
2001
2002                getContainerpageService().handleDelete(
2003                    clientId,
2004                    deleteOption,
2005                    getData().getRpcContext().getPageStructureId(),
2006                    getData().getRequestParams(),
2007                    this);
2008            }
2009
2010            @Override
2011            protected void onResponse(Void result) {
2012
2013                if (callback != null) {
2014                    callback.execute(result);
2015                }
2016            }
2017        };
2018        action.execute();
2019    }
2020
2021    /**
2022     * Returns if the selection button is active.<p>
2023     *
2024     * @return <code>true</code> if the selection button is active
2025     */
2026    public boolean hasActiveSelection() {
2027
2028        return m_handler.hasActiveSelection();
2029    }
2030
2031    /**
2032     * Returns if the page has changed.<p>
2033     *
2034     * @return <code>true</code> if the page has changed
2035     */
2036    public boolean hasPageChanged() {
2037
2038        return m_pageChanged;
2039    }
2040
2041    /**
2042     * Hides list collector direct edit buttons, if present.<p>
2043     */
2044    public void hideEditableListButtons() {
2045
2046        removeEditButtonsPositionTimer();
2047        for (org.opencms.ade.containerpage.client.ui.CmsContainerPageContainer container : m_targetContainers.values()) {
2048            container.hideEditableListButtons();
2049        }
2050    }
2051
2052    /**
2053     * Initializes the controller.<p>
2054     *
2055     * @param handler the container-page handler
2056     * @param dndHandler the drag and drop handler
2057     * @param contentEditorHandler the XML content editor handler
2058     * @param containerpageUtil the container-page utility
2059     */
2060    public void init(
2061        CmsContainerpageHandler handler,
2062        CmsDNDHandler dndHandler,
2063        CmsContentEditorHandler contentEditorHandler,
2064        CmsContainerpageUtil containerpageUtil) {
2065
2066        Window.addResizeHandler(new ResizeHandler() {
2067
2068            public void onResize(ResizeEvent event) {
2069
2070                CmsContainerpageController.this.onResize();
2071            }
2072        });
2073        m_containerpageUtil = containerpageUtil;
2074        m_handler = handler;
2075        m_contentEditorHandler = contentEditorHandler;
2076        m_dndHandler = dndHandler;
2077        m_cntDndController = m_dndHandler.getController();
2078
2079        m_elements = new HashMap<String, CmsContainerElementData>();
2080        m_newElements = new HashMap<String, CmsContainerElementData>();
2081        m_containers = new HashMap<String, CmsContainer>();
2082        if (m_data == null) {
2083            m_handler.m_editor.disableEditing(Messages.get().key(Messages.ERR_READING_CONTAINER_PAGE_DATA_0));
2084            CmsErrorDialog dialog = new CmsErrorDialog(
2085                Messages.get().key(Messages.ERR_READING_CONTAINER_PAGE_DATA_0),
2086                null);
2087            dialog.center();
2088            return;
2089        }
2090        // ensure any embedded flash players are set opaque so UI elements may be placed above them
2091        CmsDomUtil.fixFlashZindex(RootPanel.getBodyElement());
2092        m_targetContainers = m_containerpageUtil.consumeContainers(m_containers, RootPanel.getBodyElement());
2093        updateContainerLevelInfo();
2094        resetEditButtons();
2095        Event.addNativePreviewHandler(new NativePreviewHandler() {
2096
2097            public void onPreviewNativeEvent(NativePreviewEvent event) {
2098
2099                previewNativeEvent(event);
2100            }
2101        });
2102        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(m_data.getNoEditReason())) {
2103            m_handler.m_editor.disableEditing(m_data.getNoEditReason());
2104        } else {
2105            checkLockInfo();
2106        }
2107
2108        // initialize the browser history handler
2109        History.addValueChangeHandler(new ValueChangeHandler<String>() {
2110
2111            public void onValueChange(ValueChangeEvent<String> event) {
2112
2113                String historyToken = event.getValue();
2114                if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(historyToken)) {
2115                    getContentEditorHandler().openEditorForHistory(historyToken);
2116                } else {
2117                    getContentEditorHandler().closeContentEditor();
2118                }
2119            }
2120        });
2121        AsyncCallback<Void> doNothing = new AsyncCallback<Void>() {
2122
2123            public void onFailure(Throwable caught) {
2124
2125                // nothing to do
2126            }
2127
2128            public void onSuccess(Void result) {
2129
2130                // nothing to do
2131            }
2132        };
2133        getContainerpageService().setLastPage(CmsCoreProvider.get().getStructureId(), m_data.getDetailId(), doNothing);
2134
2135        // check if there is already a history item available
2136        String historyToken = History.getToken();
2137        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(historyToken)) {
2138            m_contentEditorHandler.openEditorForHistory(historyToken);
2139        }
2140
2141        updateGalleryData(false, null);
2142        addContainerpageEventHandler(event -> {
2143            updateDetailPreviewStyles();
2144        });
2145        updateDetailPreviewStyles();
2146        updateButtonsForCurrentView();
2147        startPublishLockCheck();
2148    }
2149
2150    /**
2151     * Checks for element sub containers.<p>
2152     *
2153     * @param containerElement the container element
2154     */
2155    public void initializeSubContainers(CmsContainerPageElementPanel containerElement) {
2156
2157        int containerCount = m_targetContainers.size();
2158        m_targetContainers.putAll(m_containerpageUtil.consumeContainers(m_containers, containerElement.getElement()));
2159        updateContainerLevelInfo();
2160        if (m_targetContainers.size() > containerCount) {
2161            // in case new containers have been added, the gallery data needs to be updated
2162            scheduleGalleryUpdate();
2163        }
2164    }
2165
2166    /**
2167     * Returns if the given container is editable.<p>
2168     *
2169     * @param dragParent the parent container
2170     *
2171     * @return <code>true</code> if the given container is editable
2172     */
2173    public boolean isContainerEditable(I_CmsDropContainer dragParent) {
2174
2175        boolean isSubElement = dragParent instanceof CmsGroupContainerElementPanel;
2176        boolean isContainerEditable = dragParent.isEditable()
2177            && (isSubElement || !isDetailPage() || dragParent.isDetailView() || dragParent.isDetailOnly());
2178        return isContainerEditable;
2179    }
2180
2181    /**
2182     * Returns the flag indicating that a content element is being edited.<p>
2183     *
2184     * @return the flag indicating that a content element is being edited
2185     */
2186    public boolean isContentEditing() {
2187
2188        return m_isContentEditing;
2189    }
2190
2191    /**
2192     * Returns if this page displays a detail view.<p>
2193     *
2194     * @return <code>true</code> if this page displays a detail view
2195     */
2196    public boolean isDetailPage() {
2197
2198        return m_data.getDetailId() != null;
2199    }
2200
2201    /**
2202     * Checks if the page editing features should be disabled.<p>
2203     *
2204     * @return true if the page editing features should be disabled
2205     */
2206    public boolean isEditingDisabled() {
2207
2208        return (m_data == null)
2209            || CmsStringUtil.isNotEmptyOrWhitespaceOnly(m_data.getNoEditReason())
2210            || (m_lockStatus == LockStatus.failed);
2211    }
2212
2213    /**
2214     * Returns if a group-container is currently being edited.<p>
2215     *
2216     * @return <code>true</code> if a group-container is being edited
2217     */
2218    public boolean isGroupcontainerEditing() {
2219
2220        return m_groupEditor != null;
2221    }
2222
2223    /**
2224     * Checks whether the given element should be inline editable.<p>
2225     *
2226     * @param element the element
2227     * @param dragParent the element parent
2228     *
2229     * @return <code>true</code> if the element should be inline editable
2230     */
2231    public boolean isInlineEditable(CmsContainerPageElementPanel element, I_CmsDropContainer dragParent) {
2232
2233        CmsUUID elemView = element.getElementView();
2234        return !getData().isUseClassicEditor()
2235            && CmsStringUtil.isEmptyOrWhitespaceOnly(element.getNoEditReason())
2236            && hasActiveSelection()
2237            && matchRootView(elemView)
2238            && isContainerEditable(dragParent)
2239            && matchesCurrentEditLevel(dragParent)
2240            && (getData().isModelGroup() || !element.hasModelGroupParent())
2241            && (!(dragParent instanceof CmsGroupContainerElementPanel) || isGroupcontainerEditing());
2242    }
2243
2244    /**
2245     * Method to leave the page without saving.<p>
2246     *
2247     * @param targetUri the new URI to call
2248     */
2249    public void leaveUnsaved(String targetUri) {
2250
2251        setPageChanged(false, true);
2252        Window.Location.assign(targetUri);
2253    }
2254
2255    /**
2256     * Loads the context menu entries.<p>
2257     *
2258     * @param structureId the structure id of the resource to get the context menu entries for
2259     * @param context the ade context (sitemap or containerpae)
2260     */
2261    public void loadContextMenu(final CmsUUID structureId, final AdeContext context) {
2262
2263        /** The RPC menu action for the container page dialog. */
2264        CmsRpcAction<List<CmsContextMenuEntryBean>> menuAction = new CmsRpcAction<List<CmsContextMenuEntryBean>>() {
2265
2266            /**
2267            * @see org.opencms.gwt.client.rpc.CmsRpcAction#execute()
2268            */
2269            @Override
2270            public void execute() {
2271
2272                getCoreService().getContextMenuEntries(structureId, context, this);
2273            }
2274
2275            /**
2276            * @see org.opencms.gwt.client.rpc.CmsRpcAction#onResponse(java.lang.Object)
2277            */
2278            @Override
2279            public void onResponse(List<CmsContextMenuEntryBean> menuBeans) {
2280
2281                m_handler.insertContextMenu(menuBeans, structureId);
2282            }
2283        };
2284        menuAction.execute();
2285
2286    }
2287
2288    /**
2289     * Loads the favorite list and adds the elements to the favorite list widget of the tool-bar menu.<p>
2290     *
2291     * @param callback the call-back to execute with the result data
2292     */
2293    public void loadFavorites(final I_CmsSimpleCallback<List<CmsContainerElementData>> callback) {
2294
2295        CmsRpcAction<List<CmsContainerElementData>> action = new CmsRpcAction<List<CmsContainerElementData>>() {
2296
2297            /**
2298             * @see org.opencms.gwt.client.rpc.CmsRpcAction#execute()
2299             */
2300            @Override
2301            public void execute() {
2302
2303                start(200, true);
2304                getContainerpageService().getFavoriteList(
2305                    CmsCoreProvider.get().getStructureId(),
2306                    getData().getDetailId(),
2307                    getPageState(),
2308                    getLocale(),
2309                    this);
2310            }
2311
2312            /**
2313             * @see org.opencms.gwt.client.rpc.CmsRpcAction#onResponse(java.lang.Object)
2314             */
2315            @Override
2316            protected void onResponse(List<CmsContainerElementData> result) {
2317
2318                stop(false);
2319                addElements(result);
2320                callback.execute(result);
2321            }
2322        };
2323        action.execute();
2324    }
2325
2326    /**
2327     * Loads the recent list and adds the elements to the recent list widget of the tool-bar menu.<p>
2328     *
2329     * @param callback the call-back to execute with the result data
2330     */
2331    public void loadRecent(final I_CmsSimpleCallback<List<CmsContainerElementData>> callback) {
2332
2333        CmsRpcAction<List<CmsContainerElementData>> action = new CmsRpcAction<List<CmsContainerElementData>>() {
2334
2335            /**
2336             * @see org.opencms.gwt.client.rpc.CmsRpcAction#execute()
2337             */
2338            @Override
2339            public void execute() {
2340
2341                start(200, true);
2342                getContainerpageService().getRecentList(
2343                    CmsCoreProvider.get().getStructureId(),
2344                    getData().getDetailId(),
2345                    getPageState(),
2346                    getLocale(),
2347                    this);
2348            }
2349
2350            /**
2351             * @see org.opencms.gwt.client.rpc.CmsRpcAction#onResponse(java.lang.Object)
2352             */
2353            @Override
2354            protected void onResponse(List<CmsContainerElementData> result) {
2355
2356                stop(false);
2357                addElements(result);
2358                callback.execute(result);
2359            }
2360        };
2361        action.execute();
2362    }
2363
2364    /**
2365     * Locks the container-page.<p>
2366     *
2367     * @param callback the callback to execute
2368     */
2369    public void lockContainerpage(final I_CmsSimpleCallback<Boolean> callback) {
2370
2371        if (m_lockStatus == LockStatus.locked) {
2372            callback.execute(Boolean.TRUE);
2373        } else if (m_lockStatus == LockStatus.failed) {
2374            callback.execute(Boolean.FALSE);
2375        } else {
2376            I_CmsSimpleCallback<String> call = new I_CmsSimpleCallback<String>() {
2377
2378                public void execute(String lockError) {
2379
2380                    if (lockError == null) {
2381                        onLockSuccess();
2382                        callback.execute(Boolean.TRUE);
2383                    } else {
2384                        onLockFail(lockError);
2385                        callback.execute(Boolean.FALSE);
2386                    }
2387                }
2388            };
2389
2390            if (getData().getDetailContainerPage() != null) {
2391                CmsCoreProvider.get().lockOrReturnError(getData().getDetailContainerPage(), getLoadTime(), call);
2392            } else {
2393                CmsCoreProvider.get().lockOrReturnError(CmsCoreProvider.get().getStructureId(), getLoadTime(), call);
2394            }
2395        }
2396    }
2397
2398    /**
2399     * Returns true if the view with the given view id and the current view have the same root view.<p>
2400     *
2401     * @param viewIdFromElement the id of a view
2402     * @return true if the root view of the id matches the root view of the current view
2403     */
2404    public boolean matchRootView(CmsUUID viewIdFromElement) {
2405
2406        if (viewIdFromElement == null) {
2407            viewIdFromElement = CmsUUID.getNullUUID();
2408        }
2409        CmsElementViewInfo viewFromElement = getView(viewIdFromElement.toString());
2410        return (viewFromElement != null) && viewFromElement.getRootViewId().equals(m_elementView.getRootViewId());
2411    }
2412
2413    /**
2414     * This method should be called when locking the page has failed.<p>
2415     *
2416     * @param lockError the locking information
2417     */
2418    public void onLockFail(String lockError) {
2419
2420        m_lockStatus = LockStatus.failed;
2421        m_handler.onLockFail(lockError);
2422    }
2423
2424    /**
2425     * This method should be called when locking the page has succeeded.<p>
2426     *
2427     */
2428    public void onLockSuccess() {
2429
2430        assert m_lockStatus == LockStatus.unknown;
2431        m_lockStatus = LockStatus.locked;
2432    }
2433
2434    /**
2435     * Handler which is executed when the window closes.<p>
2436     */
2437    public void onWindowClose() {
2438
2439        // causes synchronous RPC call
2440        unlockContainerpage();
2441    }
2442
2443    /**
2444     * Calls the edit handler to prepare the given content element for editing.<p>
2445     *
2446     * @param clientId the element id
2447     * @param editOption the selected edit option
2448     * @param callback the callback to execute
2449     */
2450    public void prepareForEdit(
2451        final String clientId,
2452        final String editOption,
2453        final I_CmsSimpleCallback<CmsUUID> callback) {
2454
2455        CmsRpcAction<CmsUUID> action = new CmsRpcAction<CmsUUID>() {
2456
2457            @Override
2458            public void execute() {
2459
2460                getContainerpageService().prepareForEdit(
2461                    clientId,
2462                    editOption,
2463                    getData().getRpcContext().getPageStructureId(),
2464                    getData().getRequestParams(),
2465                    this);
2466            }
2467
2468            @Override
2469            protected void onResponse(CmsUUID result) {
2470
2471                callback.execute(result);
2472            }
2473        };
2474        action.execute();
2475    }
2476
2477    /**
2478     * Reinitializes the buttons in the container element menus.<p>
2479     */
2480    public void reinitializeButtons() {
2481
2482        if (isGroupcontainerEditing()) {
2483            m_groupEditor.reinitializeButtons();
2484        } else {
2485            List<CmsContainerPageElementPanel> elemWidgets = getAllContainerPageElements(true);
2486
2487            for (CmsContainerPageElementPanel elemWidget : elemWidgets) {
2488                if (requiresOptionBar(elemWidget, elemWidget.getParentTarget())) {
2489                    getContainerpageUtil().addOptionBar(elemWidget);
2490                } else {
2491                    // otherwise remove any present option bar
2492                    elemWidget.setElementOptionBar(null);
2493                }
2494                elemWidget.showEditableListButtons();
2495            }
2496        }
2497    }
2498
2499    /**
2500     * Re-initializes the inline editing.<p>
2501     */
2502    public void reInitInlineEditing() {
2503
2504        removeEditButtonsPositionTimer();
2505        if ((m_targetContainers == null) || getData().isUseClassicEditor()) {
2506            // if the target containers are not initialized yet or classic editor is set, don't do anything
2507            return;
2508        }
2509        if (isGroupcontainerEditing()) {
2510            for (Widget element : m_groupEditor.getGroupContainerWidget()) {
2511                if (((element instanceof CmsContainerPageElementPanel)
2512                    && isInlineEditable(
2513                        (CmsContainerPageElementPanel)element,
2514                        m_groupEditor.getGroupContainerWidget()))) {
2515                    ((CmsContainerPageElementPanel)element).initInlineEditor(this);
2516                }
2517            }
2518        } else {
2519            for (CmsContainerPageContainer container : m_targetContainers.values()) {
2520                // first remove inline editors
2521                for (Widget element : container) {
2522                    if ((element instanceof CmsContainerPageElementPanel)) {
2523                        ((CmsContainerPageElementPanel)element).removeInlineEditor();
2524                    }
2525                }
2526
2527                // add inline editors only on suitable elements
2528                if (isContainerEditable(container) && matchesCurrentEditLevel(container)) {
2529                    for (Widget element : container) {
2530                        if ((element instanceof CmsContainerPageElementPanel)
2531                            && isInlineEditable((CmsContainerPageElementPanel)element, container)) {
2532                            ((CmsContainerPageElementPanel)element).initInlineEditor(this);
2533                        }
2534                    }
2535                }
2536            }
2537        }
2538    }
2539
2540    /**
2541     * Reloads the content for the given elements and related elements.
2542     *
2543     * @param ids the element ids
2544     * @param callback the callback to execute after the reload
2545     */
2546    public void reloadElements(Collection<String> ids, Runnable callback) {
2547
2548        Set<String> related = new HashSet<String>();
2549        for (String id : ids) {
2550            related.addAll(getRelatedElementIds(id));
2551        }
2552        if (!related.isEmpty()) {
2553            ReloadElementAction action = new ReloadElementAction(related, callback);
2554            action.execute();
2555        }
2556    }
2557
2558    /**
2559     * Reloads the content for the given element and all related elements.<p>
2560     *
2561     * Call this if the element content has changed.<p>
2562     *
2563     * @param ids the element ids
2564     * @param callback the callback to execute after the reload
2565     */
2566    public void reloadElements(String[] ids, Runnable callback) {
2567
2568        Set<String> related = new HashSet<String>();
2569        for (int i = 0; i < ids.length; i++) {
2570            related.addAll(getRelatedElementIds(ids[i]));
2571        }
2572        if (!related.isEmpty()) {
2573            ReloadElementAction action = new ReloadElementAction(related, callback);
2574            action.execute();
2575        }
2576    }
2577
2578    /**
2579     * Reloads a container page element with a new set of settings.<p>
2580     *
2581     * @param elementWidget the widget of the container page element which should be reloaded
2582     * @param clientId the id of the container page element which should be reloaded
2583     * @param settings the new set of settings
2584     * @param afterReloadAction a callback which is executed after the element has been reloaded
2585     */
2586    public void reloadElementWithSettings(
2587        final org.opencms.ade.containerpage.client.ui.CmsContainerPageElementPanel elementWidget,
2588        final String clientId,
2589        final Map<String, String> settings,
2590        final I_CmsSimpleCallback<CmsContainerPageElementPanel> afterReloadAction) {
2591
2592        final I_CmsSimpleCallback<CmsContainerElementData> callback = new I_CmsSimpleCallback<CmsContainerElementData>() {
2593
2594            public void execute(CmsContainerElementData newElement) {
2595
2596                try {
2597                    final CmsContainerPageElementPanel replacement = replaceContainerElement(elementWidget, newElement);
2598                    resetEditButtons();
2599                    addToRecentList(newElement.getClientId(), null);
2600                    afterReloadAction.execute(replacement);
2601                } catch (Exception e) {
2602                    // should never happen
2603                    CmsDebugLog.getInstance().printLine(e.getLocalizedMessage());
2604                }
2605            }
2606        };
2607
2608        if (!isGroupcontainerEditing()) {
2609
2610            lockContainerpage(new I_CmsSimpleCallback<Boolean>() {
2611
2612                public void execute(Boolean arg) {
2613
2614                    if (arg.booleanValue()) {
2615                        CmsRpcAction<CmsContainerElementData> action = new CmsRpcAction<CmsContainerElementData>() {
2616
2617                            @Override
2618                            public void execute() {
2619
2620                                start(500, true);
2621                                getContainerpageService().saveElementSettings(
2622                                    getData().getRpcContext(),
2623                                    getData().getDetailId(),
2624                                    getRequestParams(),
2625                                    clientId,
2626                                    settings,
2627                                    getPageState(),
2628                                    getLocale(),
2629                                    this);
2630                            }
2631
2632                            @Override
2633                            protected void onResponse(CmsContainerElementData result) {
2634
2635                                stop(false);
2636                                CmsContainerpageController.get().fireEvent(
2637                                    new CmsContainerpageEvent(EventType.pageSaved));
2638                                setPageChanged(false, false);
2639                                if (result != null) {
2640                                    // cache the loaded element
2641                                    m_elements.put(result.getClientId(), result);
2642                                    setLoadTime(Long.valueOf(result.getLoadTime()));
2643                                }
2644                                callback.execute(result);
2645                            }
2646                        };
2647                        action.execute();
2648                    }
2649                }
2650            });
2651
2652        } else {
2653            getElementWithSettings(clientId, settings, callback);
2654        }
2655    }
2656
2657    /**
2658     * Reloads the page.<p>
2659     */
2660    public void reloadPage() {
2661
2662        Timer timer = new Timer() {
2663
2664            @Override
2665            @SuppressWarnings("synthetic-access")
2666            public void run() {
2667
2668                Window.Location.reload();
2669            }
2670        };
2671
2672        timer.schedule(150);
2673
2674    }
2675
2676    /**
2677     * Removes the given container element from its parent container.<p>
2678     *
2679     * @param dragElement the element to remove
2680     */
2681    public void removeElement(org.opencms.ade.containerpage.client.ui.CmsContainerPageElementPanel dragElement) {
2682
2683        ElementRemoveMode removeMode = isConfirmRemove()
2684        ? ElementRemoveMode.confirmRemove
2685        : ElementRemoveMode.saveAndCheckReferences;
2686        removeElement(dragElement, removeMode);
2687    }
2688
2689    /**
2690     * Removes the given container element from its parent container.<p>
2691     *
2692     * @param dragElement the element to remove
2693     * @param removeMode the remove mode
2694     */
2695    public void removeElement(
2696        org.opencms.ade.containerpage.client.ui.CmsContainerPageElementPanel dragElement,
2697        ElementRemoveMode removeMode) {
2698
2699        if (isGroupcontainerEditing()) {
2700            dragElement.removeFromParent();
2701            if (!getGroupcontainer().iterator().hasNext()) {
2702                // group-container is empty, mark it
2703                getGroupcontainer().addStyleName(I_CmsLayoutBundle.INSTANCE.containerpageCss().emptyGroupContainer());
2704            }
2705            getGroupcontainer().refreshHighlighting();
2706        } else {
2707            final String id = dragElement.getId();
2708            if (id != null) {
2709                addToRecentList(id, null);
2710            }
2711
2712            I_CmsDropContainer container = dragElement.getParentTarget();
2713            switch (removeMode) {
2714                case saveAndCheckReferences:
2715                    dragElement.removeFromParent();
2716                    if (container instanceof CmsContainerPageContainer) {
2717                        ((CmsContainerPageContainer)container).checkEmptyContainers();
2718                    }
2719                    cleanUpContainers();
2720                    Runnable checkReferencesAction = new Runnable() {
2721
2722                        public void run() {
2723
2724                            checkReferencesToRemovedElement(id);
2725                        }
2726                    };
2727                    setPageChanged(checkReferencesAction);
2728                    break;
2729                case confirmRemove:
2730                    handleConfirmRemove(dragElement);
2731                    break;
2732                case silent:
2733                default:
2734                    dragElement.removeFromParent();
2735                    if (container instanceof CmsContainerPageContainer) {
2736                        ((CmsContainerPageContainer)container).checkEmptyContainers();
2737                    }
2738                    cleanUpContainers();
2739                    setPageChanged();
2740                    break;
2741            }
2742        }
2743    }
2744
2745    /**
2746     * Replaces the given drag-element with the given container element.<p>
2747     *
2748     * @param containerElement the container element to replace
2749     * @param elementData the new element data
2750     *
2751     * @return the container element which replaced the old one
2752     *
2753     * @throws Exception if something goes wrong
2754     */
2755    public CmsContainerPageElementPanel replaceContainerElement(
2756        CmsContainerPageElementPanel containerElement,
2757        CmsContainerElementData elementData)
2758    throws Exception {
2759
2760        I_CmsDropContainer parentContainer = containerElement.getParentTarget();
2761        String containerId = parentContainer.getContainerId();
2762        CmsContainerPageElementPanel replacer = null;
2763        String elementContent = elementData.getContents().get(containerId);
2764        if ((elementContent != null) && (elementContent.trim().length() > 0)) {
2765            replacer = getContainerpageUtil().createElement(elementData, parentContainer, false);
2766
2767            if (containerElement.isNew()) {
2768                // if replacing element data has the same structure id, keep the 'new' state by setting the new type property
2769                // this should only be the case when editing settings of a new element that has not been created in the VFS yet
2770                String id = getServerId(containerElement.getId());
2771                if (elementData.getClientId().startsWith(id)) {
2772                    replacer.setNewType(containerElement.getNewType());
2773                }
2774            }
2775            replacer.setCreateNew(containerElement.isCreateNew());
2776            // replacer.setModelGroup(containerElement.isModelGroup());
2777            if (isGroupcontainerEditing() && (containerElement.getInheritanceInfo() != null)) {
2778                // in case of inheritance container editing, keep the inheritance info
2779                replacer.setInheritanceInfo(containerElement.getInheritanceInfo());
2780                // set the proper element options
2781                CmsInheritanceContainerEditor.getInstance().setOptionBar(replacer);
2782            }
2783            parentContainer.insert(replacer, parentContainer.getWidgetIndex(containerElement));
2784            containerElement.removeFromParent();
2785            initializeSubContainers(replacer);
2786        }
2787        cleanUpContainers();
2788        return replacer;
2789    }
2790
2791    /**
2792     * Replaces the given element with another content while keeping it's settings.<p>
2793     *
2794     * @param elementWidget the element to replace
2795     * @param elementId the id of the replacing content
2796     * @param callback  the callback to execute after the element is replaced
2797     */
2798    public void replaceElement(
2799        final CmsContainerPageElementPanel elementWidget,
2800        final String elementId,
2801        Runnable callback) {
2802
2803        final CmsRpcAction<CmsContainerElementData> action = new CmsRpcAction<CmsContainerElementData>() {
2804
2805            @Override
2806            public void execute() {
2807
2808                start(500, true);
2809                getContainerpageService().replaceElement(
2810                    getData().getRpcContext(),
2811                    getData().getDetailId(),
2812                    getRequestParams(),
2813                    elementWidget.getId(),
2814                    elementId,
2815                    getPageState(),
2816                    getLocale(),
2817                    this);
2818            }
2819
2820            @Override
2821            protected void onResponse(CmsContainerElementData result) {
2822
2823                stop(false);
2824
2825                if (result != null) {
2826                    // cache the loaded element
2827                    m_elements.put(result.getClientId(), result);
2828                    try {
2829                        replaceContainerElement(elementWidget, result);
2830                        resetEditButtons();
2831                        addToRecentList(result.getClientId(), null);
2832                        setPageChanged(new Runnable() {
2833
2834                            public void run() {
2835
2836                                if (callback != null) {
2837                                    callback.run();
2838                                }
2839                            }
2840                        });
2841                    } catch (Exception e) {
2842                        // should never happen
2843                        CmsDebugLog.getInstance().printLine(e.getLocalizedMessage());
2844                    }
2845                }
2846            }
2847        };
2848
2849        if (!isGroupcontainerEditing()) {
2850
2851            lockContainerpage(new I_CmsSimpleCallback<Boolean>() {
2852
2853                public void execute(Boolean arg) {
2854
2855                    if (arg.booleanValue()) {
2856                        action.execute();
2857                    }
2858                }
2859            });
2860
2861        } else {
2862            action.execute();
2863        }
2864    }
2865
2866    /**
2867     * Checks whether the given element should display the option bar.<p>
2868     *
2869     * @param element the element
2870     * @param dragParent the element parent
2871     *
2872     * @return <code>true</code> if the given element should display the option bar
2873     */
2874    public boolean requiresOptionBar(CmsContainerPageElementPanel element, I_CmsDropContainer dragParent) {
2875
2876        return element.hasViewPermission()
2877            && (!element.hasModelGroupParent() || getData().isModelGroup())
2878            && (matchRootView(element.getElementView())
2879                || isGroupcontainerEditing()
2880                || shouldShowModelgroupOptionBar(element))
2881            && isContainerEditable(dragParent)
2882            && matchesCurrentEditLevel(dragParent);
2883    }
2884
2885    /**
2886     * Resets all edit buttons an there positions.<p>
2887     */
2888    public void resetEditButtons() {
2889
2890        removeEditButtonsPositionTimer();
2891        m_editButtonsPositionTimer = new Timer() {
2892
2893            /** Timer run counter. */
2894            private int m_timerRuns;
2895
2896            @Override
2897            public void run() {
2898
2899                for (org.opencms.ade.containerpage.client.ui.CmsContainerPageContainer container : m_targetContainers.values()) {
2900                    container.showEditableListButtons();
2901                    container.updateOptionBars();
2902                }
2903                if (m_timerRuns > 3) {
2904                    cancel();
2905                }
2906                m_timerRuns++;
2907            }
2908        };
2909        m_editButtonsPositionTimer.scheduleRepeating(100);
2910    }
2911
2912    /**
2913     * Resets the container-page.<p>
2914     */
2915    public void resetPage() {
2916
2917        setPageChanged(false, true);
2918        Window.Location.reload();
2919    }
2920
2921    /**
2922     * Method to save and leave the page.<p>
2923     *
2924     * @param leaveCommand the command to execute to leave the page
2925     */
2926    public void saveAndLeave(final Command leaveCommand) {
2927
2928        if (hasPageChanged()) {
2929            CmsRpcAction<Long> action = new CmsRpcAction<Long>() {
2930
2931                /**
2932                 * @see org.opencms.gwt.client.rpc.CmsRpcAction#execute()
2933                 */
2934                @Override
2935                public void execute() {
2936
2937                    if (getData().getDetailContainerPage() != null) {
2938                        getContainerpageService().saveDetailContainers(
2939                            getData().getDetailId(),
2940                            getData().getDetailContainerPage(),
2941                            getPageContent(),
2942                            this);
2943                    } else {
2944                        getContainerpageService().saveContainerpage(
2945                            CmsCoreProvider.get().getStructureId(),
2946                            getPageContent(),
2947                            this);
2948                    }
2949                }
2950
2951                /**
2952                 * @see org.opencms.gwt.client.rpc.CmsRpcAction#onResponse(java.lang.Object)
2953                 */
2954                @Override
2955                protected void onResponse(Long result) {
2956
2957                    setLoadTime(result);
2958                    CmsNotification.get().send(Type.NORMAL, Messages.get().key(Messages.GUI_NOTIFICATION_PAGE_SAVED_0));
2959                    CmsContainerpageController.get().fireEvent(new CmsContainerpageEvent(EventType.pageSaved));
2960                    setPageChanged(false, true);
2961                    leaveCommand.execute();
2962                }
2963            };
2964            action.execute();
2965        }
2966    }
2967
2968    /**
2969     * Method to save and leave the page.<p>
2970     *
2971     * @param targetUri the new URI to call
2972     */
2973    public void saveAndLeave(final String targetUri) {
2974
2975        if (hasPageChanged()) {
2976            CmsRpcAction<Long> action = new CmsRpcAction<Long>() {
2977
2978                /**
2979                 * @see org.opencms.gwt.client.rpc.CmsRpcAction#execute()
2980                 */
2981                @Override
2982                public void execute() {
2983
2984                    if (getData().getDetailContainerPage() != null) {
2985                        getContainerpageService().saveDetailContainers(
2986                            getData().getDetailId(),
2987                            getData().getDetailContainerPage(),
2988                            getPageContent(),
2989                            this);
2990                    } else {
2991                        getContainerpageService().saveContainerpage(
2992                            CmsCoreProvider.get().getStructureId(),
2993                            getPageContent(),
2994                            this);
2995                    }
2996                }
2997
2998                /**
2999                 * @see org.opencms.gwt.client.rpc.CmsRpcAction#onResponse(java.lang.Object)
3000                 */
3001                @Override
3002                protected void onResponse(Long result) {
3003
3004                    setLoadTime(result);
3005                    CmsNotification.get().send(Type.NORMAL, Messages.get().key(Messages.GUI_NOTIFICATION_PAGE_SAVED_0));
3006                    CmsContainerpageController.get().fireEvent(new CmsContainerpageEvent(EventType.pageSaved));
3007                    setPageChanged(false, true);
3008                    Window.Location.assign(targetUri);
3009                }
3010            };
3011            action.execute();
3012        }
3013    }
3014
3015    /**
3016     * Saves the clipboard tab  index selected by the user.<p>
3017     *
3018     * @param tabIndex the tab index
3019     */
3020    public void saveClipboardTab(final int tabIndex) {
3021
3022        CmsRpcAction<Void> action = new CmsRpcAction<Void>() {
3023
3024            @Override
3025            public void execute() {
3026
3027                start(1, false);
3028                getContainerpageService().saveClipboardTab(tabIndex, this);
3029            }
3030
3031            @Override
3032            protected void onResponse(Void result) {
3033
3034                stop(false);
3035            }
3036        };
3037        action.execute();
3038    }
3039
3040    /**
3041     * Saves the current state of the container-page.<p>
3042     *
3043     * @param afterSaveActions the actions to execute after saving
3044     */
3045    public void saveContainerpage(final Runnable... afterSaveActions) {
3046
3047        if (hasPageChanged()) {
3048            final CmsRpcAction<Long> action = new CmsRpcAction<Long>() {
3049
3050                /**
3051                 * @see org.opencms.gwt.client.rpc.CmsRpcAction#execute()
3052                 */
3053                @Override
3054                public void execute() {
3055
3056                    start(500, true);
3057                    if (getData().getDetailContainerPage() != null) {
3058                        getContainerpageService().saveDetailContainers(
3059                            getData().getDetailId(),
3060                            getData().getDetailContainerPage(),
3061                            getPageContent(),
3062                            this);
3063                    } else {
3064                        getContainerpageService().saveContainerpage(
3065                            CmsCoreProvider.get().getStructureId(),
3066                            getPageContent(),
3067                            this);
3068                    }
3069                }
3070
3071                /**
3072                 * @see org.opencms.gwt.client.rpc.CmsRpcAction#onResponse(java.lang.Object)
3073                 */
3074                @Override
3075                protected void onResponse(Long result) {
3076
3077                    setLoadTime(result);
3078                    stop(false);
3079                    setPageChanged(false, false);
3080                    CmsContainerpageController.get().fireEvent(new CmsContainerpageEvent(EventType.pageSaved));
3081                    for (Runnable afterSaveAction : afterSaveActions) {
3082                        afterSaveAction.run();
3083                    }
3084                }
3085            };
3086            if (getData().getDetailContainerPage() != null) {
3087                action.execute();
3088            } else {
3089                lockContainerpage(new I_CmsSimpleCallback<Boolean>() {
3090
3091                    public void execute(Boolean arg) {
3092
3093                        if (arg.booleanValue()) {
3094                            action.execute();
3095                        }
3096                    }
3097                });
3098            }
3099        }
3100    }
3101
3102    /**
3103     * Saves the favorite list.<p>
3104     *
3105     * @param clientIds the client id's of the list's elements
3106     */
3107    public void saveFavoriteList(final List<String> clientIds) {
3108
3109        CmsRpcAction<Void> action = new CmsRpcAction<Void>() {
3110
3111            /**
3112             * @see org.opencms.gwt.client.rpc.CmsRpcAction#execute()
3113             */
3114            @Override
3115            public void execute() {
3116
3117                getContainerpageService().saveFavoriteList(clientIds, CmsCoreProvider.get().getUri(), this);
3118            }
3119
3120            /**
3121             * @see org.opencms.gwt.client.rpc.CmsRpcAction#onResponse(java.lang.Object)
3122             */
3123            @Override
3124            protected void onResponse(Void result) {
3125
3126                CmsNotification.get().send(
3127                    Type.NORMAL,
3128                    Messages.get().key(Messages.GUI_NOTIFICATION_FAVORITES_SAVED_0));
3129            }
3130        };
3131        action.execute();
3132    }
3133
3134    /**
3135     * Saves the group-container.<p>
3136     *
3137     * @param groupContainer the group-container data to save
3138     * @param groupContainerElement the group-container widget
3139     */
3140    public void saveGroupcontainer(
3141        final CmsGroupContainer groupContainer,
3142        final CmsGroupContainerElementPanel groupContainerElement) {
3143
3144        if (getGroupcontainer() != null) {
3145            CmsRpcAction<CmsGroupContainerSaveResult> action = new CmsRpcAction<CmsGroupContainerSaveResult>() {
3146
3147                /**
3148                 * @see org.opencms.gwt.client.rpc.CmsRpcAction#execute()
3149                 */
3150                @Override
3151                public void execute() {
3152
3153                    start(0, true);
3154                    getContainerpageService().saveGroupContainer(
3155                        getData().getRpcContext(),
3156                        getData().getDetailId(),
3157                        getRequestParams(),
3158                        groupContainer,
3159                        getPageState(),
3160                        getLocale(),
3161                        this);
3162                }
3163
3164                /**
3165                 * @see org.opencms.gwt.client.rpc.CmsRpcAction#onResponse(java.lang.Object)
3166                 */
3167                @Override
3168                protected void onResponse(CmsGroupContainerSaveResult saveResult) {
3169
3170                    stop(false);
3171                    Map<String, CmsContainerElementData> elementData = saveResult.getElementData();
3172                    m_elements.putAll(elementData);
3173                    try {
3174                        replaceContainerElement(groupContainerElement, elementData.get(groupContainerElement.getId()));
3175                    } catch (Exception e) {
3176                        CmsDebugLog.getInstance().printLine("Error replacing group container element");
3177                    }
3178                    addToRecentList(groupContainerElement.getId(), null);
3179                    CmsNotification.get().send(
3180                        Type.NORMAL,
3181                        Messages.get().key(Messages.GUI_NOTIFICATION_GROUP_CONTAINER_SAVED_0));
3182                    List<CmsRemovedElementStatus> removedElements = saveResult.getRemovedElements();
3183                    for (CmsRemovedElementStatus removedElement : removedElements) {
3184                        askWhetherRemovedElementShouldBeDeleted(removedElement);
3185                    }
3186
3187                }
3188            };
3189            action.execute();
3190
3191        }
3192    }
3193
3194    /**
3195     * Saves the inheritance container.<p>
3196     *
3197     * @param inheritanceContainer the inheritance container data to save
3198     * @param groupContainerElement the group container widget
3199     */
3200    public void saveInheritContainer(
3201        final CmsInheritanceContainer inheritanceContainer,
3202        final CmsGroupContainerElementPanel groupContainerElement) {
3203
3204        if (getGroupcontainer() != null) {
3205            CmsRpcAction<Map<String, CmsContainerElementData>> action = new CmsRpcAction<Map<String, CmsContainerElementData>>() {
3206
3207                /**
3208                 * @see org.opencms.gwt.client.rpc.CmsRpcAction#execute()
3209                 */
3210                @Override
3211                public void execute() {
3212
3213                    start(0, true);
3214                    getContainerpageService().saveInheritanceContainer(
3215                        CmsCoreProvider.get().getStructureId(),
3216                        getData().getDetailId(),
3217                        inheritanceContainer,
3218                        getPageState(),
3219                        getLocale(),
3220                        this);
3221                }
3222
3223                /**
3224                 * @see org.opencms.gwt.client.rpc.CmsRpcAction#onResponse(java.lang.Object)
3225                 */
3226                @Override
3227                protected void onResponse(Map<String, CmsContainerElementData> result) {
3228
3229                    stop(false);
3230                    m_elements.putAll(result);
3231                    try {
3232                        replaceContainerElement(groupContainerElement, result.get(groupContainerElement.getId()));
3233                    } catch (Exception e) {
3234                        CmsDebugLog.getInstance().printLine("Error replacing group container element");
3235                    }
3236                    addToRecentList(groupContainerElement.getId(), null);
3237                    CmsNotification.get().send(
3238                        Type.NORMAL,
3239                        Messages.get().key(Messages.GUI_NOTIFICATION_INHERITANCE_CONTAINER_SAVED_0));
3240
3241                }
3242            };
3243            action.execute();
3244
3245        }
3246    }
3247
3248    /**
3249     * Sets the flag indicating that a content element is being edited.<p>
3250     *
3251     * @param isContentEditing the flag indicating that a content element is being edited
3252     */
3253    public void setContentEditing(boolean isContentEditing) {
3254
3255        if (m_groupEditor != null) {
3256            if (isContentEditing) {
3257                m_groupEditor.hidePopup();
3258            } else {
3259                m_groupEditor.showPopup();
3260            }
3261        }
3262        m_isContentEditing = isContentEditing;
3263    }
3264
3265    /**
3266     * Sets the DND controller.<p>
3267     *
3268     * @param dnd the new DND controller
3269     */
3270    public void setDndController(CmsCompositeDNDController dnd) {
3271
3272        m_dndController = dnd;
3273    }
3274
3275    /**
3276     * Sets the element view.<p>
3277     *
3278     * @param viewInfo the element view
3279     * @param nextAction the action to execute after setting the view
3280     */
3281    public void setElementView(CmsElementViewInfo viewInfo, Runnable nextAction) {
3282
3283        if (viewInfo != null) {
3284            m_elementView = viewInfo;
3285
3286            CmsRpcAction<Void> action = new CmsRpcAction<Void>() {
3287
3288                @SuppressWarnings("synthetic-access")
3289                @Override
3290                public void execute() {
3291
3292                    getContainerpageService().setElementView(m_elementView.getElementViewId(), this);
3293                }
3294
3295                @Override
3296                protected void onResponse(Void result) {
3297
3298                    // nothing to do
3299                }
3300            };
3301            action.execute();
3302
3303            m_currentEditLevel = -1;
3304            reinitializeButtons();
3305            updateButtonsForCurrentView();
3306            reInitInlineEditing();
3307            updateGalleryData(true, nextAction);
3308        }
3309    }
3310
3311    /**
3312     * Sets the model group base element id.<p>
3313     *
3314     * @param modelGroupElementId the model group base element id
3315     */
3316    public void setModelGroupElementId(String modelGroupElementId) {
3317
3318        m_modelGroupElementId = modelGroupElementId;
3319    }
3320
3321    /**
3322     * Marks the page as changed.<p>
3323     *
3324     * @param nextActions the actions to perform after the page has been marked as changed
3325     */
3326    public void setPageChanged(Runnable... nextActions) {
3327
3328        if (!isGroupcontainerEditing()) {
3329            // the container page will be saved immediately
3330            m_pageChanged = true;
3331            saveContainerpage(nextActions);
3332        }
3333    }
3334
3335    /**
3336     * Method to determine whether a container element should be shown in the current template context.<p>
3337     *
3338     * @param elementData the element data
3339     *
3340     * @return true if the element should be shown
3341     */
3342    public boolean shouldShowInContext(CmsContainerElementData elementData) {
3343
3344        CmsTemplateContextInfo contextInfo = getData().getTemplateContextInfo();
3345        if (contextInfo.getCurrentContext() == null) {
3346            return true;
3347        }
3348        CmsDefaultSet<String> allowedContexts = contextInfo.getAllowedContexts().get(elementData.getResourceType());
3349        if ((allowedContexts != null) && !allowedContexts.contains(contextInfo.getCurrentContext())) {
3350            return false;
3351        }
3352
3353        String settingValue = elementData.getSettings().get(CmsTemplateContextInfo.SETTING);
3354        return (settingValue == null) || settingValue.contains(contextInfo.getCurrentContext());
3355    }
3356
3357    /**
3358     * Tells the controller that group-container editing has started.<p>
3359     *
3360     * @param groupContainer the group container
3361     * @param isElementGroup <code>true</code> if the group container is an element group and not an inheritance group
3362     */
3363    public void startEditingGroupcontainer(
3364        final CmsGroupContainerElementPanel groupContainer,
3365        final boolean isElementGroup) {
3366
3367        removeEditButtonsPositionTimer();
3368        I_CmsSimpleCallback<Boolean> callback = new I_CmsSimpleCallback<Boolean>() {
3369
3370            public void execute(Boolean arg) {
3371
3372                if (arg.booleanValue()) {
3373                    if (isElementGroup) {
3374                        m_groupEditor = CmsGroupContainerEditor.openGroupcontainerEditor(
3375                            groupContainer,
3376                            CmsContainerpageController.this,
3377                            m_handler);
3378                    } else {
3379                        m_groupEditor = CmsInheritanceContainerEditor.openInheritanceContainerEditor(
3380                            groupContainer,
3381                            CmsContainerpageController.this,
3382                            m_handler);
3383                    }
3384                } else {
3385                    CmsNotification.get().send(
3386                        Type.WARNING,
3387                        Messages.get().key(Messages.GUI_NOTIFICATION_UNABLE_TO_LOCK_0));
3388                }
3389            }
3390        };
3391        if ((m_groupEditor == null) && (groupContainer.isNew())) {
3392            callback.execute(Boolean.TRUE);
3393        } else {
3394            lockContainerpage(callback);
3395        }
3396
3397    }
3398
3399    /**
3400     * Starts the publish lock check.
3401     */
3402    public void startPublishLockCheck() {
3403
3404        Set<CmsUUID> elementIds = new HashSet<>();
3405        processPageContent(new I_PageContentVisitor() {
3406
3407            public boolean beginContainer(String name, CmsContainer container) {
3408
3409                return true;
3410            }
3411
3412            public void endContainer() {
3413
3414                // do nothing
3415            }
3416
3417            public void handleElement(CmsContainerPageElementPanel element) {
3418
3419                if (element.hasWritePermission() && element.getLockInfo().isPublishLock()) {
3420                    CmsUUID structureId = element.getStructureId();
3421                    if (structureId != null) {
3422                        elementIds.add(structureId);
3423                    }
3424                }
3425            }
3426        });
3427
3428        m_publishLockChecker.addIdsToCheck(elementIds);
3429    }
3430
3431    /**
3432     * Tells the controller that group-container editing has stopped.<p>
3433     */
3434    public void stopEditingGroupcontainer() {
3435
3436        m_groupEditor = null;
3437    }
3438
3439    /**
3440     * Unlocks the given resource.<p>
3441     *
3442     * @param structureId the structure id of the resource to unlock
3443     *
3444     * @return <code>true</code> if the resource was unlocked successfully
3445     */
3446    public boolean unlockResource(CmsUUID structureId) {
3447
3448        return CmsCoreProvider.get().unlock(structureId);
3449    }
3450
3451    /**
3452     * Updates he
3453     */
3454    public void updateButtonsForCurrentView() {
3455
3456        String nonDefaultViewClass = I_CmsLayoutBundle.INSTANCE.containerpageCss().nonDefaultView();
3457        CmsUUID viewId = getElementView().getRootViewId();
3458        if (viewId.isNullUUID()) {
3459            RootPanel.get().removeStyleName(nonDefaultViewClass);
3460        } else {
3461            RootPanel.get().addStyleName(nonDefaultViewClass);
3462        }
3463    }
3464
3465    /**
3466     * Adds the given element data to the element cache.<p>
3467     *
3468     * @param elements the element data
3469     */
3470    protected void addElements(List<CmsContainerElementData> elements) {
3471
3472        for (CmsContainerElementData element : elements) {
3473            m_elements.put(element.getClientId(), element);
3474        }
3475    }
3476
3477    /**
3478     * Adds the given element data to the element cache.<p>
3479     *
3480     * @param elements the element data
3481     */
3482    protected void addElements(Map<String, CmsContainerElementData> elements) {
3483
3484        for (CmsContainerElementData element : elements.values()) {
3485            m_elements.put(element.getClientId(), element);
3486        }
3487    }
3488
3489    /**
3490     * Asks the user whether an element which has been removed should be deleted.<p>
3491     *
3492     * @param status the status of the removed element
3493     */
3494    protected void askWhetherRemovedElementShouldBeDeleted(final CmsRemovedElementStatus status) {
3495
3496        CmsRemovedElementDeletionDialog dialog = new CmsRemovedElementDeletionDialog(status);
3497        dialog.center();
3498    }
3499
3500    /**
3501     * Checks that a removed can be possibly deleted and if so, asks the user if it should be deleted.<p>
3502     *
3503     * @param id the client id of the element
3504     */
3505    protected void checkReferencesToRemovedElement(final String id) {
3506
3507        if (id != null) {
3508            //NOTE: We only use an RPC call here to check for references on the server side. If, at a later point, we decide
3509            //to add a save button again, this will have to be changed, because then we have to consider client-side state.
3510            CmsRpcAction<CmsRemovedElementStatus> getStatusAction = new CmsRpcAction<CmsRemovedElementStatus>() {
3511
3512                @Override
3513                public void execute() {
3514
3515                    start(200, true);
3516                    getContainerpageService().getRemovedElementStatus(id, null, this);
3517                }
3518
3519                @Override
3520                public void onResponse(final CmsRemovedElementStatus status) {
3521
3522                    stop(false);
3523                    if (status.isDeletionCandidate()) {
3524                        askWhetherRemovedElementShouldBeDeleted(status);
3525
3526                    }
3527                }
3528
3529            };
3530            getStatusAction.execute();
3531
3532        }
3533    }
3534
3535    /**
3536     * Disables option and toolbar buttons.<p>
3537     */
3538    protected void deactivateOnClosing() {
3539
3540        removeEditButtonsPositionTimer();
3541        m_handler.deactivateCurrentButton();
3542        m_handler.disableToolbarButtons();
3543    }
3544
3545    /**
3546     * Helper method to get all current container page elements.<p>
3547     *
3548     * @param includeGroupContents true if the contents of group containers should also be included
3549     *
3550     * @return the list of current container page elements
3551     */
3552    protected List<CmsContainerPageElementPanel> getAllContainerPageElements(boolean includeGroupContents) {
3553
3554        List<CmsContainerPageElementPanel> elemWidgets = new ArrayList<CmsContainerPageElementPanel>();
3555        for (Entry<String, org.opencms.ade.containerpage.client.ui.CmsContainerPageContainer> entry : CmsContainerpageController.get().getContainerTargets().entrySet()) {
3556            Iterator<Widget> elIt = entry.getValue().iterator();
3557            while (elIt.hasNext()) {
3558                try {
3559                    org.opencms.ade.containerpage.client.ui.CmsContainerPageElementPanel elementWidget = (org.opencms.ade.containerpage.client.ui.CmsContainerPageElementPanel)elIt.next();
3560                    elemWidgets.add(elementWidget);
3561                    if (includeGroupContents && (elementWidget instanceof CmsGroupContainerElementPanel)) {
3562                        List<CmsContainerPageElementPanel> groupChildren = ((CmsGroupContainerElementPanel)elementWidget).getGroupChildren();
3563                        elemWidgets.addAll(groupChildren);
3564                    }
3565                } catch (ClassCastException e) {
3566                    // no proper container element, skip it (this should never happen!)
3567                    CmsDebugLog.getInstance().printLine(
3568                        "WARNING: there is an inappropriate element within a container");
3569                }
3570            }
3571        }
3572        return elemWidgets;
3573    }
3574
3575    /**
3576     * Returns the core RPC service.<p>
3577     *
3578     * @return the core service
3579     */
3580    protected I_CmsCoreServiceAsync getCoreService() {
3581
3582        if (m_coreSvc == null) {
3583            m_coreSvc = CmsCoreProvider.getService();
3584        }
3585        return m_coreSvc;
3586    }
3587
3588    /**
3589     * Returns the currently active group editor.<p>
3590     *
3591     * @return the currently active group editor
3592     */
3593    protected A_CmsGroupEditor getGroupEditor() {
3594
3595        return m_groupEditor;
3596    }
3597
3598    /**
3599     * Returns the content locale.<p>
3600     *
3601     * @return the content locale
3602     */
3603    protected String getLocale() {
3604
3605        return m_data.getLocale();
3606    }
3607
3608    /**
3609     * Gets the page content for purposes of saving.<p>
3610     *
3611     * @return the page content
3612     */
3613    protected List<CmsContainer> getPageContent() {
3614
3615        SaveDataVisitor visitor = new SaveDataVisitor();
3616        processPageContent(visitor);
3617        return visitor.getContainers();
3618
3619    }
3620
3621    /**
3622     * Returns the containers of the page in their current state.<p>
3623     *
3624     * @return the containers of the page
3625     */
3626    protected List<CmsContainer> getPageState() {
3627
3628        PageStateVisitor visitor = new PageStateVisitor();
3629        processPageContent(visitor);
3630        return visitor.getContainers();
3631    }
3632
3633    /**
3634     * Returns the request parameters of the displayed container-page.<p>
3635     *
3636     * @return the request parameters
3637     */
3638    protected String getRequestParams() {
3639
3640        return m_data.getRequestParams();
3641    }
3642
3643    /**
3644     * Checks if any of the containers are nested containers.<p>
3645     *
3646     * @return true if there are nested containers
3647     */
3648    protected boolean hasNestedContainers() {
3649
3650        boolean hasNestedContainers = false;
3651        for (CmsContainer container : m_containers.values()) {
3652            if (container.getParentContainerName() != null) {
3653                hasNestedContainers = true;
3654                break;
3655            }
3656        }
3657        return hasNestedContainers;
3658    }
3659
3660    /**
3661     * Returns whether the given container is considered a root container.<p>
3662     *
3663     * @param container the container to check
3664     *
3665     * @return <code>true</code> if the given container is a root container
3666     */
3667    protected boolean isRootContainer(CmsContainer container) {
3668
3669        boolean isRoot = false;
3670        if (!container.isSubContainer()) {
3671            isRoot = true;
3672        } else if (container.isDetailOnly()) {
3673            CmsContainer parent = getContainer(container.getParentContainerName());
3674            isRoot = (parent != null) && !parent.isDetailOnly();
3675        }
3676        return isRoot;
3677    }
3678
3679    /**
3680     * Opens the editor for the newly created element.<p>
3681     *
3682     * @param element the container element
3683     * @param newElementData the new element data
3684     * @param inline <code>true</code> to open the inline editor for the given element if available
3685     */
3686    protected void openEditorForNewElement(
3687        org.opencms.ade.containerpage.client.ui.CmsContainerPageElementPanel element,
3688        CmsContainerElement newElementData,
3689        boolean inline) {
3690
3691        String oldId = element.getNewType();
3692        element.setNewType(null);
3693        if (inline) {
3694            String newId = getServerId(newElementData.getClientId());
3695            CmsContentEditor.replaceResourceIds(element.getElement(), oldId, newId);
3696        }
3697        element.setId(newElementData.getClientId());
3698        element.setSitePath(newElementData.getSitePath());
3699        if (!isGroupcontainerEditing()) {
3700            setPageChanged();
3701        }
3702        getHandler().hidePageOverlay();
3703        getHandler().openEditorForElement(element, inline, true);
3704    }
3705
3706    /**
3707    * Previews events. Shows the leaving page dialog, if the page has changed and an anchor has been clicked.<p>
3708    * Also triggers an element view change on 'Ctrl+E'.<p>
3709    *
3710    * @param event the native event
3711    */
3712    protected void previewNativeEvent(NativePreviewEvent event) {
3713
3714        Event nativeEvent = Event.as(event.getNativeEvent());
3715
3716        if ((nativeEvent.getTypeInt() == Event.ONCLICK) && hasPageChanged()) {
3717            EventTarget target = nativeEvent.getEventTarget();
3718            if (!Element.is(target)) {
3719                return;
3720            }
3721            Element element = Element.as(target);
3722            element = CmsDomUtil.getAncestor(element, CmsDomUtil.Tag.a);
3723            if (element == null) {
3724                return;
3725            }
3726            AnchorElement anc = AnchorElement.as(element);
3727            final String uri = anc.getHref();
3728
3729            // avoid to abort events for date-picker widgets
3730            if (CmsStringUtil.isEmptyOrWhitespaceOnly(uri)
3731                || (CmsDomUtil.getAncestor(element, "x-date-picker") != null)) {
3732                return;
3733            }
3734            nativeEvent.preventDefault();
3735            nativeEvent.stopPropagation();
3736            m_handler.leavePage(uri);
3737        }
3738        if (event.getTypeInt() == Event.ONKEYDOWN) {
3739            int keyCode = nativeEvent.getKeyCode();
3740            if ((keyCode == KeyCodes.KEY_F5) && hasPageChanged()) {
3741                // user pressed F5
3742                nativeEvent.preventDefault();
3743                nativeEvent.stopPropagation();
3744                m_handler.leavePage(Window.Location.getHref());
3745            }
3746            if (nativeEvent.getCtrlKey() || nativeEvent.getMetaKey()) {
3747                // look for short cuts
3748                if (keyCode == KeyCodes.KEY_E) {
3749                    if (nativeEvent.getShiftKey()) {
3750                        circleContainerEditLayers();
3751                    } else {
3752                        openNextElementView();
3753                    }
3754                    nativeEvent.preventDefault();
3755                    nativeEvent.stopPropagation();
3756                }
3757            }
3758        }
3759    }
3760
3761    /**
3762     * Iterates over all the container contents and calls a visitor object with the visited containers/elements as parameters.
3763     *
3764     * @param visitor the visitor which the container elements should be passed to
3765     */
3766    protected void processPageContent(I_PageContentVisitor visitor) {
3767
3768        for (Entry<String, org.opencms.ade.containerpage.client.ui.CmsContainerPageContainer> entry : m_targetContainers.entrySet()) {
3769
3770            CmsContainer cnt = m_containers.get(entry.getKey());
3771            if (visitor.beginContainer(entry.getKey(), cnt)) {
3772                Iterator<Widget> elIt = entry.getValue().iterator();
3773                while (elIt.hasNext()) {
3774                    try {
3775                        CmsContainerPageElementPanel elementWidget = (CmsContainerPageElementPanel)elIt.next();
3776                        visitor.handleElement(elementWidget);
3777                    } catch (ClassCastException e) {
3778                        // no proper container element, skip it (this should never happen!)
3779                        CmsDebugLog.getInstance().printLine(
3780                            "WARNING: there is an inappropriate element within a container");
3781                    }
3782                }
3783                visitor.endContainer();
3784            }
3785        }
3786    }
3787
3788    /**
3789     * Removes all container elements with the given id from all containers and the client side cache.<p>
3790     *
3791     * @param resourceId the resource id
3792     */
3793    protected void removeContainerElements(String resourceId) {
3794
3795        boolean changed = false;
3796        Iterator<org.opencms.ade.containerpage.client.ui.CmsContainerPageElementPanel> it = getAllDragElements().iterator();
3797        while (it.hasNext()) {
3798            org.opencms.ade.containerpage.client.ui.CmsContainerPageElementPanel containerElement = it.next();
3799            if (resourceId.startsWith(containerElement.getId())) {
3800                containerElement.removeFromParent();
3801                changed = true;
3802            }
3803        }
3804        for (String elementId : m_elements.keySet()) {
3805            if (elementId.startsWith(resourceId)) {
3806                m_elements.remove(elementId);
3807            }
3808        }
3809        if (changed) {
3810            setPageChanged();
3811        }
3812    }
3813
3814    /**
3815     * Schedules an update of the gallery data according to the current element view and the editable containers.<p>
3816     */
3817    protected void scheduleGalleryUpdate() {
3818
3819        // only if not already scheduled
3820        if (m_galleryUpdateTimer == null) {
3821            m_galleryUpdateTimer = new Timer() {
3822
3823                @Override
3824                public void run() {
3825
3826                    m_galleryUpdateTimer = null;
3827                    updateGalleryData(false, null);
3828                }
3829            };
3830            m_galleryUpdateTimer.schedule(50);
3831        }
3832    }
3833
3834    /**
3835     * Sets the page changed flag and initializes the window closing handler if necessary.<p>
3836     *
3837     * @param changed if <code>true</code> the page has changed
3838     * @param unlock if <code>true</code> the page will be unlocked for unchanged pages
3839     */
3840    protected void setPageChanged(boolean changed, boolean unlock) {
3841
3842        if (changed) {
3843            if (!m_pageChanged) {
3844                m_pageChanged = changed;
3845                lockContainerpage(new I_CmsSimpleCallback<Boolean>() {
3846
3847                    public void execute(Boolean arg) {
3848
3849                        // nothing to do
3850                    }
3851                });
3852            }
3853        } else {
3854            m_pageChanged = changed;
3855            if (unlock) {
3856                unlockContainerpage();
3857            }
3858        }
3859    }
3860
3861    /**
3862     * Asynchronously unlocks the container page.
3863     */
3864    protected void unlockContainerpage() {
3865
3866        I_CmsUnlockDataFactory factory = GWT.create(I_CmsUnlockDataFactory.class);
3867        AutoBean<I_CmsUnlockData> unlockParams = factory.unlockData();
3868        unlockParams.as().setPageId("" + CmsCoreProvider.get().getStructureId());
3869        if (getData().getDetailId() != null) {
3870            unlockParams.as().setDetailId("" + getData().getDetailId());
3871        }
3872        unlockParams.as().setLocale(CmsCoreProvider.get().getLocale());
3873        String url = CmsCoreProvider.get().link("/handleBuiltinService" + CmsGwtConstants.HANDLER_UNLOCK_PAGE);
3874        sendBeacon(url, AutoBeanCodex.encode(unlockParams).getPayload());
3875    }
3876
3877    /**
3878     * Returns the pages of editable containers.<p>
3879     *
3880     * @return the containers
3881     */
3882    List<CmsContainer> getEditableContainers() {
3883
3884        List<CmsContainer> containers = new ArrayList<CmsContainer>();
3885        for (CmsContainer container : m_containers.values()) {
3886            if ((m_targetContainers.get(container.getName()) != null)
3887                && isContainerEditable(m_targetContainers.get(container.getName()))) {
3888                containers.add(container);
3889            }
3890        }
3891        return containers;
3892    }
3893
3894    /**
3895     * Handles a window resize to reset highlighting and the edit button positions.<p>
3896     */
3897    void handleResize() {
3898
3899        m_resizeTimer = null;
3900        resetEditButtons();
3901    }
3902
3903    /**
3904     * Call on window resize.<p>
3905     */
3906    void onResize() {
3907
3908        if (!isGroupcontainerEditing() && (m_resizeTimer == null)) {
3909            m_resizeTimer = new Timer() {
3910
3911                @Override
3912                public void run() {
3913
3914                    handleResize();
3915                }
3916            };
3917            m_resizeTimer.schedule(300);
3918        }
3919    }
3920
3921    /**
3922     * Sets the load time.<p>
3923     *
3924     * @param time the time to set
3925     */
3926    void setLoadTime(Long time) {
3927
3928        if (time != null) {
3929            m_loadTime = time.longValue();
3930        }
3931    }
3932
3933    /**
3934     * Updates the gallery data according to the current element view and the editable containers.<p>
3935     * This method should only be called from the gallery update timer to avoid unnecessary requests.<p>
3936     *
3937     * @param viewChanged <code>true</code> in case the element view changed
3938     * @param nextAction the action to execute after updating the gallery data
3939     */
3940    void updateGalleryData(final boolean viewChanged, final Runnable nextAction) {
3941
3942        CmsRpcAction<CmsContainerPageGalleryData> dataAction = new CmsRpcAction<CmsContainerPageGalleryData>() {
3943
3944            @Override
3945            public void execute() {
3946
3947                getContainerpageService().getGalleryDataForPage(
3948                    getEditableContainers(),
3949                    getElementView().getElementViewId(),
3950                    CmsCoreProvider.get().getUri(),
3951                    getData().getLocale(),
3952                    this);
3953            }
3954
3955            @Override
3956            protected void onResponse(CmsContainerPageGalleryData result) {
3957
3958                m_handler.m_editor.getAdd().updateGalleryData(result, viewChanged);
3959                if (nextAction != null) {
3960                    nextAction.run();
3961                }
3962            }
3963        };
3964        dataAction.execute();
3965    }
3966
3967    /**
3968     * Checks whether there are other references to a given container page element.<p>
3969     *
3970     * @param element the element to check
3971     * @param callback the callback which will be called with the result of the check (true if there are other references)
3972     */
3973    private void checkElementReferences(
3974        final CmsContainerPageElementPanel element,
3975        final AsyncCallback<CmsRemovedElementStatus> callback) {
3976
3977        ReferenceCheckVisitor visitor = new ReferenceCheckVisitor(element);
3978        processPageContent(visitor);
3979        if (visitor.hasReferences()) {
3980            // Don't need to ask the server because we already know we have other references in the same page
3981            CmsRpcAction<CmsListInfoBean> infoAction = new CmsRpcAction<CmsListInfoBean>() {
3982
3983                @Override
3984                public void execute() {
3985
3986                    start(200, true);
3987                    CmsCoreProvider.getVfsService().getPageInfo(new CmsUUID(getServerId(element.getId())), this);
3988                }
3989
3990                @Override
3991                protected void onResponse(CmsListInfoBean result) {
3992
3993                    stop(false);
3994                    callback.onSuccess(new CmsRemovedElementStatus(null, result, false, null));
3995                }
3996            };
3997            infoAction.execute();
3998        } else {
3999            CmsRpcAction<CmsRemovedElementStatus> getStatusAction = new CmsRpcAction<CmsRemovedElementStatus>() {
4000
4001                @Override
4002                public void execute() {
4003
4004                    start(200, true);
4005                    getContainerpageService().getRemovedElementStatus(
4006                        element.getId(),
4007                        CmsCoreProvider.get().getStructureId(),
4008                        this);
4009                }
4010
4011                @Override
4012                public void onResponse(final CmsRemovedElementStatus status) {
4013
4014                    stop(false);
4015                    callback.onSuccess(status);
4016                }
4017
4018            };
4019            getStatusAction.execute();
4020
4021        }
4022    }
4023
4024    /**
4025     * Checks if the page was locked by another user at load time.<p>
4026     */
4027    private void checkLockInfo() {
4028
4029        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(getData().getLockInfo())) {
4030            CmsNotification.get().send(Type.ERROR, getData().getLockInfo());
4031            m_lockStatus = LockStatus.failed;
4032            m_handler.m_editor.disableEditing(getData().getLockInfo());
4033        }
4034    }
4035
4036    /**
4037     * Selects the next container edit level.<p>
4038     */
4039    private void circleContainerEditLayers() {
4040
4041        if (m_isContentEditing || isGroupcontainerEditing() || (m_maxContainerLevel == 0)) {
4042            return;
4043        }
4044        boolean hasEditables = false;
4045        int previousLevel = m_currentEditLevel;
4046        String message = "";
4047        while (!hasEditables) {
4048            if (m_currentEditLevel == m_maxContainerLevel) {
4049                m_currentEditLevel = -1;
4050                message = Messages.get().key(Messages.GUI_SWITCH_EDIT_LEVEL_ALL_1, m_elementView.getTitle());
4051            } else {
4052                m_currentEditLevel++;
4053                message = Messages.get().key(Messages.GUI_SWITCH_EDIT_LEVEL_1, Integer.valueOf(m_currentEditLevel));
4054            }
4055            reinitializeButtons();
4056            hasEditables = !CmsDomUtil.getElementsByClass(
4057                I_CmsElementToolbarContext.ELEMENT_OPTION_BAR_CSS_CLASS).isEmpty();
4058        }
4059        if (previousLevel != m_currentEditLevel) {
4060            CmsNotification.get().send(Type.NORMAL, message);
4061        }
4062    }
4063
4064    /**
4065     * Returns all element id's related to the given one.<p>
4066     *
4067     * @param id the element id
4068     * @return the related id's
4069     */
4070    private Set<String> getRelatedElementIds(String id) {
4071
4072        Set<String> result = new HashSet<String>();
4073        if (id != null) {
4074            result.add(id);
4075            String serverId = getServerId(id);
4076
4077            Iterator<String> it = m_elements.keySet().iterator();
4078            while (it.hasNext()) {
4079                String elId = it.next();
4080                if (elId.startsWith(serverId)) {
4081                    result.add(elId);
4082                }
4083            }
4084
4085            Iterator<org.opencms.ade.containerpage.client.ui.CmsContainerPageElementPanel> itEl = getAllDragElements().iterator();
4086            while (itEl.hasNext()) {
4087                org.opencms.ade.containerpage.client.ui.CmsContainerPageElementPanel element = itEl.next();
4088                if (element.getId().startsWith(serverId)) {
4089                    result.add(element.getId());
4090                }
4091            }
4092        }
4093        return result;
4094    }
4095
4096    /**
4097     * Checks whether the given container matches the current edit level.<p>
4098     *
4099     * @param container the container to check
4100     *
4101     * @return <code>true</code> if the given container matches the current edit level
4102     */
4103    private boolean matchesCurrentEditLevel(I_CmsDropContainer container) {
4104
4105        boolean result = !(container instanceof CmsContainerPageContainer)
4106            || (m_currentEditLevel == -1)
4107            || (m_currentEditLevel == ((CmsContainerPageContainer)container).getContainerLevel());
4108        return result;
4109    }
4110
4111    /**
4112     * Opens the next available root element view.<p>
4113     */
4114    private void openNextElementView() {
4115
4116        List<CmsElementViewInfo> views = getData().getElementViews();
4117        if (views.size() > 1) {
4118            CmsUUID current = m_elementView.getRootViewId();
4119
4120            // look for the current view index
4121            int currentIndex = -1;
4122            for (int i = 0; i < views.size(); i++) {
4123                CmsElementViewInfo view = views.get(i);
4124                if (view.isRoot() && current.equals(view.getElementViewId())) {
4125                    currentIndex = i;
4126                    break;
4127                }
4128            }
4129            if (currentIndex != -1) {
4130                CmsElementViewInfo target = null;
4131                // look for the next root view
4132                for (int i = currentIndex + 1; i < views.size(); i++) {
4133                    CmsElementViewInfo view = views.get(i);
4134                    if (view.isRoot()) {
4135                        target = view;
4136                        break;
4137                    }
4138                }
4139                if (target == null) {
4140                    // start at the beginning
4141                    for (int i = 0; i < currentIndex; i++) {
4142                        CmsElementViewInfo view = views.get(i);
4143                        if (view.isRoot()) {
4144                            target = view;
4145                            break;
4146                        }
4147                    }
4148                }
4149                if (target != null) {
4150                    final String viewName = target.getTitle();
4151                    Runnable action = new Runnable() {
4152
4153                        public void run() {
4154
4155                            CmsNotification.get().send(
4156                                Type.NORMAL,
4157                                Messages.get().key(Messages.GUI_SWITCH_ELEMENT_VIEW_NOTIFICATION_1, viewName));
4158                        }
4159                    };
4160                    setElementView(target, action);
4161                }
4162            }
4163        }
4164    }
4165
4166    /**
4167     * Removes the edit buttons position timer.<p>
4168     */
4169    private void removeEditButtonsPositionTimer() {
4170
4171        if (m_editButtonsPositionTimer != null) {
4172            m_editButtonsPositionTimer.cancel();
4173            m_editButtonsPositionTimer = null;
4174        }
4175    }
4176
4177    /**
4178     * Calls the browser's sendBeacon function.
4179     *
4180     * @param url the URL to send the data to
4181     * @param data the data to send
4182     */
4183    private native void sendBeacon(String url, String data) /*-{
4184        $wnd.navigator.sendBeacon(url, data);
4185    }-*/;
4186
4187    /**
4188     * Checks whether given element is a model group and it's option bar edit points should be visible.<p>
4189     *
4190     * @param element the element to check
4191     *
4192     * @return <code>true</code> in case the current page is not a model group page,
4193     *  the given element is a model group and it is inside a view visible to the current user
4194     */
4195    private boolean shouldShowModelgroupOptionBar(CmsContainerPageElementPanel element) {
4196
4197        if (!getData().isModelGroup() && element.isModelGroup()) {
4198            for (CmsElementViewInfo info : getData().getElementViews()) {
4199                if (info.getElementViewId().equals(element.getElementView())) {
4200                    return true;
4201                }
4202            }
4203        }
4204
4205        return false;
4206    }
4207
4208    /**
4209     * Updates the container level info on the present containers.<p>
4210     */
4211    private void updateContainerLevelInfo() {
4212
4213        Map<String, CmsContainerPageContainer> containers = new HashMap<String, CmsContainerPageContainer>();
4214        List<CmsContainerPageContainer> temp = new ArrayList<CmsContainerPageContainer>(m_targetContainers.values());
4215        m_maxContainerLevel = 0;
4216        boolean progress = true;
4217        while (!temp.isEmpty() && progress) {
4218            int size = containers.size();
4219            Iterator<CmsContainerPageContainer> it = temp.iterator();
4220            while (it.hasNext()) {
4221                CmsContainerPageContainer container = it.next();
4222                int level = -1;
4223                if (CmsStringUtil.isEmptyOrWhitespaceOnly(container.getParentContainerId())) {
4224                    level = 0;
4225                } else if (containers.containsKey(container.getParentContainerId())) {
4226                    level = containers.get(container.getParentContainerId()).getContainerLevel() + 1;
4227                }
4228                if (level > -1) {
4229                    container.setContainerLevel(level);
4230                    containers.put(container.getContainerId(), container);
4231                    it.remove();
4232                    if (level > m_maxContainerLevel) {
4233                        m_maxContainerLevel = level;
4234                    }
4235                }
4236            }
4237            progress = containers.size() > size;
4238        }
4239    }
4240
4241    /**
4242     * Sets the oc-detail-preview class on first container elements of an appropriate type in detail containers,
4243     * if we are currently not showing a detail content.
4244     */
4245    private void updateDetailPreviewStyles() {
4246
4247        Set<String> detailTypes = getData().getDetailTypes();
4248        if ((getData().getDetailId() != null) || detailTypes.isEmpty()) {
4249            return;
4250        }
4251        for (Element elem : CmsDomUtil.getElementsByClass(CmsGwtConstants.CLASS_DETAIL_PREVIEW)) {
4252            elem.removeClassName(CmsGwtConstants.CLASS_DETAIL_PREVIEW);
4253        }
4254        boolean defaultDetailPage = detailTypes.contains(CmsGwtConstants.DEFAULT_DETAILPAGE_TYPE);
4255
4256        processPageContent(new I_PageContentVisitor() {
4257
4258            boolean m_isdetail = false;
4259
4260            public boolean beginContainer(String name, CmsContainer container) {
4261
4262                m_isdetail = container.isDetailViewContainer();
4263                return true;
4264            }
4265
4266            public void endContainer() {
4267
4268                // do nothing
4269            }
4270
4271            public void handleElement(CmsContainerPageElementPanel element) {
4272
4273                if (m_isdetail && (defaultDetailPage || detailTypes.contains(element.getResourceType()))) {
4274                    element.addStyleName(CmsGwtConstants.CLASS_DETAIL_PREVIEW);
4275                }
4276            }
4277        });
4278
4279    }
4280}