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