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