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                    getData().getDetailId(),
1044                    element.getId(),
1045                    element.getNewType(),
1046                    container,
1047                    getLocale(),
1048                    this);
1049
1050            }
1051
1052            /**
1053             * @see org.opencms.gwt.client.rpc.CmsRpcAction#onResponse(java.lang.Object)
1054             */
1055            @Override
1056            protected void onResponse(CmsCreateElementData result) {
1057
1058                if (result.needsModelSelection()) {
1059                    getHandler().openModelResourceSelect(element, result.getModelResources());
1060                } else {
1061                    openEditorForNewElement(element, result.getCreatedElement(), inline);
1062                }
1063            }
1064        };
1065        action.execute();
1066    }
1067
1068    /**
1069     * Creates a new resource for drag container elements with the status new and opens the content editor.<p>
1070     *
1071     * @param element the container element
1072     * @param modelResourceStructureId the model resource structure id
1073     */
1074    public void createAndEditNewElement(
1075        final org.opencms.ade.containerpage.client.ui.CmsContainerPageElementPanel element,
1076        final CmsUUID modelResourceStructureId) {
1077
1078        CmsRpcAction<CmsContainerElement> action = new CmsRpcAction<CmsContainerElement>() {
1079
1080            @Override
1081            public void execute() {
1082
1083                getContainerpageService().createNewElement(
1084                    CmsCoreProvider.get().getStructureId(),
1085                    getData().getDetailId(),
1086                    element.getId(),
1087                    element.getNewType(),
1088                    modelResourceStructureId,
1089                    getLocale(),
1090                    this);
1091
1092            }
1093
1094            @Override
1095            protected void onResponse(CmsContainerElement result) {
1096
1097                openEditorForNewElement(element, result, false);
1098
1099            }
1100        };
1101        action.execute();
1102    }
1103
1104    /**
1105     * Creates a new element.<p>
1106     *
1107     * @param element the widget belonging to the element which is currently in memory only
1108     * @param callback the callback to call with the result
1109     */
1110    public void createNewElement(
1111        final CmsContainerPageElementPanel element,
1112        final AsyncCallback<CmsContainerElement> callback) {
1113
1114        CmsRpcAction<CmsContainerElement> action = new CmsRpcAction<CmsContainerElement>() {
1115
1116            @Override
1117            public void execute() {
1118
1119                getContainerpageService().createNewElement(
1120                    CmsCoreProvider.get().getStructureId(),
1121                    getData().getDetailId(),
1122                    element.getId(),
1123                    element.getNewType(),
1124                    null,
1125                    getLocale(),
1126                    this);
1127
1128            }
1129
1130            @Override
1131            protected void onResponse(CmsContainerElement result) {
1132
1133                callback.onSuccess(result);
1134            }
1135        };
1136        action.execute();
1137    }
1138
1139    /**
1140     * Deletes an element from the VFS, removes it from all containers and the client side cache.<p>
1141     *
1142     * @param elementId the element to delete
1143     * @param relatedElementId related element to reload after the element has been deleted
1144     */
1145    public void deleteElement(String elementId, final String relatedElementId) {
1146
1147        elementId = getServerId(elementId);
1148        removeContainerElements(elementId);
1149        addToRecentList(elementId, null);
1150        reloadElements(new String[] {relatedElementId}, () -> {/*do nothing*/});
1151    }
1152
1153    /**
1154     * Disables the inline editing for all content elements but the given one.<p>
1155     *
1156     * @param notThisOne the content element not to disable
1157     */
1158    public void disableInlineEditing(CmsContainerPageElementPanel notThisOne) {
1159
1160        removeEditButtonsPositionTimer();
1161        if (isGroupcontainerEditing()) {
1162            for (Widget element : m_groupEditor.getGroupContainerWidget()) {
1163                if ((element instanceof CmsContainerPageElementPanel) && (element != notThisOne)) {
1164                    ((CmsContainerPageElementPanel)element).removeInlineEditor();
1165                }
1166            }
1167        } else {
1168            for (org.opencms.ade.containerpage.client.ui.CmsContainerPageContainer container : m_targetContainers.values()) {
1169                for (Widget element : container) {
1170                    if ((element instanceof CmsContainerPageElementPanel) && (element != notThisOne)) {
1171                        ((CmsContainerPageElementPanel)element).removeInlineEditor();
1172                    }
1173                }
1174            }
1175        }
1176    }
1177
1178    /**
1179     * Enables the favorites editing drag and drop controller.<p>
1180     *
1181     * @param enable if <code>true</code> favorites editing will enabled, otherwise disabled
1182     * @param dndController the favorites editing drag and drop controller
1183     */
1184    public void enableFavoriteEditing(boolean enable, I_CmsDNDController dndController) {
1185
1186        if (m_dndHandler.isDragging()) {
1187            // never switch drag and drop controllers while dragging
1188            return;
1189        }
1190        if (enable) {
1191            m_dndHandler.setController(dndController);
1192        } else {
1193            m_dndHandler.setController(m_cntDndController);
1194        }
1195
1196    }
1197
1198    /**
1199     * Replaces all element instances of the original element with the new element within the former copy model.<p>
1200     *
1201     * @param originalElementId the original element id
1202     * @param modelGroupParent the model group parent element
1203     * @param elementData the replace element data
1204     */
1205    public void executeCopyModelReplace(
1206        String originalElementId,
1207        Element modelGroupParent,
1208        CmsContainerElementData elementData) {
1209
1210        String serverId = getServerId(originalElementId);
1211        for (CmsContainerPageContainer cont : m_targetContainers.values()) {
1212            if (modelGroupParent.isOrHasChild(cont.getElement())) {
1213                // look for instances of the original element
1214                for (Widget child : cont) {
1215                    if ((child instanceof CmsContainerPageElementPanel)
1216                        && ((CmsContainerPageElementPanel)child).getId().startsWith(serverId)) {
1217                        CmsContainerPageElementPanel replacer = null;
1218                        String elementContent = elementData.getContents().get(cont.getContainerId());
1219                        if ((elementContent != null) && (elementContent.trim().length() > 0)) {
1220                            try {
1221                                replacer = getContainerpageUtil().createElement(elementData, cont, false);
1222                                cont.insert(replacer, cont.getWidgetIndex(child));
1223                                child.removeFromParent();
1224                                initializeSubContainers(replacer);
1225                            } catch (Exception e) {
1226                                //ignore
1227                            }
1228                        }
1229                    }
1230                }
1231            }
1232        }
1233    }
1234
1235    /**
1236     * Fires an event on the core event bus.<p>
1237     *
1238     * @param event the event to fire
1239     */
1240    public void fireEvent(CmsContainerpageEvent event) {
1241
1242        CmsCoreProvider.get().getEventBus().fireEvent(event);
1243
1244    }
1245
1246    /**
1247     * Returns all drag elements of the page.<p>
1248     *
1249     * @return the drag elements
1250     */
1251    public List<org.opencms.ade.containerpage.client.ui.CmsContainerPageElementPanel> getAllDragElements() {
1252
1253        List<org.opencms.ade.containerpage.client.ui.CmsContainerPageElementPanel> result = new ArrayList<org.opencms.ade.containerpage.client.ui.CmsContainerPageElementPanel>();
1254        Iterator<org.opencms.ade.containerpage.client.ui.CmsContainerPageContainer> it = m_targetContainers.values().iterator();
1255        while (it.hasNext()) {
1256            result.addAll(it.next().getAllDragElements());
1257        }
1258        if (isGroupcontainerEditing()) {
1259            Iterator<Widget> itSub = m_groupEditor.getGroupContainerWidget().iterator();
1260            while (itSub.hasNext()) {
1261                Widget w = itSub.next();
1262                if (w instanceof org.opencms.ade.containerpage.client.ui.CmsContainerPageElementPanel) {
1263                    result.add((org.opencms.ade.containerpage.client.ui.CmsContainerPageElementPanel)w);
1264                }
1265            }
1266        }
1267        return result;
1268    }
1269
1270    /**
1271     * Returns the data for the requested element, or <code>null</code> if the element has not been cached yet.<p>
1272     *
1273     * @param clientId the element id
1274     *
1275     * @return the element data
1276     */
1277    public CmsContainerElementData getCachedElement(String clientId) {
1278
1279        if (m_elements.containsKey(clientId)) {
1280            return m_elements.get(clientId);
1281        }
1282        return null;
1283    }
1284
1285    /**
1286     * Returns the data for the requested element, or <code>null</code> if the element has not been cached yet.<p>
1287     *
1288     * @param resourceTypeName the element resource type
1289     *
1290     * @return the element data
1291     */
1292    public CmsContainerElementData getCachedNewElement(String resourceTypeName) {
1293
1294        if (m_newElements.containsKey(resourceTypeName)) {
1295            return m_newElements.get(resourceTypeName);
1296        }
1297        return null;
1298    }
1299
1300    /**
1301     * Returns the container data of container with the given name.
1302     *
1303     * @param containerName the container name
1304     *
1305     * @return the container data
1306     */
1307    public CmsContainer getContainer(String containerName) {
1308
1309        return m_containers.get(containerName);
1310    }
1311
1312    /**
1313     * Gets the container element widget to which the given element belongs, or Optional.absent if none could be found.<p>
1314     *
1315     * @param element the element for which the container element widget should be found
1316     *
1317     * @return the container element widget, or Optional.absent if none can be found
1318     */
1319    public Optional<CmsContainerPageElementPanel> getContainerElementWidgetForElement(Element element) {
1320
1321        final Element parentContainerElement = CmsDomUtil.getAncestor(
1322            element,
1323            I_CmsLayoutBundle.INSTANCE.dragdropCss().dragElement());
1324        if (parentContainerElement == null) {
1325            return Optional.absent();
1326        }
1327        final List<CmsContainerPageElementPanel> result = Lists.newArrayList();
1328        processPageContent(new I_PageContentVisitor() {
1329
1330            public boolean beginContainer(String name, CmsContainer container) {
1331
1332                // we don't need to look into the container if we have already found our container element
1333                return result.isEmpty();
1334            }
1335
1336            public void endContainer() {
1337
1338                // do nothing
1339            }
1340
1341            public void handleElement(CmsContainerPageElementPanel current) {
1342
1343                if ((current.getElement() == parentContainerElement) && result.isEmpty()) {
1344                    result.add(current);
1345                }
1346            }
1347        });
1348        if (result.isEmpty()) {
1349            return Optional.absent();
1350        } else {
1351            return Optional.fromNullable(result.get(0));
1352        }
1353    }
1354
1355    /**
1356     * Gets the container info to send to the gallery service.
1357     *
1358     * @return the container info to send to the gallery service
1359     */
1360    public CmsGalleryContainerInfo getContainerInfoForGalleries() {
1361
1362        if (m_targetContainers != null) {
1363            HashSet<CmsGalleryContainerInfo.Item> items = new HashSet<>();
1364            for (CmsContainerPageContainer cont : m_targetContainers.values()) {
1365                items.add(new CmsGalleryContainerInfo.Item(cont.getContainerType(), cont.getConfiguredWidth()));
1366            }
1367            return new CmsGalleryContainerInfo(items);
1368        }
1369        return null;
1370    }
1371
1372    /**
1373     * Returns the container-page RPC service.<p>
1374     *
1375     * @return the container-page service
1376     */
1377    public I_CmsContainerpageServiceAsync getContainerpageService() {
1378
1379        if (m_containerpageService == null) {
1380            m_containerpageService = GWT.create(I_CmsContainerpageService.class);
1381            String serviceUrl = CmsCoreProvider.get().link("org.opencms.ade.containerpage.CmsContainerpageService.gwt");
1382            ((ServiceDefTarget)m_containerpageService).setServiceEntryPoint(serviceUrl);
1383        }
1384        return m_containerpageService;
1385    }
1386
1387    /**
1388     * Returns the {@link org.opencms.ade.containerpage.client.CmsContainerpageUtil}.<p>
1389     *
1390     * @return the containerpage-util
1391     */
1392    public CmsContainerpageUtil getContainerpageUtil() {
1393
1394        return m_containerpageUtil;
1395    }
1396
1397    /**
1398     * Returns the containers.<p>
1399     *
1400     * @return the containers
1401     */
1402    public Map<String, CmsContainer> getContainers() {
1403
1404        return m_containers;
1405    }
1406
1407    /**
1408     * Returns the container drag target by name (HTML id attribute).<p>
1409     *
1410     * @param containerName the container name
1411     * @return the drag target
1412     */
1413    public org.opencms.ade.containerpage.client.ui.CmsContainerPageContainer getContainerTarget(String containerName) {
1414
1415        return m_targetContainers.get(containerName);
1416    }
1417
1418    /**
1419     * Returns a map of the container drag targets.<p>
1420     *
1421     * @return the drag targets
1422     */
1423    public Map<String, org.opencms.ade.containerpage.client.ui.CmsContainerPageContainer> getContainerTargets() {
1424
1425        Map<String, org.opencms.ade.containerpage.client.ui.CmsContainerPageContainer> result = new HashMap<String, org.opencms.ade.containerpage.client.ui.CmsContainerPageContainer>();
1426        for (Entry<String, org.opencms.ade.containerpage.client.ui.CmsContainerPageContainer> entry : m_targetContainers.entrySet()) {
1427            if (entry.getValue().isEditable()
1428                && (!isDetailPage() || (entry.getValue().isDetailOnly() || entry.getValue().isDetailView()))) {
1429                result.put(entry.getKey(), entry.getValue());
1430            }
1431        }
1432        return result;
1433    }
1434
1435    /**
1436     * Returns the type of container with the given name.<p>
1437     *
1438     * @param containerName the container name
1439     *
1440     * @return the container type
1441     */
1442    public String getContainerType(String containerName) {
1443
1444        return getContainer(containerName).getType();
1445    }
1446
1447    /**
1448     * Returns the XML content editor handler.<p>
1449     *
1450     * @return the XML content editor handler
1451     */
1452    public CmsContentEditorHandler getContentEditorHandler() {
1453
1454        return m_contentEditorHandler;
1455    }
1456
1457    /**
1458     * Returns the prefetched data.<p>
1459     *
1460     * @return the prefetched data
1461     */
1462    public CmsCntPageData getData() {
1463
1464        return m_data;
1465    }
1466
1467    /**
1468     * Returns the delete options for the given content element.<p>
1469     *
1470     * @param clientId the content id
1471     * @param callback the callback to execute
1472     */
1473    public void getDeleteOptions(final String clientId, final I_CmsSimpleCallback<CmsDialogOptionsAndInfo> callback) {
1474
1475        CmsRpcAction<CmsDialogOptionsAndInfo> action = new CmsRpcAction<CmsDialogOptionsAndInfo>() {
1476
1477            @Override
1478            public void execute() {
1479
1480                getContainerpageService().getDeleteOptions(
1481                    clientId,
1482                    getData().getRpcContext().getPageStructureId(),
1483                    getData().getRequestParams(),
1484                    this);
1485            }
1486
1487            @Override
1488            protected void onResponse(CmsDialogOptionsAndInfo result) {
1489
1490                callback.execute(result);
1491            }
1492        };
1493        action.execute();
1494    }
1495
1496    /**
1497     * Gets the DND controller.<p>
1498     *
1499     * @return the DND controller
1500     */
1501    public CmsCompositeDNDController getDndController() {
1502
1503        return m_dndController;
1504    }
1505
1506    /**
1507     * Returns the drag and drop handler.<p>
1508     *
1509     * @return the drag and drop handler
1510     */
1511    public CmsDNDHandler getDndHandler() {
1512
1513        return m_dndHandler;
1514    }
1515
1516    /**
1517     * Returns the edit options for the given content element.<p>
1518     *
1519     * @param clientId the content id
1520     * @param isListElement in case a list element, not a container element is about to be edited
1521     * @param callback the callback to execute
1522     */
1523    public void getEditOptions(
1524        final String clientId,
1525        final boolean isListElement,
1526        final I_CmsSimpleCallback<CmsDialogOptionsAndInfo> callback) {
1527
1528        CmsRpcAction<CmsDialogOptionsAndInfo> action = new CmsRpcAction<CmsDialogOptionsAndInfo>() {
1529
1530            @Override
1531            public void execute() {
1532
1533                getContainerpageService().getEditOptions(
1534                    clientId,
1535                    getData().getRpcContext().getPageStructureId(),
1536                    getData().getRequestParams(),
1537                    isListElement,
1538                    this);
1539            }
1540
1541            @Override
1542            protected void onResponse(CmsDialogOptionsAndInfo result) {
1543
1544                callback.execute(result);
1545            }
1546        };
1547        action.execute();
1548    }
1549
1550    /**
1551     * 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>
1552     *
1553     * @param clientId the element id
1554     * @param containerId the id of the container from which the element is being dragged
1555     * @param alwaysCopy <code>true</code> in case reading data for a clipboard element used as a copy group
1556     * @param callback the call-back to execute with the requested data
1557     */
1558    public void getElementForDragAndDropFromContainer(
1559        final String clientId,
1560        final String containerId,
1561        boolean alwaysCopy,
1562        final I_CmsSimpleCallback<CmsContainerElementData> callback) {
1563
1564        SingleElementAction action = new SingleElementAction(clientId, alwaysCopy, callback);
1565        action.setDndContainer(containerId);
1566        action.execute();
1567    }
1568
1569    /**
1570     * Requests the data for container elements specified by the client id. The data will be provided to the given call-back function.<p>
1571     *
1572     * @param clientIds the element id's
1573     * @param callback the call-back to execute with the requested data
1574     */
1575    public void getElements(Set<String> clientIds, I_CmsSimpleCallback<Map<String, CmsContainerElementData>> callback) {
1576
1577        MultiElementAction action = new MultiElementAction(clientIds, callback);
1578        action.execute();
1579    }
1580
1581    /**
1582     * 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>
1583     *
1584     * @param clientId the element id
1585     * @param containerId the parent container id
1586     * @param callback the call-back to execute with the requested data
1587     */
1588    public void getElementSettingsConfig(
1589        final String clientId,
1590        final String containerId,
1591        final I_CmsSimpleCallback<CmsElementSettingsConfig> callback) {
1592
1593        CmsRpcAction<CmsElementSettingsConfig> action = new CmsRpcAction<CmsElementSettingsConfig>() {
1594
1595            /**
1596             * @see org.opencms.gwt.client.rpc.CmsRpcAction#execute()
1597             */
1598            @Override
1599            public void execute() {
1600
1601                start(100, true);
1602                getContainerpageService().getElementSettingsConfig(
1603                    getData().getRpcContext(),
1604                    clientId,
1605                    containerId,
1606                    getPageState(),
1607                    getLocale(),
1608                    this);
1609
1610            }
1611
1612            /**
1613             * @see org.opencms.gwt.client.rpc.CmsRpcAction#onResponse(java.lang.Object)
1614             */
1615            @Override
1616            protected void onResponse(CmsElementSettingsConfig result) {
1617
1618                if (result != null) {
1619                    callback.execute(result);
1620                }
1621                stop(false);
1622            }
1623        };
1624        action.execute();
1625    }
1626
1627    /**
1628     * Returns the current element view.<p>
1629     *
1630     * @return the current element view
1631     */
1632    public CmsElementViewInfo getElementView() {
1633
1634        return m_elementView;
1635    }
1636
1637    /**
1638     * Retrieves a container element with a given set of settings.<p>
1639     *
1640     * @param clientId the id of the container element
1641     * @param settings the set of settings
1642     *
1643     * @param callback the callback which should be executed when the element has been loaded
1644     */
1645    public void getElementWithSettings(
1646        final String clientId,
1647        final Map<String, String> settings,
1648        final I_CmsSimpleCallback<CmsContainerElementData> callback) {
1649
1650        CmsRpcAction<CmsContainerElementData> action = new CmsRpcAction<CmsContainerElementData>() {
1651
1652            /**
1653             * @see org.opencms.gwt.client.rpc.CmsRpcAction#execute()
1654             */
1655            @Override
1656            public void execute() {
1657
1658                start(200, false);
1659                getContainerpageService().getElementWithSettings(
1660                    getData().getRpcContext(),
1661                    getData().getDetailId(),
1662                    getRequestParams(),
1663                    clientId,
1664                    settings,
1665                    getPageState(),
1666                    getLocale(),
1667                    this);
1668
1669            }
1670
1671            /**
1672             * @see org.opencms.gwt.client.rpc.CmsRpcAction#onResponse(java.lang.Object)
1673             */
1674            @Override
1675            protected void onResponse(CmsContainerElementData result) {
1676
1677                stop(false);
1678                if (result != null) {
1679                    // cache the loaded element
1680                    m_elements.put(result.getClientId(), result);
1681                }
1682                callback.execute(result);
1683            }
1684
1685        };
1686        action.execute();
1687
1688    }
1689
1690    /**
1691     * Returns the group-container element being edited.<p>
1692     *
1693     * @return the group-container
1694     */
1695    public CmsGroupContainerElementPanel getGroupcontainer() {
1696
1697        return m_groupEditor.getGroupContainerWidget();
1698    }
1699
1700    /**
1701     * Returns the id of the currently edited group-container.<p>
1702     *
1703     * @return the group-container id, or <code>null</code> if no editing is taking place
1704     */
1705    public String getGroupcontainerId() {
1706
1707        if (m_groupEditor != null) {
1708            return m_groupEditor.getGroupContainerWidget().getContainerId();
1709        }
1710        return null;
1711    }
1712
1713    /**
1714     * Returns the container-page handler.<p>
1715     *
1716     * @return the container-page handler
1717     */
1718    public CmsContainerpageHandler getHandler() {
1719
1720        return m_handler;
1721    }
1722
1723    /**
1724     * Returns the time off page load.<p>
1725     *
1726     * @return the time stamp
1727     */
1728    public long getLoadTime() {
1729
1730        return m_loadTime;
1731    }
1732
1733    /**
1734     * Gets the lock error message.<p>
1735     *
1736     * @return the lock error message
1737     */
1738    public String getLockErrorMessage() {
1739
1740        return m_lockErrorMessage;
1741    }
1742
1743    /**
1744     * Returns the model group base element id.<p>
1745     *
1746     * @return the model group base element id
1747     */
1748    public String getModelGroupElementId() {
1749
1750        return m_modelGroupElementId;
1751    }
1752
1753    /**
1754     * Collects all container elements which are model groups.<p>
1755     *
1756     * @return the list of model group container elements
1757     */
1758    public List<CmsContainerPageElementPanel> getModelGroups() {
1759
1760        final List<CmsContainerPageElementPanel> result = Lists.newArrayList();
1761
1762        processPageContent(new I_PageContentVisitor() {
1763
1764            public boolean beginContainer(String name, CmsContainer container) {
1765
1766                return true;
1767            }
1768
1769            public void endContainer() {
1770
1771                // do nothing
1772            }
1773
1774            public void handleElement(CmsContainerPageElementPanel element) {
1775
1776                if (element.isModelGroup()) {
1777                    result.add(element);
1778                }
1779            }
1780        });
1781        return result;
1782    }
1783
1784    /**
1785     * Returns the element data for a resource type representing a new element.<p>
1786     *
1787     * @param resourceType the resource type name
1788     * @param callback the callback to execute with the new element data
1789     */
1790    public void getNewElement(final String resourceType, final I_CmsSimpleCallback<CmsContainerElementData> callback) {
1791
1792        CmsRpcAction<CmsContainerElementData> action = new CmsRpcAction<CmsContainerElementData>() {
1793
1794            @Override
1795            public void execute() {
1796
1797                getContainerpageService().getNewElementData(
1798                    getData().getRpcContext(),
1799                    getData().getDetailId(),
1800                    getRequestParams(),
1801                    resourceType,
1802                    getPageState(),
1803                    getLocale(),
1804                    this);
1805            }
1806
1807            @Override
1808            protected void onResponse(CmsContainerElementData result) {
1809
1810                m_elements.put(result.getClientId(), result);
1811                callback.execute(result);
1812            }
1813        };
1814        action.execute();
1815    }
1816
1817    /**
1818     * Fetches the options for creating a new element from the edit handler.<p>
1819     *
1820     * @param clientId the client id of the element
1821     * @param callback the callback which is called with the result
1822     */
1823    public void getNewOptions(final String clientId, final I_CmsSimpleCallback<CmsDialogOptionsAndInfo> callback) {
1824
1825        CmsRpcAction<CmsDialogOptionsAndInfo> action = new CmsRpcAction<CmsDialogOptionsAndInfo>() {
1826
1827            @Override
1828            public void execute() {
1829
1830                getContainerpageService().getNewOptions(
1831                    clientId,
1832                    getData().getRpcContext().getPageStructureId(),
1833                    getData().getRequestParams(),
1834                    this);
1835            }
1836
1837            @Override
1838            protected void onResponse(CmsDialogOptionsAndInfo result) {
1839
1840                callback.execute(result);
1841            }
1842        };
1843
1844        action.execute();
1845    }
1846
1847    /**
1848     * Produces the "return code", which is needed to return to the current page from the sitemap.<p>
1849     *
1850     * @return the return code
1851     */
1852    public String getReturnCode() {
1853
1854        CmsUUID ownId = CmsCoreProvider.get().getStructureId();
1855        CmsUUID detailId = m_data.getDetailId();
1856        if (detailId != null) {
1857            return "" + ownId + ":" + detailId;
1858        } else {
1859            return "" + ownId;
1860        }
1861    }
1862
1863    /**
1864     * Returns the deserialized element data.<p>
1865     *
1866     * @param data the data to deserialize
1867     *
1868     * @return the container element
1869     * @throws SerializationException if deserialization fails
1870     */
1871    public CmsContainer getSerializedContainer(String data) throws SerializationException {
1872
1873        return (CmsContainer)CmsRpcPrefetcher.getSerializedObjectFromString(getContainerpageService(), data);
1874    }
1875
1876    /**
1877     * Returns the deserialized element data.<p>
1878     *
1879     * @param data the data to deserialize
1880     *
1881     * @return the container element
1882     * @throws SerializationException if deserialization fails
1883     */
1884    public CmsContainerElement getSerializedElement(String data) throws SerializationException {
1885
1886        return (CmsContainerElement)CmsRpcPrefetcher.getSerializedObjectFromString(getContainerpageService(), data);
1887    }
1888
1889    /**
1890     * Gets the handler for small elements.<p>
1891     *
1892     * @return the small elements handler
1893     */
1894    public CmsSmallElementsHandler getSmallElementsHandler() {
1895
1896        return m_smallElementsHandler;
1897    }
1898
1899    /**
1900     * Gets the view with the given id.<p>
1901     *
1902     * @param value the view id as a string
1903     *
1904     * @return the view with the given id, or null if no such view is available
1905     */
1906    public CmsElementViewInfo getView(String value) {
1907
1908        for (CmsElementViewInfo info : m_data.getElementViews()) {
1909            if (info.getElementViewId().toString().equals(value)) {
1910                return info;
1911            }
1912        }
1913        return null;
1914    }
1915
1916    /**
1917     * Handler that gets called when the template context setting of an element was changed by the user.<p>
1918     *
1919     * @param element the element whose template context setting was changed
1920     *
1921     * @param newValue the new value of the setting
1922     */
1923    public void handleChangeTemplateContext(final CmsContainerPageElementPanel element, final String newValue) {
1924
1925        if (CmsStringUtil.isEmptyOrWhitespaceOnly(newValue) || CmsTemplateContextInfo.EMPTY_VALUE.equals(newValue)) {
1926            if (CmsInheritanceContainerEditor.getInstance() != null) {
1927                CmsInheritanceContainerEditor.getInstance().removeElement(element);
1928            } else {
1929                removeElement(element, ElementRemoveMode.silent);
1930            }
1931        }
1932    }
1933
1934    /**
1935     * Asks the user for confirmation before removing a container page element.<p>
1936     *
1937     * @param element the element for which the user should confirm the removal
1938     */
1939    public void handleConfirmRemove(final CmsContainerPageElementPanel element) {
1940
1941        if (element.isNew()) {
1942            element.removeFromParent();
1943            cleanUpContainers();
1944            setPageChanged();
1945            return;
1946        }
1947        checkElementReferences(element, new AsyncCallback<CmsRemovedElementStatus>() {
1948
1949            public void onFailure(Throwable caught) {
1950
1951                // ignore, will never be executed
1952
1953            }
1954
1955            public void onSuccess(CmsRemovedElementStatus status) {
1956
1957                boolean showDeleteCheckbox = status.isDeletionCandidate();
1958                ElementDeleteMode deleteMode = status.getElementDeleteMode();
1959                if (deleteMode == null) {
1960                    deleteMode = getData().getDeleteMode();
1961                }
1962                CmsConfirmRemoveDialog removeDialog = new CmsConfirmRemoveDialog(
1963                    status.getElementInfo(),
1964                    showDeleteCheckbox,
1965                    deleteMode,
1966                    new AsyncCallback<Boolean>() {
1967
1968                        public void onFailure(Throwable caught) {
1969
1970                            element.removeHighlighting();
1971                        }
1972
1973                        public void onSuccess(Boolean shouldDeleteResource) {
1974
1975                            Runnable[] nextActions = new Runnable[] {};
1976
1977                            if (shouldDeleteResource.booleanValue()) {
1978                                final CmsRpcAction<Void> deleteAction = new CmsRpcAction<Void>() {
1979
1980                                    @Override
1981                                    public void execute() {
1982
1983                                        start(200, true);
1984
1985                                        CmsUUID id = new CmsUUID(getServerId(element.getId()));
1986                                        CmsCoreProvider.getVfsService().deleteResource(id, this);
1987                                    }
1988
1989                                    @Override
1990                                    public void onResponse(Void result) {
1991
1992                                        stop(true);
1993                                    }
1994                                };
1995                                nextActions = new Runnable[] {new Runnable() {
1996
1997                                    public void run() {
1998
1999                                        deleteAction.execute();
2000                                    }
2001                                }};
2002                            }
2003                            I_CmsDropContainer container = element.getParentTarget();
2004                            element.removeFromParent();
2005                            if (container instanceof CmsContainerPageContainer) {
2006                                ((CmsContainerPageContainer)container).checkEmptyContainers();
2007                            }
2008                            cleanUpContainers();
2009                            setPageChanged(nextActions);
2010                        }
2011                    });
2012                removeDialog.center();
2013            }
2014
2015        });
2016    }
2017
2018    /**
2019     * Calls the edit handler to handle the delete action.<p>
2020     *
2021     * @param clientId the content client id
2022     * @param deleteOption the selected delete option
2023     * @param callback the callback to execute after the delete
2024     */
2025    public void handleDelete(
2026        final String clientId,
2027        final String deleteOption,
2028        final I_CmsSimpleCallback<Void> callback) {
2029
2030        CmsRpcAction<Void> action = new CmsRpcAction<Void>() {
2031
2032            @Override
2033            public void execute() {
2034
2035                getContainerpageService().handleDelete(
2036                    clientId,
2037                    deleteOption,
2038                    getData().getRpcContext().getPageStructureId(),
2039                    getData().getRequestParams(),
2040                    this);
2041            }
2042
2043            @Override
2044            protected void onResponse(Void result) {
2045
2046                if (callback != null) {
2047                    callback.execute(result);
2048                }
2049            }
2050        };
2051        action.execute();
2052    }
2053
2054    /**
2055     * Returns if the selection button is active.<p>
2056     *
2057     * @return <code>true</code> if the selection button is active
2058     */
2059    public boolean hasActiveSelection() {
2060
2061        return m_handler.hasActiveSelection();
2062    }
2063
2064    /**
2065     * Returns if the page has changed.<p>
2066     *
2067     * @return <code>true</code> if the page has changed
2068     */
2069    public boolean hasPageChanged() {
2070
2071        return m_pageChanged;
2072    }
2073
2074    /**
2075     * Hides list collector direct edit buttons, if present.<p>
2076     */
2077    public void hideEditableListButtons() {
2078
2079        removeEditButtonsPositionTimer();
2080        for (org.opencms.ade.containerpage.client.ui.CmsContainerPageContainer container : m_targetContainers.values()) {
2081            container.hideEditableListButtons();
2082        }
2083    }
2084
2085    /**
2086     * Initializes the controller.<p>
2087     *
2088     * @param handler the container-page handler
2089     * @param dndHandler the drag and drop handler
2090     * @param contentEditorHandler the XML content editor handler
2091     * @param containerpageUtil the container-page utility
2092     */
2093    public void init(
2094        CmsContainerpageHandler handler,
2095        CmsDNDHandler dndHandler,
2096        CmsContentEditorHandler contentEditorHandler,
2097        CmsContainerpageUtil containerpageUtil) {
2098
2099        Window.addResizeHandler(new ResizeHandler() {
2100
2101            public void onResize(ResizeEvent event) {
2102
2103                CmsContainerpageController.this.onResize();
2104            }
2105        });
2106        m_containerpageUtil = containerpageUtil;
2107        m_handler = handler;
2108        m_contentEditorHandler = contentEditorHandler;
2109        m_dndHandler = dndHandler;
2110        m_cntDndController = m_dndHandler.getController();
2111
2112        m_elements = new HashMap<String, CmsContainerElementData>();
2113        m_newElements = new HashMap<String, CmsContainerElementData>();
2114        m_containers = new HashMap<String, CmsContainer>();
2115        if (m_data == null) {
2116            m_handler.m_editor.disableEditing(Messages.get().key(Messages.ERR_READING_CONTAINER_PAGE_DATA_0));
2117            CmsErrorDialog dialog = new CmsErrorDialog(
2118                Messages.get().key(Messages.ERR_READING_CONTAINER_PAGE_DATA_0),
2119                null);
2120            dialog.center();
2121            return;
2122        }
2123        // ensure any embedded flash players are set opaque so UI elements may be placed above them
2124        CmsDomUtil.fixFlashZindex(RootPanel.getBodyElement());
2125        m_targetContainers = m_containerpageUtil.consumeContainers(m_containers, RootPanel.getBodyElement());
2126        updateContainerLevelInfo();
2127        resetEditButtons();
2128        Event.addNativePreviewHandler(new NativePreviewHandler() {
2129
2130            public void onPreviewNativeEvent(NativePreviewEvent event) {
2131
2132                previewNativeEvent(event);
2133            }
2134        });
2135        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(m_data.getNoEditReason())) {
2136            m_handler.m_editor.disableEditing(m_data.getNoEditReason());
2137        } else {
2138            checkLockInfo();
2139        }
2140
2141        // initialize the browser history handler
2142        History.addValueChangeHandler(new ValueChangeHandler<String>() {
2143
2144            public void onValueChange(ValueChangeEvent<String> event) {
2145
2146                String historyToken = event.getValue();
2147                if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(historyToken)) {
2148                    getContentEditorHandler().openEditorForHistory(historyToken);
2149                } else {
2150                    getContentEditorHandler().closeContentEditor();
2151                }
2152            }
2153        });
2154        AsyncCallback<Void> doNothing = new AsyncCallback<Void>() {
2155
2156            public void onFailure(Throwable caught) {
2157
2158                // nothing to do
2159            }
2160
2161            public void onSuccess(Void result) {
2162
2163                // nothing to do
2164            }
2165        };
2166        getContainerpageService().setLastPage(CmsCoreProvider.get().getStructureId(), m_data.getDetailId(), doNothing);
2167
2168        // check if there is already a history item available
2169        String historyToken = History.getToken();
2170        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(historyToken)) {
2171            m_contentEditorHandler.openEditorForHistory(historyToken);
2172        }
2173
2174        updateGalleryData(false, null);
2175        addContainerpageEventHandler(event -> {
2176            updateDetailPreviewStyles();
2177        });
2178        updateDetailPreviewStyles();
2179        updateButtonsForCurrentView();
2180        startPublishLockCheck();
2181    }
2182
2183    /**
2184     * Checks for element sub containers.<p>
2185     *
2186     * @param containerElement the container element
2187     */
2188    public void initializeSubContainers(CmsContainerPageElementPanel containerElement) {
2189
2190        int containerCount = m_targetContainers.size();
2191        m_targetContainers.putAll(m_containerpageUtil.consumeContainers(m_containers, containerElement.getElement()));
2192        updateContainerLevelInfo();
2193        if (m_targetContainers.size() > containerCount) {
2194            // in case new containers have been added, the gallery data needs to be updated
2195            scheduleGalleryUpdate();
2196        }
2197    }
2198
2199    /**
2200     * Returns if the given container is editable.<p>
2201     *
2202     * @param dragParent the parent container
2203     *
2204     * @return <code>true</code> if the given container is editable
2205     */
2206    public boolean isContainerEditable(I_CmsDropContainer dragParent) {
2207
2208        boolean isSubElement = dragParent instanceof CmsGroupContainerElementPanel;
2209        boolean isContainerEditable = dragParent.isEditable()
2210            && (isSubElement || !isDetailPage() || dragParent.isDetailView() || dragParent.isDetailOnly());
2211        return isContainerEditable;
2212    }
2213
2214    /**
2215     * Returns the flag indicating that a content element is being edited.<p>
2216     *
2217     * @return the flag indicating that a content element is being edited
2218     */
2219    public boolean isContentEditing() {
2220
2221        return m_isContentEditing;
2222    }
2223
2224    /**
2225     * Returns if this page displays a detail view.<p>
2226     *
2227     * @return <code>true</code> if this page displays a detail view
2228     */
2229    public boolean isDetailPage() {
2230
2231        return m_data.getDetailId() != null;
2232    }
2233
2234    /**
2235     * Checks if the page editing features should be disabled.<p>
2236     *
2237     * @return true if the page editing features should be disabled
2238     */
2239    public boolean isEditingDisabled() {
2240
2241        return (m_data == null)
2242            || CmsStringUtil.isNotEmptyOrWhitespaceOnly(m_data.getNoEditReason())
2243            || (m_lockStatus == LockStatus.failed);
2244    }
2245
2246    /**
2247     * Returns if a group-container is currently being edited.<p>
2248     *
2249     * @return <code>true</code> if a group-container is being edited
2250     */
2251    public boolean isGroupcontainerEditing() {
2252
2253        return m_groupEditor != null;
2254    }
2255
2256    /**
2257     * Checks whether the given element should be inline editable.<p>
2258     *
2259     * @param element the element
2260     * @param dragParent the element parent
2261     *
2262     * @return <code>true</code> if the element should be inline editable
2263     */
2264    public boolean isInlineEditable(CmsContainerPageElementPanel element, I_CmsDropContainer dragParent) {
2265
2266        CmsUUID elemView = element.getElementView();
2267        return !getData().isUseClassicEditor()
2268            && CmsStringUtil.isEmptyOrWhitespaceOnly(element.getNoEditReason())
2269            && hasActiveSelection()
2270            && matchRootView(elemView)
2271            && isContainerEditable(dragParent)
2272            && matchesCurrentEditLevel(dragParent)
2273            && (getData().isModelGroup() || !element.hasModelGroupParent())
2274            && (!(dragParent instanceof CmsGroupContainerElementPanel) || isGroupcontainerEditing());
2275    }
2276
2277    /**
2278     * Method to leave the page without saving.<p>
2279     *
2280     * @param targetUri the new URI to call
2281     */
2282    public void leaveUnsaved(String targetUri) {
2283
2284        setPageChanged(false, true);
2285        Window.Location.assign(targetUri);
2286    }
2287
2288    /**
2289     * Loads the context menu entries.<p>
2290     *
2291     * @param structureId the structure id of the resource to get the context menu entries for
2292     * @param context the ade context (sitemap or containerpae)
2293     */
2294    public void loadContextMenu(final CmsUUID structureId, final AdeContext context) {
2295
2296        /** The RPC menu action for the container page dialog. */
2297        CmsRpcAction<List<CmsContextMenuEntryBean>> menuAction = new CmsRpcAction<List<CmsContextMenuEntryBean>>() {
2298
2299            /**
2300            * @see org.opencms.gwt.client.rpc.CmsRpcAction#execute()
2301            */
2302            @Override
2303            public void execute() {
2304
2305                getCoreService().getContextMenuEntries(structureId, context, this);
2306            }
2307
2308            /**
2309            * @see org.opencms.gwt.client.rpc.CmsRpcAction#onResponse(java.lang.Object)
2310            */
2311            @Override
2312            public void onResponse(List<CmsContextMenuEntryBean> menuBeans) {
2313
2314                m_handler.insertContextMenu(menuBeans, structureId);
2315            }
2316        };
2317        menuAction.execute();
2318
2319    }
2320
2321    /**
2322     * Loads the favorite list and adds the elements to the favorite list widget of the tool-bar menu.<p>
2323     *
2324     * @param callback the call-back to execute with the result data
2325     */
2326    public void loadFavorites(final I_CmsSimpleCallback<List<CmsContainerElementData>> callback) {
2327
2328        CmsRpcAction<List<CmsContainerElementData>> action = new CmsRpcAction<List<CmsContainerElementData>>() {
2329
2330            /**
2331             * @see org.opencms.gwt.client.rpc.CmsRpcAction#execute()
2332             */
2333            @Override
2334            public void execute() {
2335
2336                start(200, true);
2337                getContainerpageService().getFavoriteList(
2338                    CmsCoreProvider.get().getStructureId(),
2339                    getData().getDetailId(),
2340                    getPageState(),
2341                    getLocale(),
2342                    this);
2343            }
2344
2345            /**
2346             * @see org.opencms.gwt.client.rpc.CmsRpcAction#onResponse(java.lang.Object)
2347             */
2348            @Override
2349            protected void onResponse(List<CmsContainerElementData> result) {
2350
2351                stop(false);
2352                addElements(result);
2353                callback.execute(result);
2354            }
2355        };
2356        action.execute();
2357    }
2358
2359    /**
2360     * Loads the recent list and adds the elements to the recent list widget of the tool-bar menu.<p>
2361     *
2362     * @param callback the call-back to execute with the result data
2363     */
2364    public void loadRecent(final I_CmsSimpleCallback<List<CmsContainerElementData>> callback) {
2365
2366        CmsRpcAction<List<CmsContainerElementData>> action = new CmsRpcAction<List<CmsContainerElementData>>() {
2367
2368            /**
2369             * @see org.opencms.gwt.client.rpc.CmsRpcAction#execute()
2370             */
2371            @Override
2372            public void execute() {
2373
2374                start(200, true);
2375                getContainerpageService().getRecentList(
2376                    CmsCoreProvider.get().getStructureId(),
2377                    getData().getDetailId(),
2378                    getPageState(),
2379                    getLocale(),
2380                    this);
2381            }
2382
2383            /**
2384             * @see org.opencms.gwt.client.rpc.CmsRpcAction#onResponse(java.lang.Object)
2385             */
2386            @Override
2387            protected void onResponse(List<CmsContainerElementData> result) {
2388
2389                stop(false);
2390                addElements(result);
2391                callback.execute(result);
2392            }
2393        };
2394        action.execute();
2395    }
2396
2397    /**
2398     * Locks the container-page.<p>
2399     *
2400     * @param callback the callback to execute
2401     */
2402    public void lockContainerpage(final I_CmsSimpleCallback<Boolean> callback) {
2403
2404        if (m_lockStatus == LockStatus.locked) {
2405            callback.execute(Boolean.TRUE);
2406        } else if (m_lockStatus == LockStatus.failed) {
2407            callback.execute(Boolean.FALSE);
2408        } else {
2409            I_CmsSimpleCallback<String> call = new I_CmsSimpleCallback<String>() {
2410
2411                public void execute(String lockError) {
2412
2413                    if (lockError == null) {
2414                        onLockSuccess();
2415                        callback.execute(Boolean.TRUE);
2416                    } else {
2417                        onLockFail(lockError);
2418                        callback.execute(Boolean.FALSE);
2419                    }
2420                }
2421            };
2422
2423            if (getData().getDetailContainerPage() != null) {
2424                CmsCoreProvider.get().lockOrReturnError(getData().getDetailContainerPage(), getLoadTime(), call);
2425            } else {
2426                CmsCoreProvider.get().lockOrReturnError(CmsCoreProvider.get().getStructureId(), getLoadTime(), call);
2427            }
2428        }
2429    }
2430
2431    /**
2432     * Returns true if the view with the given view id and the current view have the same root view.<p>
2433     *
2434     * @param viewIdFromElement the id of a view
2435     * @return true if the root view of the id matches the root view of the current view
2436     */
2437    public boolean matchRootView(CmsUUID viewIdFromElement) {
2438
2439        if (viewIdFromElement == null) {
2440            viewIdFromElement = CmsUUID.getNullUUID();
2441        }
2442        CmsElementViewInfo viewFromElement = getView(viewIdFromElement.toString());
2443        return (viewFromElement != null) && viewFromElement.getRootViewId().equals(m_elementView.getRootViewId());
2444    }
2445
2446    /**
2447     * This method should be called when locking the page has failed.<p>
2448     *
2449     * @param lockError the locking information
2450     */
2451    public void onLockFail(String lockError) {
2452
2453        m_lockStatus = LockStatus.failed;
2454        m_handler.onLockFail(lockError);
2455    }
2456
2457    /**
2458     * This method should be called when locking the page has succeeded.<p>
2459     *
2460     */
2461    public void onLockSuccess() {
2462
2463        assert m_lockStatus == LockStatus.unknown;
2464        m_lockStatus = LockStatus.locked;
2465    }
2466
2467    /**
2468     * Handler which is executed when the window closes.<p>
2469     */
2470    public void onWindowClose() {
2471
2472        // causes synchronous RPC call
2473        unlockContainerpage();
2474    }
2475
2476    /**
2477     * Calls the edit handler to prepare the given content element for editing.<p>
2478     *
2479     * @param clientId the element id
2480     * @param editOption the selected edit option
2481     * @param callback the callback to execute
2482     */
2483    public void prepareForEdit(
2484        final String clientId,
2485        final String editOption,
2486        final I_CmsSimpleCallback<CmsUUID> callback) {
2487
2488        CmsRpcAction<CmsUUID> action = new CmsRpcAction<CmsUUID>() {
2489
2490            @Override
2491            public void execute() {
2492
2493                getContainerpageService().prepareForEdit(
2494                    clientId,
2495                    editOption,
2496                    getData().getRpcContext().getPageStructureId(),
2497                    getData().getRequestParams(),
2498                    this);
2499            }
2500
2501            @Override
2502            protected void onResponse(CmsUUID result) {
2503
2504                callback.execute(result);
2505            }
2506        };
2507        action.execute();
2508    }
2509
2510    /**
2511     * Reinitializes the buttons in the container element menus.<p>
2512     */
2513    public void reinitializeButtons() {
2514
2515        if (isGroupcontainerEditing()) {
2516            m_groupEditor.reinitializeButtons();
2517        } else {
2518            List<CmsContainerPageElementPanel> elemWidgets = getAllContainerPageElements(true);
2519
2520            for (CmsContainerPageElementPanel elemWidget : elemWidgets) {
2521                if (requiresOptionBar(elemWidget, elemWidget.getParentTarget())) {
2522                    getContainerpageUtil().addOptionBar(elemWidget);
2523                } else {
2524                    // otherwise remove any present option bar
2525                    elemWidget.setElementOptionBar(null);
2526                }
2527                elemWidget.showEditableListButtons();
2528            }
2529        }
2530    }
2531
2532    /**
2533     * Re-initializes the inline editing.<p>
2534     */
2535    public void reInitInlineEditing() {
2536
2537        removeEditButtonsPositionTimer();
2538        if ((m_targetContainers == null) || getData().isUseClassicEditor()) {
2539            // if the target containers are not initialized yet or classic editor is set, don't do anything
2540            return;
2541        }
2542        if (isGroupcontainerEditing()) {
2543            for (Widget element : m_groupEditor.getGroupContainerWidget()) {
2544                if (((element instanceof CmsContainerPageElementPanel)
2545                    && isInlineEditable(
2546                        (CmsContainerPageElementPanel)element,
2547                        m_groupEditor.getGroupContainerWidget()))) {
2548                    ((CmsContainerPageElementPanel)element).initInlineEditor(this);
2549                }
2550            }
2551        } else {
2552            for (CmsContainerPageContainer container : m_targetContainers.values()) {
2553                // first remove inline editors
2554                for (Widget element : container) {
2555                    if ((element instanceof CmsContainerPageElementPanel)) {
2556                        ((CmsContainerPageElementPanel)element).removeInlineEditor();
2557                    }
2558                }
2559
2560                // add inline editors only on suitable elements
2561                if (isContainerEditable(container) && matchesCurrentEditLevel(container)) {
2562                    for (Widget element : container) {
2563                        if ((element instanceof CmsContainerPageElementPanel)
2564                            && isInlineEditable((CmsContainerPageElementPanel)element, container)) {
2565                            ((CmsContainerPageElementPanel)element).initInlineEditor(this);
2566                        }
2567                    }
2568                }
2569            }
2570        }
2571    }
2572
2573    /**
2574     * Reloads the content for the given elements and related elements.
2575     *
2576     * @param ids the element ids
2577     * @param callback the callback to execute after the reload
2578     */
2579    public void reloadElements(Collection<String> ids, Runnable callback) {
2580
2581        Set<String> related = new HashSet<String>();
2582        for (String id : ids) {
2583            related.addAll(getRelatedElementIds(id));
2584        }
2585        if (!related.isEmpty()) {
2586            ReloadElementAction action = new ReloadElementAction(related, callback);
2587            action.execute();
2588        }
2589    }
2590
2591    /**
2592     * Reloads the content for the given element and all related elements.<p>
2593     *
2594     * Call this if the element content has changed.<p>
2595     *
2596     * @param ids the element ids
2597     * @param callback the callback to execute after the reload
2598     */
2599    public void reloadElements(String[] ids, Runnable callback) {
2600
2601        Set<String> related = new HashSet<String>();
2602        for (int i = 0; i < ids.length; i++) {
2603            related.addAll(getRelatedElementIds(ids[i]));
2604        }
2605        if (!related.isEmpty()) {
2606            ReloadElementAction action = new ReloadElementAction(related, callback);
2607            action.execute();
2608        }
2609    }
2610
2611    /**
2612     * Reloads a container page element with a new set of settings.<p>
2613     *
2614     * @param elementWidget the widget of the container page element which should be reloaded
2615     * @param clientId the id of the container page element which should be reloaded
2616     * @param settings the new set of settings
2617     * @param afterReloadAction a callback which is executed after the element has been reloaded
2618     */
2619    public void reloadElementWithSettings(
2620        final org.opencms.ade.containerpage.client.ui.CmsContainerPageElementPanel elementWidget,
2621        final String clientId,
2622        final Map<String, String> settings,
2623        final I_CmsSimpleCallback<CmsContainerPageElementPanel> afterReloadAction) {
2624
2625        final I_CmsSimpleCallback<CmsContainerElementData> callback = new I_CmsSimpleCallback<CmsContainerElementData>() {
2626
2627            public void execute(CmsContainerElementData newElement) {
2628
2629                try {
2630                    final CmsContainerPageElementPanel replacement = replaceContainerElement(elementWidget, newElement);
2631                    resetEditButtons();
2632                    addToRecentList(newElement.getClientId(), null);
2633                    afterReloadAction.execute(replacement);
2634                } catch (Exception e) {
2635                    // should never happen
2636                    CmsDebugLog.getInstance().printLine(e.getLocalizedMessage());
2637                }
2638            }
2639        };
2640
2641        if (!isGroupcontainerEditing()) {
2642
2643            lockContainerpage(new I_CmsSimpleCallback<Boolean>() {
2644
2645                public void execute(Boolean arg) {
2646
2647                    if (arg.booleanValue()) {
2648                        CmsRpcAction<CmsContainerElementData> action = new CmsRpcAction<CmsContainerElementData>() {
2649
2650                            @Override
2651                            public void execute() {
2652
2653                                start(500, true);
2654                                getContainerpageService().saveElementSettings(
2655                                    getData().getRpcContext(),
2656                                    getData().getDetailId(),
2657                                    getRequestParams(),
2658                                    clientId,
2659                                    settings,
2660                                    getPageState(),
2661                                    getLocale(),
2662                                    this);
2663                            }
2664
2665                            @Override
2666                            protected void onResponse(CmsContainerElementData result) {
2667
2668                                stop(false);
2669                                CmsContainerpageController.get().fireEvent(
2670                                    new CmsContainerpageEvent(EventType.pageSaved));
2671                                setPageChanged(false, false);
2672                                if (result != null) {
2673                                    // cache the loaded element
2674                                    m_elements.put(result.getClientId(), result);
2675                                    setLoadTime(Long.valueOf(result.getLoadTime()));
2676                                }
2677                                callback.execute(result);
2678                            }
2679                        };
2680                        action.execute();
2681                    }
2682                }
2683            });
2684
2685        } else {
2686            getElementWithSettings(clientId, settings, callback);
2687        }
2688    }
2689
2690    /**
2691     * Reloads the page.<p>
2692     */
2693    public void reloadPage() {
2694
2695        Timer timer = new Timer() {
2696
2697            @Override
2698            public void run() {
2699
2700                Window.Location.reload();
2701            }
2702        };
2703
2704        timer.schedule(150);
2705
2706    }
2707
2708    /**
2709     * Removes the given container element from its parent container.<p>
2710     *
2711     * @param dragElement the element to remove
2712     */
2713    public void removeElement(org.opencms.ade.containerpage.client.ui.CmsContainerPageElementPanel dragElement) {
2714
2715        ElementRemoveMode removeMode = isConfirmRemove()
2716        ? ElementRemoveMode.confirmRemove
2717        : ElementRemoveMode.saveAndCheckReferences;
2718        removeElement(dragElement, removeMode);
2719    }
2720
2721    /**
2722     * Removes the given container element from its parent container.<p>
2723     *
2724     * @param dragElement the element to remove
2725     * @param removeMode the remove mode
2726     */
2727    public void removeElement(
2728        org.opencms.ade.containerpage.client.ui.CmsContainerPageElementPanel dragElement,
2729        ElementRemoveMode removeMode) {
2730
2731        if (isGroupcontainerEditing()) {
2732            dragElement.removeFromParent();
2733            if (!getGroupcontainer().iterator().hasNext()) {
2734                // group-container is empty, mark it
2735                getGroupcontainer().addStyleName(I_CmsLayoutBundle.INSTANCE.containerpageCss().emptyGroupContainer());
2736            }
2737            getGroupcontainer().refreshHighlighting();
2738        } else {
2739            final String id = dragElement.getId();
2740            if (id != null) {
2741                addToRecentList(id, null);
2742            }
2743
2744            I_CmsDropContainer container = dragElement.getParentTarget();
2745            switch (removeMode) {
2746                case saveAndCheckReferences:
2747                    dragElement.removeFromParent();
2748                    if (container instanceof CmsContainerPageContainer) {
2749                        ((CmsContainerPageContainer)container).checkEmptyContainers();
2750                    }
2751                    cleanUpContainers();
2752                    Runnable checkReferencesAction = new Runnable() {
2753
2754                        public void run() {
2755
2756                            checkReferencesToRemovedElement(id);
2757                        }
2758                    };
2759                    setPageChanged(checkReferencesAction);
2760                    break;
2761                case confirmRemove:
2762                    handleConfirmRemove(dragElement);
2763                    break;
2764                case silent:
2765                default:
2766                    dragElement.removeFromParent();
2767                    if (container instanceof CmsContainerPageContainer) {
2768                        ((CmsContainerPageContainer)container).checkEmptyContainers();
2769                    }
2770                    cleanUpContainers();
2771                    setPageChanged();
2772                    break;
2773            }
2774        }
2775    }
2776
2777    /**
2778     * Replaces the given drag-element with the given container element.<p>
2779     *
2780     * @param containerElement the container element to replace
2781     * @param elementData the new element data
2782     *
2783     * @return the container element which replaced the old one
2784     *
2785     * @throws Exception if something goes wrong
2786     */
2787    public CmsContainerPageElementPanel replaceContainerElement(
2788        CmsContainerPageElementPanel containerElement,
2789        CmsContainerElementData elementData)
2790    throws Exception {
2791
2792        I_CmsDropContainer parentContainer = containerElement.getParentTarget();
2793        String containerId = parentContainer.getContainerId();
2794        CmsContainerPageElementPanel replacer = null;
2795        String elementContent = elementData.getContents().get(containerId);
2796        if ((elementContent != null) && (elementContent.trim().length() > 0)) {
2797            replacer = getContainerpageUtil().createElement(elementData, parentContainer, false);
2798
2799            if (containerElement.isNew()) {
2800                // if replacing element data has the same structure id, keep the 'new' state by setting the new type property
2801                // this should only be the case when editing settings of a new element that has not been created in the VFS yet
2802                String id = getServerId(containerElement.getId());
2803                if (elementData.getClientId().startsWith(id)) {
2804                    replacer.setNewType(containerElement.getNewType());
2805                }
2806            }
2807            replacer.setCreateNew(containerElement.isCreateNew());
2808            // replacer.setModelGroup(containerElement.isModelGroup());
2809            if (isGroupcontainerEditing() && (containerElement.getInheritanceInfo() != null)) {
2810                // in case of inheritance container editing, keep the inheritance info
2811                replacer.setInheritanceInfo(containerElement.getInheritanceInfo());
2812                // set the proper element options
2813                CmsInheritanceContainerEditor.getInstance().setOptionBar(replacer);
2814            }
2815            parentContainer.insert(replacer, parentContainer.getWidgetIndex(containerElement));
2816            containerElement.removeFromParent();
2817            initializeSubContainers(replacer);
2818        }
2819        cleanUpContainers();
2820        return replacer;
2821    }
2822
2823    /**
2824     * Replaces the given element with another content while keeping it's settings.<p>
2825     *
2826     * @param elementWidget the element to replace
2827     * @param elementId the id of the replacing content
2828     * @param callback  the callback to execute after the element is replaced
2829     */
2830    public void replaceElement(
2831        final CmsContainerPageElementPanel elementWidget,
2832        final String elementId,
2833        Runnable callback) {
2834
2835        final CmsRpcAction<CmsContainerElementData> action = new CmsRpcAction<CmsContainerElementData>() {
2836
2837            @Override
2838            public void execute() {
2839
2840                start(500, true);
2841                getContainerpageService().replaceElement(
2842                    getData().getRpcContext(),
2843                    getData().getDetailId(),
2844                    getRequestParams(),
2845                    elementWidget.getId(),
2846                    elementId,
2847                    getPageState(),
2848                    getLocale(),
2849                    this);
2850            }
2851
2852            @Override
2853            protected void onResponse(CmsContainerElementData result) {
2854
2855                stop(false);
2856
2857                if (result != null) {
2858                    // cache the loaded element
2859                    m_elements.put(result.getClientId(), result);
2860                    try {
2861                        replaceContainerElement(elementWidget, result);
2862                        resetEditButtons();
2863                        addToRecentList(result.getClientId(), null);
2864                        setPageChanged(new Runnable() {
2865
2866                            public void run() {
2867
2868                                if (callback != null) {
2869                                    callback.run();
2870                                }
2871                            }
2872                        });
2873                    } catch (Exception e) {
2874                        // should never happen
2875                        CmsDebugLog.getInstance().printLine(e.getLocalizedMessage());
2876                    }
2877                }
2878            }
2879        };
2880
2881        if (!isGroupcontainerEditing()) {
2882
2883            lockContainerpage(new I_CmsSimpleCallback<Boolean>() {
2884
2885                public void execute(Boolean arg) {
2886
2887                    if (arg.booleanValue()) {
2888                        action.execute();
2889                    }
2890                }
2891            });
2892
2893        } else {
2894            action.execute();
2895        }
2896    }
2897
2898    /**
2899     * Checks whether the given element should display the option bar.<p>
2900     *
2901     * @param element the element
2902     * @param dragParent the element parent
2903     *
2904     * @return <code>true</code> if the given element should display the option bar
2905     */
2906    public boolean requiresOptionBar(CmsContainerPageElementPanel element, I_CmsDropContainer dragParent) {
2907
2908        return element.hasViewPermission()
2909            && (!element.hasModelGroupParent() || getData().isModelGroup())
2910            && (matchRootView(element.getElementView())
2911                || isGroupcontainerEditing()
2912                || shouldShowModelgroupOptionBar(element))
2913            && isContainerEditable(dragParent)
2914            && matchesCurrentEditLevel(dragParent);
2915    }
2916
2917    /**
2918     * Resets all edit buttons an there positions.<p>
2919     */
2920    public void resetEditButtons() {
2921
2922        removeEditButtonsPositionTimer();
2923        m_editButtonsPositionTimer = new Timer() {
2924
2925            /** Timer run counter. */
2926            private int m_timerRuns;
2927
2928            @Override
2929            public void run() {
2930
2931                for (org.opencms.ade.containerpage.client.ui.CmsContainerPageContainer container : m_targetContainers.values()) {
2932                    container.showEditableListButtons();
2933                    container.updateOptionBars();
2934                }
2935                if (m_timerRuns > 3) {
2936                    cancel();
2937                }
2938                m_timerRuns++;
2939            }
2940        };
2941        m_editButtonsPositionTimer.scheduleRepeating(100);
2942    }
2943
2944    /**
2945     * Resets the container-page.<p>
2946     */
2947    public void resetPage() {
2948
2949        setPageChanged(false, true);
2950        Window.Location.reload();
2951    }
2952
2953    /**
2954     * Method to save and leave the page.<p>
2955     *
2956     * @param leaveCommand the command to execute to leave the page
2957     */
2958    public void saveAndLeave(final Command leaveCommand) {
2959
2960        if (hasPageChanged()) {
2961            CmsRpcAction<Long> action = new CmsRpcAction<Long>() {
2962
2963                /**
2964                 * @see org.opencms.gwt.client.rpc.CmsRpcAction#execute()
2965                 */
2966                @Override
2967                public void execute() {
2968
2969                    if (getData().getDetailContainerPage() != null) {
2970                        getContainerpageService().saveDetailContainers(
2971                            getData().getDetailId(),
2972                            getData().getDetailContainerPage(),
2973                            getPageContent(),
2974                            this);
2975                    } else {
2976                        getContainerpageService().saveContainerpage(
2977                            CmsCoreProvider.get().getStructureId(),
2978                            getPageContent(),
2979                            this);
2980                    }
2981                }
2982
2983                /**
2984                 * @see org.opencms.gwt.client.rpc.CmsRpcAction#onResponse(java.lang.Object)
2985                 */
2986                @Override
2987                protected void onResponse(Long result) {
2988
2989                    setLoadTime(result);
2990                    CmsNotification.get().send(Type.NORMAL, Messages.get().key(Messages.GUI_NOTIFICATION_PAGE_SAVED_0));
2991                    CmsContainerpageController.get().fireEvent(new CmsContainerpageEvent(EventType.pageSaved));
2992                    setPageChanged(false, true);
2993                    leaveCommand.execute();
2994                }
2995            };
2996            action.execute();
2997        }
2998    }
2999
3000    /**
3001     * Method to save and leave the page.<p>
3002     *
3003     * @param targetUri the new URI to call
3004     */
3005    public void saveAndLeave(final String targetUri) {
3006
3007        if (hasPageChanged()) {
3008            CmsRpcAction<Long> action = new CmsRpcAction<Long>() {
3009
3010                /**
3011                 * @see org.opencms.gwt.client.rpc.CmsRpcAction#execute()
3012                 */
3013                @Override
3014                public void execute() {
3015
3016                    if (getData().getDetailContainerPage() != null) {
3017                        getContainerpageService().saveDetailContainers(
3018                            getData().getDetailId(),
3019                            getData().getDetailContainerPage(),
3020                            getPageContent(),
3021                            this);
3022                    } else {
3023                        getContainerpageService().saveContainerpage(
3024                            CmsCoreProvider.get().getStructureId(),
3025                            getPageContent(),
3026                            this);
3027                    }
3028                }
3029
3030                /**
3031                 * @see org.opencms.gwt.client.rpc.CmsRpcAction#onResponse(java.lang.Object)
3032                 */
3033                @Override
3034                protected void onResponse(Long result) {
3035
3036                    setLoadTime(result);
3037                    CmsNotification.get().send(Type.NORMAL, Messages.get().key(Messages.GUI_NOTIFICATION_PAGE_SAVED_0));
3038                    CmsContainerpageController.get().fireEvent(new CmsContainerpageEvent(EventType.pageSaved));
3039                    setPageChanged(false, true);
3040                    Window.Location.assign(targetUri);
3041                }
3042            };
3043            action.execute();
3044        }
3045    }
3046
3047    /**
3048     * Saves the clipboard tab  index selected by the user.<p>
3049     *
3050     * @param tabIndex the tab index
3051     */
3052    public void saveClipboardTab(final int tabIndex) {
3053
3054        CmsRpcAction<Void> action = new CmsRpcAction<Void>() {
3055
3056            @Override
3057            public void execute() {
3058
3059                start(1, false);
3060                getContainerpageService().saveClipboardTab(tabIndex, this);
3061            }
3062
3063            @Override
3064            protected void onResponse(Void result) {
3065
3066                stop(false);
3067            }
3068        };
3069        action.execute();
3070    }
3071
3072    /**
3073     * Saves the current state of the container-page.<p>
3074     *
3075     * @param afterSaveActions the actions to execute after saving
3076     */
3077    public void saveContainerpage(final Runnable... afterSaveActions) {
3078
3079        if (hasPageChanged()) {
3080            final CmsRpcAction<Long> action = new CmsRpcAction<Long>() {
3081
3082                /**
3083                 * @see org.opencms.gwt.client.rpc.CmsRpcAction#execute()
3084                 */
3085                @Override
3086                public void execute() {
3087
3088                    start(500, true);
3089                    if (getData().getDetailContainerPage() != null) {
3090                        getContainerpageService().saveDetailContainers(
3091                            getData().getDetailId(),
3092                            getData().getDetailContainerPage(),
3093                            getPageContent(),
3094                            this);
3095                    } else {
3096                        getContainerpageService().saveContainerpage(
3097                            CmsCoreProvider.get().getStructureId(),
3098                            getPageContent(),
3099                            this);
3100                    }
3101                }
3102
3103                /**
3104                 * @see org.opencms.gwt.client.rpc.CmsRpcAction#onResponse(java.lang.Object)
3105                 */
3106                @Override
3107                protected void onResponse(Long result) {
3108
3109                    setLoadTime(result);
3110                    stop(false);
3111                    setPageChanged(false, false);
3112                    CmsContainerpageController.get().fireEvent(new CmsContainerpageEvent(EventType.pageSaved));
3113                    for (Runnable afterSaveAction : afterSaveActions) {
3114                        afterSaveAction.run();
3115                    }
3116                }
3117            };
3118            if (getData().getDetailContainerPage() != null) {
3119                action.execute();
3120            } else {
3121                lockContainerpage(new I_CmsSimpleCallback<Boolean>() {
3122
3123                    public void execute(Boolean arg) {
3124
3125                        if (arg.booleanValue()) {
3126                            action.execute();
3127                        }
3128                    }
3129                });
3130            }
3131        }
3132    }
3133
3134    /**
3135     * Saves the favorite list.<p>
3136     *
3137     * @param clientIds the client id's of the list's elements
3138     */
3139    public void saveFavoriteList(final List<String> clientIds) {
3140
3141        CmsRpcAction<Void> action = new CmsRpcAction<Void>() {
3142
3143            /**
3144             * @see org.opencms.gwt.client.rpc.CmsRpcAction#execute()
3145             */
3146            @Override
3147            public void execute() {
3148
3149                getContainerpageService().saveFavoriteList(clientIds, CmsCoreProvider.get().getUri(), this);
3150            }
3151
3152            /**
3153             * @see org.opencms.gwt.client.rpc.CmsRpcAction#onResponse(java.lang.Object)
3154             */
3155            @Override
3156            protected void onResponse(Void result) {
3157
3158                CmsNotification.get().send(
3159                    Type.NORMAL,
3160                    Messages.get().key(Messages.GUI_NOTIFICATION_FAVORITES_SAVED_0));
3161            }
3162        };
3163        action.execute();
3164    }
3165
3166    /**
3167     * Saves the group-container.<p>
3168     *
3169     * @param groupContainer the group-container data to save
3170     * @param groupContainerElement the group-container widget
3171     */
3172    public void saveGroupcontainer(
3173        final CmsGroupContainer groupContainer,
3174        final CmsGroupContainerElementPanel groupContainerElement) {
3175
3176        if (getGroupcontainer() != null) {
3177            CmsRpcAction<CmsGroupContainerSaveResult> action = new CmsRpcAction<CmsGroupContainerSaveResult>() {
3178
3179                /**
3180                 * @see org.opencms.gwt.client.rpc.CmsRpcAction#execute()
3181                 */
3182                @Override
3183                public void execute() {
3184
3185                    start(0, true);
3186                    getContainerpageService().saveGroupContainer(
3187                        getData().getRpcContext(),
3188                        getData().getDetailId(),
3189                        getRequestParams(),
3190                        groupContainer,
3191                        getPageState(),
3192                        getLocale(),
3193                        this);
3194                }
3195
3196                /**
3197                 * @see org.opencms.gwt.client.rpc.CmsRpcAction#onResponse(java.lang.Object)
3198                 */
3199                @Override
3200                protected void onResponse(CmsGroupContainerSaveResult saveResult) {
3201
3202                    stop(false);
3203                    Map<String, CmsContainerElementData> elementData = saveResult.getElementData();
3204                    m_elements.putAll(elementData);
3205                    try {
3206                        replaceContainerElement(groupContainerElement, elementData.get(groupContainerElement.getId()));
3207                    } catch (Exception e) {
3208                        CmsDebugLog.getInstance().printLine("Error replacing group container element");
3209                    }
3210                    addToRecentList(groupContainerElement.getId(), null);
3211                    CmsNotification.get().send(
3212                        Type.NORMAL,
3213                        Messages.get().key(Messages.GUI_NOTIFICATION_GROUP_CONTAINER_SAVED_0));
3214                    List<CmsRemovedElementStatus> removedElements = saveResult.getRemovedElements();
3215                    for (CmsRemovedElementStatus removedElement : removedElements) {
3216                        askWhetherRemovedElementShouldBeDeleted(removedElement);
3217                    }
3218
3219                }
3220            };
3221            action.execute();
3222
3223        }
3224    }
3225
3226    /**
3227     * Saves the inheritance container.<p>
3228     *
3229     * @param inheritanceContainer the inheritance container data to save
3230     * @param groupContainerElement the group container widget
3231     */
3232    public void saveInheritContainer(
3233        final CmsInheritanceContainer inheritanceContainer,
3234        final CmsGroupContainerElementPanel groupContainerElement) {
3235
3236        if (getGroupcontainer() != null) {
3237            CmsRpcAction<Map<String, CmsContainerElementData>> action = new CmsRpcAction<Map<String, CmsContainerElementData>>() {
3238
3239                /**
3240                 * @see org.opencms.gwt.client.rpc.CmsRpcAction#execute()
3241                 */
3242                @Override
3243                public void execute() {
3244
3245                    start(0, true);
3246                    getContainerpageService().saveInheritanceContainer(
3247                        CmsCoreProvider.get().getStructureId(),
3248                        getData().getDetailId(),
3249                        inheritanceContainer,
3250                        getPageState(),
3251                        getLocale(),
3252                        this);
3253                }
3254
3255                /**
3256                 * @see org.opencms.gwt.client.rpc.CmsRpcAction#onResponse(java.lang.Object)
3257                 */
3258                @Override
3259                protected void onResponse(Map<String, CmsContainerElementData> result) {
3260
3261                    stop(false);
3262                    m_elements.putAll(result);
3263                    try {
3264                        replaceContainerElement(groupContainerElement, result.get(groupContainerElement.getId()));
3265                    } catch (Exception e) {
3266                        CmsDebugLog.getInstance().printLine("Error replacing group container element");
3267                    }
3268                    addToRecentList(groupContainerElement.getId(), null);
3269                    CmsNotification.get().send(
3270                        Type.NORMAL,
3271                        Messages.get().key(Messages.GUI_NOTIFICATION_INHERITANCE_CONTAINER_SAVED_0));
3272
3273                }
3274            };
3275            action.execute();
3276
3277        }
3278    }
3279
3280    /**
3281     * Sets the flag indicating that a content element is being edited.<p>
3282     *
3283     * @param isContentEditing the flag indicating that a content element is being edited
3284     */
3285    public void setContentEditing(boolean isContentEditing) {
3286
3287        if (m_groupEditor != null) {
3288            if (isContentEditing) {
3289                m_groupEditor.hidePopup();
3290            } else {
3291                m_groupEditor.showPopup();
3292            }
3293        }
3294        m_isContentEditing = isContentEditing;
3295    }
3296
3297    /**
3298     * Sets the DND controller.<p>
3299     *
3300     * @param dnd the new DND controller
3301     */
3302    public void setDndController(CmsCompositeDNDController dnd) {
3303
3304        m_dndController = dnd;
3305    }
3306
3307    /**
3308     * Sets the element view.<p>
3309     *
3310     * @param viewInfo the element view
3311     * @param nextAction the action to execute after setting the view
3312     */
3313    public void setElementView(CmsElementViewInfo viewInfo, Runnable nextAction) {
3314
3315        if (viewInfo != null) {
3316            m_elementView = viewInfo;
3317
3318            CmsRpcAction<Void> action = new CmsRpcAction<Void>() {
3319
3320                @SuppressWarnings("synthetic-access")
3321                @Override
3322                public void execute() {
3323
3324                    getContainerpageService().setElementView(m_elementView.getElementViewId(), this);
3325                }
3326
3327                @Override
3328                protected void onResponse(Void result) {
3329
3330                    // nothing to do
3331                }
3332            };
3333            action.execute();
3334
3335            m_currentEditLevel = -1;
3336            reinitializeButtons();
3337            updateButtonsForCurrentView();
3338            reInitInlineEditing();
3339            updateGalleryData(true, nextAction);
3340        }
3341    }
3342
3343    /**
3344     * Sets the model group base element id.<p>
3345     *
3346     * @param modelGroupElementId the model group base element id
3347     */
3348    public void setModelGroupElementId(String modelGroupElementId) {
3349
3350        m_modelGroupElementId = modelGroupElementId;
3351    }
3352
3353    /**
3354     * Marks the page as changed.<p>
3355     *
3356     * @param nextActions the actions to perform after the page has been marked as changed
3357     */
3358    public void setPageChanged(Runnable... nextActions) {
3359
3360        if (!isGroupcontainerEditing()) {
3361            // the container page will be saved immediately
3362            m_pageChanged = true;
3363            saveContainerpage(nextActions);
3364        }
3365    }
3366
3367    /**
3368     * Method to determine whether a container element should be shown in the current template context.<p>
3369     *
3370     * @param elementData the element data
3371     *
3372     * @return true if the element should be shown
3373     */
3374    public boolean shouldShowInContext(CmsContainerElementData elementData) {
3375
3376        CmsTemplateContextInfo contextInfo = getData().getTemplateContextInfo();
3377        if (contextInfo.getCurrentContext() == null) {
3378            return true;
3379        }
3380        CmsDefaultSet<String> allowedContexts = contextInfo.getAllowedContexts().get(elementData.getResourceType());
3381        if ((allowedContexts != null) && !allowedContexts.contains(contextInfo.getCurrentContext())) {
3382            return false;
3383        }
3384
3385        String settingValue = elementData.getSettings().get(CmsTemplateContextInfo.SETTING);
3386        return (settingValue == null) || settingValue.contains(contextInfo.getCurrentContext());
3387    }
3388
3389    /**
3390     * Tells the controller that group-container editing has started.<p>
3391     *
3392     * @param groupContainer the group container
3393     * @param isElementGroup <code>true</code> if the group container is an element group and not an inheritance group
3394     */
3395    public void startEditingGroupcontainer(
3396        final CmsGroupContainerElementPanel groupContainer,
3397        final boolean isElementGroup) {
3398
3399        removeEditButtonsPositionTimer();
3400        I_CmsSimpleCallback<Boolean> callback = new I_CmsSimpleCallback<Boolean>() {
3401
3402            public void execute(Boolean arg) {
3403
3404                if (arg.booleanValue()) {
3405                    if (isElementGroup) {
3406                        m_groupEditor = CmsGroupContainerEditor.openGroupcontainerEditor(
3407                            groupContainer,
3408                            CmsContainerpageController.this,
3409                            m_handler);
3410                    } else {
3411                        m_groupEditor = CmsInheritanceContainerEditor.openInheritanceContainerEditor(
3412                            groupContainer,
3413                            CmsContainerpageController.this,
3414                            m_handler);
3415                    }
3416                } else {
3417                    CmsNotification.get().send(
3418                        Type.WARNING,
3419                        Messages.get().key(Messages.GUI_NOTIFICATION_UNABLE_TO_LOCK_0));
3420                }
3421            }
3422        };
3423        if ((m_groupEditor == null) && (groupContainer.isNew())) {
3424            callback.execute(Boolean.TRUE);
3425        } else {
3426            lockContainerpage(callback);
3427        }
3428
3429    }
3430
3431    /**
3432     * Starts the publish lock check.
3433     */
3434    public void startPublishLockCheck() {
3435
3436        Set<CmsUUID> elementIds = new HashSet<>();
3437        processPageContent(new I_PageContentVisitor() {
3438
3439            public boolean beginContainer(String name, CmsContainer container) {
3440
3441                return true;
3442            }
3443
3444            public void endContainer() {
3445
3446                // do nothing
3447            }
3448
3449            public void handleElement(CmsContainerPageElementPanel element) {
3450
3451                if (element.hasWritePermission() && element.getLockInfo().isPublishLock()) {
3452                    CmsUUID structureId = element.getStructureId();
3453                    if (structureId != null) {
3454                        elementIds.add(structureId);
3455                    }
3456                }
3457            }
3458        });
3459
3460        m_publishLockChecker.addIdsToCheck(elementIds);
3461    }
3462
3463    /**
3464     * Tells the controller that group-container editing has stopped.<p>
3465     */
3466    public void stopEditingGroupcontainer() {
3467
3468        m_groupEditor = null;
3469    }
3470
3471    /**
3472     * Unlocks the given resource.<p>
3473     *
3474     * @param structureId the structure id of the resource to unlock
3475     *
3476     * @return <code>true</code> if the resource was unlocked successfully
3477     */
3478    public boolean unlockResource(CmsUUID structureId) {
3479
3480        return CmsCoreProvider.get().unlock(structureId);
3481    }
3482
3483    /**
3484     * Updates he
3485     */
3486    public void updateButtonsForCurrentView() {
3487
3488        String nonDefaultViewClass = I_CmsLayoutBundle.INSTANCE.containerpageCss().nonDefaultView();
3489        CmsUUID viewId = getElementView().getRootViewId();
3490        if (viewId.isNullUUID()) {
3491            RootPanel.get().removeStyleName(nonDefaultViewClass);
3492        } else {
3493            RootPanel.get().addStyleName(nonDefaultViewClass);
3494        }
3495    }
3496
3497    /**
3498     * Updates the formatter in the server-side element bean.
3499     *
3500     * @param clientId the client element bean
3501     * @param containerId the container id
3502     * @param settings the settings
3503     */
3504    public void updateServerElementFormatter(String clientId, String containerId, Map<String, String> settings) {
3505
3506        CmsRpcAction<Void> action = new CmsRpcAction<Void>() {
3507
3508            @Override
3509            public void execute() {
3510
3511                start(0, false);
3512                getContainerpageService().updateServerElementFormatter(clientId, containerId, settings, this);
3513
3514            }
3515
3516            @Override
3517            protected void onResponse(Void result) {
3518
3519                stop(false);
3520
3521            }
3522
3523        };
3524        action.execute();
3525    }
3526
3527    /**
3528     * Adds the given element data to the element cache.<p>
3529     *
3530     * @param elements the element data
3531     */
3532    protected void addElements(List<CmsContainerElementData> elements) {
3533
3534        for (CmsContainerElementData element : elements) {
3535            m_elements.put(element.getClientId(), element);
3536        }
3537    }
3538
3539    /**
3540     * Adds the given element data to the element cache.<p>
3541     *
3542     * @param elements the element data
3543     */
3544    protected void addElements(Map<String, CmsContainerElementData> elements) {
3545
3546        for (CmsContainerElementData element : elements.values()) {
3547            m_elements.put(element.getClientId(), element);
3548        }
3549    }
3550
3551    /**
3552     * Asks the user whether an element which has been removed should be deleted.<p>
3553     *
3554     * @param status the status of the removed element
3555     */
3556    protected void askWhetherRemovedElementShouldBeDeleted(final CmsRemovedElementStatus status) {
3557
3558        CmsRemovedElementDeletionDialog dialog = new CmsRemovedElementDeletionDialog(status);
3559        dialog.center();
3560    }
3561
3562    /**
3563     * Checks that a removed can be possibly deleted and if so, asks the user if it should be deleted.<p>
3564     *
3565     * @param id the client id of the element
3566     */
3567    protected void checkReferencesToRemovedElement(final String id) {
3568
3569        if (id != null) {
3570            //NOTE: We only use an RPC call here to check for references on the server side. If, at a later point, we decide
3571            //to add a save button again, this will have to be changed, because then we have to consider client-side state.
3572            CmsRpcAction<CmsRemovedElementStatus> getStatusAction = new CmsRpcAction<CmsRemovedElementStatus>() {
3573
3574                @Override
3575                public void execute() {
3576
3577                    start(200, true);
3578                    getContainerpageService().getRemovedElementStatus(id, null, this);
3579                }
3580
3581                @Override
3582                public void onResponse(final CmsRemovedElementStatus status) {
3583
3584                    stop(false);
3585                    if (status.isDeletionCandidate()) {
3586                        askWhetherRemovedElementShouldBeDeleted(status);
3587
3588                    }
3589                }
3590
3591            };
3592            getStatusAction.execute();
3593
3594        }
3595    }
3596
3597    /**
3598     * Disables option and toolbar buttons.<p>
3599     */
3600    protected void deactivateOnClosing() {
3601
3602        removeEditButtonsPositionTimer();
3603        m_handler.deactivateCurrentButton();
3604        m_handler.disableToolbarButtons();
3605    }
3606
3607    /**
3608     * Helper method to get all current container page elements.<p>
3609     *
3610     * @param includeGroupContents true if the contents of group containers should also be included
3611     *
3612     * @return the list of current container page elements
3613     */
3614    protected List<CmsContainerPageElementPanel> getAllContainerPageElements(boolean includeGroupContents) {
3615
3616        List<CmsContainerPageElementPanel> elemWidgets = new ArrayList<CmsContainerPageElementPanel>();
3617        for (Entry<String, org.opencms.ade.containerpage.client.ui.CmsContainerPageContainer> entry : CmsContainerpageController.get().getContainerTargets().entrySet()) {
3618            Iterator<Widget> elIt = entry.getValue().iterator();
3619            while (elIt.hasNext()) {
3620                try {
3621                    org.opencms.ade.containerpage.client.ui.CmsContainerPageElementPanel elementWidget = (org.opencms.ade.containerpage.client.ui.CmsContainerPageElementPanel)elIt.next();
3622                    elemWidgets.add(elementWidget);
3623                    if (includeGroupContents && (elementWidget instanceof CmsGroupContainerElementPanel)) {
3624                        List<CmsContainerPageElementPanel> groupChildren = ((CmsGroupContainerElementPanel)elementWidget).getGroupChildren();
3625                        elemWidgets.addAll(groupChildren);
3626                    }
3627                } catch (ClassCastException e) {
3628                    // no proper container element, skip it (this should never happen!)
3629                    CmsDebugLog.getInstance().printLine(
3630                        "WARNING: there is an inappropriate element within a container");
3631                }
3632            }
3633        }
3634        return elemWidgets;
3635    }
3636
3637    /**
3638     * Returns the core RPC service.<p>
3639     *
3640     * @return the core service
3641     */
3642    protected I_CmsCoreServiceAsync getCoreService() {
3643
3644        if (m_coreSvc == null) {
3645            m_coreSvc = CmsCoreProvider.getService();
3646        }
3647        return m_coreSvc;
3648    }
3649
3650    /**
3651     * Returns the currently active group editor.<p>
3652     *
3653     * @return the currently active group editor
3654     */
3655    protected A_CmsGroupEditor getGroupEditor() {
3656
3657        return m_groupEditor;
3658    }
3659
3660    /**
3661     * Returns the content locale.<p>
3662     *
3663     * @return the content locale
3664     */
3665    protected String getLocale() {
3666
3667        return m_data.getLocale();
3668    }
3669
3670    /**
3671     * Gets the page content for purposes of saving.<p>
3672     *
3673     * @return the page content
3674     */
3675    protected List<CmsContainer> getPageContent() {
3676
3677        SaveDataVisitor visitor = new SaveDataVisitor();
3678        processPageContent(visitor);
3679        return visitor.getContainers();
3680
3681    }
3682
3683    /**
3684     * Returns the containers of the page in their current state.<p>
3685     *
3686     * @return the containers of the page
3687     */
3688    protected List<CmsContainer> getPageState() {
3689
3690        PageStateVisitor visitor = new PageStateVisitor();
3691        processPageContent(visitor);
3692        return visitor.getContainers();
3693    }
3694
3695    /**
3696     * Returns the request parameters of the displayed container-page.<p>
3697     *
3698     * @return the request parameters
3699     */
3700    protected String getRequestParams() {
3701
3702        return m_data.getRequestParams();
3703    }
3704
3705    /**
3706     * Checks if any of the containers are nested containers.<p>
3707     *
3708     * @return true if there are nested containers
3709     */
3710    protected boolean hasNestedContainers() {
3711
3712        boolean hasNestedContainers = false;
3713        for (CmsContainer container : m_containers.values()) {
3714            if (container.getParentContainerName() != null) {
3715                hasNestedContainers = true;
3716                break;
3717            }
3718        }
3719        return hasNestedContainers;
3720    }
3721
3722    /**
3723     * Returns whether the given container is considered a root container.<p>
3724     *
3725     * @param container the container to check
3726     *
3727     * @return <code>true</code> if the given container is a root container
3728     */
3729    protected boolean isRootContainer(CmsContainer container) {
3730
3731        boolean isRoot = false;
3732        if (!container.isSubContainer()) {
3733            isRoot = true;
3734        } else if (container.isDetailOnly()) {
3735            CmsContainer parent = getContainer(container.getParentContainerName());
3736            isRoot = (parent != null) && !parent.isDetailOnly();
3737        }
3738        return isRoot;
3739    }
3740
3741    /**
3742     * Opens the editor for the newly created element.<p>
3743     *
3744     * @param element the container element
3745     * @param newElementData the new element data
3746     * @param inline <code>true</code> to open the inline editor for the given element if available
3747     */
3748    protected void openEditorForNewElement(
3749        org.opencms.ade.containerpage.client.ui.CmsContainerPageElementPanel element,
3750        CmsContainerElement newElementData,
3751        boolean inline) {
3752
3753        String oldId = element.getNewType();
3754        element.setNewType(null);
3755        if (inline) {
3756            String newId = getServerId(newElementData.getClientId());
3757            CmsContentEditor.replaceResourceIds(element.getElement(), oldId, newId);
3758        }
3759        element.setId(newElementData.getClientId());
3760        element.setSitePath(newElementData.getSitePath());
3761        if (!isGroupcontainerEditing()) {
3762            setPageChanged();
3763        }
3764        getHandler().hidePageOverlay();
3765        getHandler().openEditorForElement(element, inline, true);
3766    }
3767
3768    /**
3769    * Previews events. Shows the leaving page dialog, if the page has changed and an anchor has been clicked.<p>
3770    * Also triggers an element view change on 'Ctrl+E'.<p>
3771    *
3772    * @param event the native event
3773    */
3774    protected void previewNativeEvent(NativePreviewEvent event) {
3775
3776        Event nativeEvent = Event.as(event.getNativeEvent());
3777
3778        if ((nativeEvent.getTypeInt() == Event.ONCLICK) && hasPageChanged()) {
3779            EventTarget target = nativeEvent.getEventTarget();
3780            if (!Element.is(target)) {
3781                return;
3782            }
3783            Element element = Element.as(target);
3784            element = CmsDomUtil.getAncestor(element, CmsDomUtil.Tag.a);
3785            if (element == null) {
3786                return;
3787            }
3788            AnchorElement anc = AnchorElement.as(element);
3789            final String uri = anc.getHref();
3790
3791            // avoid to abort events for date-picker widgets
3792            if (CmsStringUtil.isEmptyOrWhitespaceOnly(uri)
3793                || (CmsDomUtil.getAncestor(element, "x-date-picker") != null)) {
3794                return;
3795            }
3796            nativeEvent.preventDefault();
3797            nativeEvent.stopPropagation();
3798            m_handler.leavePage(uri);
3799        }
3800        if (event.getTypeInt() == Event.ONKEYDOWN) {
3801            int keyCode = nativeEvent.getKeyCode();
3802            if ((keyCode == KeyCodes.KEY_F5) && hasPageChanged()) {
3803                // user pressed F5
3804                nativeEvent.preventDefault();
3805                nativeEvent.stopPropagation();
3806                m_handler.leavePage(Window.Location.getHref());
3807            }
3808            if (nativeEvent.getCtrlKey() || nativeEvent.getMetaKey()) {
3809                // look for short cuts
3810                if (keyCode == KeyCodes.KEY_E) {
3811                    if (nativeEvent.getShiftKey()) {
3812                        circleContainerEditLayers();
3813                    } else {
3814                        openNextElementView();
3815                    }
3816                    nativeEvent.preventDefault();
3817                    nativeEvent.stopPropagation();
3818                }
3819            }
3820        }
3821    }
3822
3823    /**
3824     * Iterates over all the container contents and calls a visitor object with the visited containers/elements as parameters.
3825     *
3826     * @param visitor the visitor which the container elements should be passed to
3827     */
3828    protected void processPageContent(I_PageContentVisitor visitor) {
3829
3830        for (Entry<String, org.opencms.ade.containerpage.client.ui.CmsContainerPageContainer> entry : m_targetContainers.entrySet()) {
3831
3832            CmsContainer cnt = m_containers.get(entry.getKey());
3833            if (visitor.beginContainer(entry.getKey(), cnt)) {
3834                Iterator<Widget> elIt = entry.getValue().iterator();
3835                while (elIt.hasNext()) {
3836                    try {
3837                        CmsContainerPageElementPanel elementWidget = (CmsContainerPageElementPanel)elIt.next();
3838                        visitor.handleElement(elementWidget);
3839                    } catch (ClassCastException e) {
3840                        // no proper container element, skip it (this should never happen!)
3841                        CmsDebugLog.getInstance().printLine(
3842                            "WARNING: there is an inappropriate element within a container");
3843                    }
3844                }
3845                visitor.endContainer();
3846            }
3847        }
3848    }
3849
3850    /**
3851     * Removes all container elements with the given id from all containers and the client side cache.<p>
3852     *
3853     * @param resourceId the resource id
3854     */
3855    protected void removeContainerElements(String resourceId) {
3856
3857        boolean changed = false;
3858        Iterator<org.opencms.ade.containerpage.client.ui.CmsContainerPageElementPanel> it = getAllDragElements().iterator();
3859        while (it.hasNext()) {
3860            org.opencms.ade.containerpage.client.ui.CmsContainerPageElementPanel containerElement = it.next();
3861            if (resourceId.startsWith(containerElement.getId())) {
3862                containerElement.removeFromParent();
3863                changed = true;
3864            }
3865        }
3866        for (String elementId : m_elements.keySet()) {
3867            if (elementId.startsWith(resourceId)) {
3868                m_elements.remove(elementId);
3869            }
3870        }
3871        if (changed) {
3872            setPageChanged();
3873        }
3874    }
3875
3876    /**
3877     * Schedules an update of the gallery data according to the current element view and the editable containers.<p>
3878     */
3879    protected void scheduleGalleryUpdate() {
3880
3881        // only if not already scheduled
3882        if (m_galleryUpdateTimer == null) {
3883            m_galleryUpdateTimer = new Timer() {
3884
3885                @Override
3886                public void run() {
3887
3888                    m_galleryUpdateTimer = null;
3889                    updateGalleryData(false, null);
3890                }
3891            };
3892            m_galleryUpdateTimer.schedule(50);
3893        }
3894    }
3895
3896    /**
3897     * Sets the page changed flag and initializes the window closing handler if necessary.<p>
3898     *
3899     * @param changed if <code>true</code> the page has changed
3900     * @param unlock if <code>true</code> the page will be unlocked for unchanged pages
3901     */
3902    protected void setPageChanged(boolean changed, boolean unlock) {
3903
3904        if (changed) {
3905            if (!m_pageChanged) {
3906                m_pageChanged = changed;
3907                lockContainerpage(new I_CmsSimpleCallback<Boolean>() {
3908
3909                    public void execute(Boolean arg) {
3910
3911                        // nothing to do
3912                    }
3913                });
3914            }
3915        } else {
3916            m_pageChanged = changed;
3917            if (unlock) {
3918                unlockContainerpage();
3919            }
3920        }
3921    }
3922
3923    /**
3924     * Asynchronously unlocks the container page.
3925     */
3926    protected void unlockContainerpage() {
3927
3928        I_CmsAutoBeanFactory factory = CmsCoreProvider.AUTO_BEAN_FACTORY;
3929        AutoBean<I_CmsUnlockData> unlockParams = factory.unlockData();
3930        unlockParams.as().setPageId("" + CmsCoreProvider.get().getStructureId());
3931        if (getData().getDetailId() != null) {
3932            unlockParams.as().setDetailId("" + getData().getDetailId());
3933        }
3934        unlockParams.as().setLocale(CmsCoreProvider.get().getLocale());
3935        String url = CmsCoreProvider.get().link("/handleBuiltinService" + CmsGwtConstants.HANDLER_UNLOCK_PAGE);
3936        sendBeacon(url, AutoBeanCodex.encode(unlockParams).getPayload());
3937    }
3938
3939    /**
3940     * Returns the pages of editable containers.<p>
3941     *
3942     * @return the containers
3943     */
3944    List<CmsContainer> getEditableContainers() {
3945
3946        List<CmsContainer> containers = new ArrayList<CmsContainer>();
3947        for (CmsContainer container : m_containers.values()) {
3948            if ((m_targetContainers.get(container.getName()) != null)
3949                && isContainerEditable(m_targetContainers.get(container.getName()))) {
3950                containers.add(container);
3951            }
3952        }
3953        return containers;
3954    }
3955
3956    /**
3957     * Handles a window resize to reset highlighting and the edit button positions.<p>
3958     */
3959    void handleResize() {
3960
3961        m_resizeTimer = null;
3962        resetEditButtons();
3963    }
3964
3965    /**
3966     * Call on window resize.<p>
3967     */
3968    void onResize() {
3969
3970        if (!isGroupcontainerEditing() && (m_resizeTimer == null)) {
3971            m_resizeTimer = new Timer() {
3972
3973                @Override
3974                public void run() {
3975
3976                    handleResize();
3977                }
3978            };
3979            m_resizeTimer.schedule(300);
3980        }
3981    }
3982
3983    /**
3984     * Sets the load time.<p>
3985     *
3986     * @param time the time to set
3987     */
3988    void setLoadTime(Long time) {
3989
3990        if (time != null) {
3991            m_loadTime = time.longValue();
3992        }
3993    }
3994
3995    /**
3996     * Updates the gallery data according to the current element view and the editable containers.<p>
3997     * This method should only be called from the gallery update timer to avoid unnecessary requests.<p>
3998     *
3999     * @param viewChanged <code>true</code> in case the element view changed
4000     * @param nextAction the action to execute after updating the gallery data
4001     */
4002    void updateGalleryData(final boolean viewChanged, final Runnable nextAction) {
4003
4004        CmsRpcAction<CmsContainerPageGalleryData> dataAction = new CmsRpcAction<CmsContainerPageGalleryData>() {
4005
4006            @Override
4007            public void execute() {
4008
4009                getContainerpageService().getGalleryDataForPage(
4010                    getEditableContainers(),
4011                    getElementView().getElementViewId(),
4012                    CmsCoreProvider.get().getUri(),
4013                    getData().getDetailId(),
4014                    getData().getLocale(),
4015                    CmsContainerpageController.get().getData().getTemplateContextInfo(),
4016                    this);
4017            }
4018
4019            @Override
4020            protected void onResponse(CmsContainerPageGalleryData result) {
4021
4022                m_handler.m_editor.getAdd().updateGalleryData(result, viewChanged);
4023                if (nextAction != null) {
4024                    nextAction.run();
4025                }
4026            }
4027        };
4028        dataAction.execute();
4029    }
4030
4031    /**
4032     * Checks whether there are other references to a given container page element.<p>
4033     *
4034     * @param element the element to check
4035     * @param callback the callback which will be called with the result of the check (true if there are other references)
4036     */
4037    private void checkElementReferences(
4038        final CmsContainerPageElementPanel element,
4039        final AsyncCallback<CmsRemovedElementStatus> callback) {
4040
4041        ReferenceCheckVisitor visitor = new ReferenceCheckVisitor(element);
4042        processPageContent(visitor);
4043        if (visitor.hasReferences()) {
4044            // Don't need to ask the server because we already know we have other references in the same page
4045            CmsRpcAction<CmsListInfoBean> infoAction = new CmsRpcAction<CmsListInfoBean>() {
4046
4047                @Override
4048                public void execute() {
4049
4050                    start(200, true);
4051                    CmsCoreProvider.getVfsService().getPageInfo(new CmsUUID(getServerId(element.getId())), this);
4052                }
4053
4054                @Override
4055                protected void onResponse(CmsListInfoBean result) {
4056
4057                    stop(false);
4058                    callback.onSuccess(new CmsRemovedElementStatus(null, result, false, null));
4059                }
4060            };
4061            infoAction.execute();
4062        } else {
4063            CmsRpcAction<CmsRemovedElementStatus> getStatusAction = new CmsRpcAction<CmsRemovedElementStatus>() {
4064
4065                @Override
4066                public void execute() {
4067
4068                    start(200, true);
4069                    getContainerpageService().getRemovedElementStatus(
4070                        element.getId(),
4071                        CmsCoreProvider.get().getStructureId(),
4072                        this);
4073                }
4074
4075                @Override
4076                public void onResponse(final CmsRemovedElementStatus status) {
4077
4078                    stop(false);
4079                    callback.onSuccess(status);
4080                }
4081
4082            };
4083            getStatusAction.execute();
4084
4085        }
4086    }
4087
4088    /**
4089     * Checks if the page was locked by another user at load time.<p>
4090     */
4091    private void checkLockInfo() {
4092
4093        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(getData().getLockInfo())) {
4094            CmsNotification.get().send(Type.ERROR, getData().getLockInfo());
4095            m_lockStatus = LockStatus.failed;
4096            m_handler.m_editor.disableEditing(getData().getLockInfo());
4097        }
4098    }
4099
4100    /**
4101     * Selects the next container edit level.<p>
4102     */
4103    private void circleContainerEditLayers() {
4104
4105        if (m_isContentEditing || isGroupcontainerEditing() || (m_maxContainerLevel == 0)) {
4106            return;
4107        }
4108        boolean hasEditables = false;
4109        int previousLevel = m_currentEditLevel;
4110        String message = "";
4111        while (!hasEditables) {
4112            if (m_currentEditLevel == m_maxContainerLevel) {
4113                m_currentEditLevel = -1;
4114                message = Messages.get().key(Messages.GUI_SWITCH_EDIT_LEVEL_ALL_1, m_elementView.getTitle());
4115            } else {
4116                m_currentEditLevel++;
4117                message = Messages.get().key(Messages.GUI_SWITCH_EDIT_LEVEL_1, Integer.valueOf(m_currentEditLevel));
4118            }
4119            reinitializeButtons();
4120            hasEditables = !CmsDomUtil.getElementsByClass(
4121                I_CmsElementToolbarContext.ELEMENT_OPTION_BAR_CSS_CLASS).isEmpty();
4122        }
4123        if (previousLevel != m_currentEditLevel) {
4124            CmsNotification.get().send(Type.NORMAL, message);
4125        }
4126    }
4127
4128    /**
4129     * Returns all element id's related to the given one.<p>
4130     *
4131     * @param id the element id
4132     * @return the related id's
4133     */
4134    private Set<String> getRelatedElementIds(String id) {
4135
4136        Set<String> result = new HashSet<String>();
4137        if (id != null) {
4138            result.add(id);
4139            String serverId = getServerId(id);
4140
4141            Iterator<String> it = m_elements.keySet().iterator();
4142            while (it.hasNext()) {
4143                String elId = it.next();
4144                if (elId.startsWith(serverId)) {
4145                    result.add(elId);
4146                }
4147            }
4148
4149            Iterator<org.opencms.ade.containerpage.client.ui.CmsContainerPageElementPanel> itEl = getAllDragElements().iterator();
4150            while (itEl.hasNext()) {
4151                org.opencms.ade.containerpage.client.ui.CmsContainerPageElementPanel element = itEl.next();
4152                if (element.getId().startsWith(serverId)) {
4153                    result.add(element.getId());
4154                }
4155            }
4156        }
4157        return result;
4158    }
4159
4160    /**
4161     * Checks whether the given container matches the current edit level.<p>
4162     *
4163     * @param container the container to check
4164     *
4165     * @return <code>true</code> if the given container matches the current edit level
4166     */
4167    private boolean matchesCurrentEditLevel(I_CmsDropContainer container) {
4168
4169        boolean result = !(container instanceof CmsContainerPageContainer)
4170            || (m_currentEditLevel == -1)
4171            || (m_currentEditLevel == ((CmsContainerPageContainer)container).getContainerLevel());
4172        return result;
4173    }
4174
4175    /**
4176     * Opens the next available root element view.<p>
4177     */
4178    private void openNextElementView() {
4179
4180        List<CmsElementViewInfo> views = getData().getElementViews();
4181        if (views.size() > 1) {
4182            CmsUUID current = m_elementView.getRootViewId();
4183
4184            // look for the current view index
4185            int currentIndex = -1;
4186            for (int i = 0; i < views.size(); i++) {
4187                CmsElementViewInfo view = views.get(i);
4188                if (view.isRoot() && current.equals(view.getElementViewId())) {
4189                    currentIndex = i;
4190                    break;
4191                }
4192            }
4193            if (currentIndex != -1) {
4194                CmsElementViewInfo target = null;
4195                // look for the next root view
4196                for (int i = currentIndex + 1; i < views.size(); i++) {
4197                    CmsElementViewInfo view = views.get(i);
4198                    if (view.isRoot()) {
4199                        target = view;
4200                        break;
4201                    }
4202                }
4203                if (target == null) {
4204                    // start at the beginning
4205                    for (int i = 0; i < currentIndex; i++) {
4206                        CmsElementViewInfo view = views.get(i);
4207                        if (view.isRoot()) {
4208                            target = view;
4209                            break;
4210                        }
4211                    }
4212                }
4213                if (target != null) {
4214                    final String viewName = target.getTitle();
4215                    Runnable action = new Runnable() {
4216
4217                        public void run() {
4218
4219                            CmsNotification.get().send(
4220                                Type.NORMAL,
4221                                Messages.get().key(Messages.GUI_SWITCH_ELEMENT_VIEW_NOTIFICATION_1, viewName));
4222                        }
4223                    };
4224                    setElementView(target, action);
4225                }
4226            }
4227        }
4228    }
4229
4230    /**
4231     * Removes the edit buttons position timer.<p>
4232     */
4233    private void removeEditButtonsPositionTimer() {
4234
4235        if (m_editButtonsPositionTimer != null) {
4236            m_editButtonsPositionTimer.cancel();
4237            m_editButtonsPositionTimer = null;
4238        }
4239    }
4240
4241    /**
4242     * Calls the browser's sendBeacon function.
4243     *
4244     * @param url the URL to send the data to
4245     * @param data the data to send
4246     */
4247    private native void sendBeacon(String url, String data) /*-{
4248        $wnd.navigator.sendBeacon(url, data);
4249    }-*/;
4250
4251    /**
4252     * Checks whether given element is a model group and it's option bar edit points should be visible.<p>
4253     *
4254     * @param element the element to check
4255     *
4256     * @return <code>true</code> in case the current page is not a model group page,
4257     *  the given element is a model group and it is inside a view visible to the current user
4258     */
4259    private boolean shouldShowModelgroupOptionBar(CmsContainerPageElementPanel element) {
4260
4261        if (!getData().isModelGroup() && element.isModelGroup()) {
4262            for (CmsElementViewInfo info : getData().getElementViews()) {
4263                if (info.getElementViewId().equals(element.getElementView())) {
4264                    return true;
4265                }
4266            }
4267        }
4268
4269        return false;
4270    }
4271
4272    /**
4273     * Updates the container level info on the present containers.<p>
4274     */
4275    private void updateContainerLevelInfo() {
4276
4277        Map<String, CmsContainerPageContainer> containers = new HashMap<String, CmsContainerPageContainer>();
4278        List<CmsContainerPageContainer> temp = new ArrayList<CmsContainerPageContainer>(m_targetContainers.values());
4279        m_maxContainerLevel = 0;
4280        boolean progress = true;
4281        while (!temp.isEmpty() && progress) {
4282            int size = containers.size();
4283            Iterator<CmsContainerPageContainer> it = temp.iterator();
4284            while (it.hasNext()) {
4285                CmsContainerPageContainer container = it.next();
4286                int level = -1;
4287                if (CmsStringUtil.isEmptyOrWhitespaceOnly(container.getParentContainerId())) {
4288                    level = 0;
4289                } else if (containers.containsKey(container.getParentContainerId())) {
4290                    level = containers.get(container.getParentContainerId()).getContainerLevel() + 1;
4291                }
4292                if (level > -1) {
4293                    container.setContainerLevel(level);
4294                    containers.put(container.getContainerId(), container);
4295                    it.remove();
4296                    if (level > m_maxContainerLevel) {
4297                        m_maxContainerLevel = level;
4298                    }
4299                }
4300            }
4301            progress = containers.size() > size;
4302        }
4303    }
4304
4305    /**
4306     * Sets the oc-detail-preview class on first container elements of an appropriate type in detail containers,
4307     * if we are currently not showing a detail content.
4308     */
4309    private void updateDetailPreviewStyles() {
4310
4311        Set<String> detailTypes = getData().getDetailTypes();
4312        if ((getData().getDetailId() != null) || detailTypes.isEmpty()) {
4313            return;
4314        }
4315        for (Element elem : CmsDomUtil.getElementsByClass(CmsGwtConstants.CLASS_DETAIL_PREVIEW)) {
4316            elem.removeClassName(CmsGwtConstants.CLASS_DETAIL_PREVIEW);
4317        }
4318        boolean defaultDetailPage = detailTypes.contains(CmsGwtConstants.DEFAULT_DETAILPAGE_TYPE);
4319
4320        processPageContent(new I_PageContentVisitor() {
4321
4322            boolean m_isdetail = false;
4323
4324            public boolean beginContainer(String name, CmsContainer container) {
4325
4326                m_isdetail = container.isDetailViewContainer();
4327                return true;
4328            }
4329
4330            public void endContainer() {
4331
4332                // do nothing
4333            }
4334
4335            public void handleElement(CmsContainerPageElementPanel element) {
4336
4337                if (m_isdetail && (defaultDetailPage || detailTypes.contains(element.getResourceType()))) {
4338                    element.addStyleName(CmsGwtConstants.CLASS_DETAIL_PREVIEW);
4339                }
4340            }
4341        });
4342
4343    }
4344}