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.sitemap.client;
029
030import org.opencms.ade.sitemap.client.control.CmsSitemapChangeEvent;
031import org.opencms.ade.sitemap.client.control.CmsSitemapController;
032import org.opencms.ade.sitemap.client.control.CmsSitemapDNDController;
033import org.opencms.ade.sitemap.client.control.CmsSitemapLoadEvent;
034import org.opencms.ade.sitemap.client.control.I_CmsSitemapChangeHandler;
035import org.opencms.ade.sitemap.client.control.I_CmsSitemapLoadHandler;
036import org.opencms.ade.sitemap.client.hoverbar.A_CmsSitemapMenuEntry;
037import org.opencms.ade.sitemap.client.hoverbar.CmsChangeCategoryMenuEntry;
038import org.opencms.ade.sitemap.client.hoverbar.CmsCreateCategoryMenuEntry;
039import org.opencms.ade.sitemap.client.hoverbar.CmsCreateCategoryMenuEntry.CmsCategoryTitleAndName;
040import org.opencms.ade.sitemap.client.hoverbar.CmsDeleteCategoryMenuEntry;
041import org.opencms.ade.sitemap.client.hoverbar.CmsHoverbarCreateGalleryButton;
042import org.opencms.ade.sitemap.client.hoverbar.CmsHoverbarCreateModelPageButton;
043import org.opencms.ade.sitemap.client.hoverbar.CmsSitemapHoverbar;
044import org.opencms.ade.sitemap.client.hoverbar.I_CmsContextMenuItemProvider;
045import org.opencms.ade.sitemap.client.toolbar.CmsSitemapToolbar;
046import org.opencms.ade.sitemap.client.ui.CmsStatusIconUpdateHandler;
047import org.opencms.ade.sitemap.client.ui.css.I_CmsImageBundle;
048import org.opencms.ade.sitemap.client.ui.css.I_CmsSitemapLayoutBundle;
049import org.opencms.ade.sitemap.shared.CmsClientSitemapEntry;
050import org.opencms.ade.sitemap.shared.CmsDetailPageTable;
051import org.opencms.ade.sitemap.shared.CmsGalleryFolderEntry;
052import org.opencms.ade.sitemap.shared.CmsGalleryType;
053import org.opencms.ade.sitemap.shared.CmsModelInfo;
054import org.opencms.ade.sitemap.shared.CmsModelPageEntry;
055import org.opencms.ade.sitemap.shared.CmsSitemapCategoryData;
056import org.opencms.ade.sitemap.shared.CmsSitemapChange;
057import org.opencms.ade.sitemap.shared.CmsSitemapData;
058import org.opencms.ade.sitemap.shared.CmsSitemapData.EditorMode;
059import org.opencms.ade.sitemap.shared.CmsSitemapInfo;
060import org.opencms.file.CmsResource;
061import org.opencms.gwt.client.A_CmsEntryPoint;
062import org.opencms.gwt.client.CmsBroadcastTimer;
063import org.opencms.gwt.client.dnd.CmsDNDHandler;
064import org.opencms.gwt.client.rpc.CmsRpcAction;
065import org.opencms.gwt.client.ui.CmsErrorDialog;
066import org.opencms.gwt.client.ui.CmsInfoHeader;
067import org.opencms.gwt.client.ui.CmsList;
068import org.opencms.gwt.client.ui.CmsListItemWidget;
069import org.opencms.gwt.client.ui.CmsListItemWidget.Background;
070import org.opencms.gwt.client.ui.CmsNotification;
071import org.opencms.gwt.client.ui.CmsPushButton;
072import org.opencms.gwt.client.ui.I_CmsButton;
073import org.opencms.gwt.client.ui.I_CmsButton.ButtonStyle;
074import org.opencms.gwt.client.ui.I_CmsListItem;
075import org.opencms.gwt.client.ui.css.I_CmsLayoutBundle;
076import org.opencms.gwt.client.ui.tree.CmsLazyTree;
077import org.opencms.gwt.client.ui.tree.CmsLazyTreeItem;
078import org.opencms.gwt.client.ui.tree.CmsTree;
079import org.opencms.gwt.client.ui.tree.CmsTreeItem;
080import org.opencms.gwt.client.ui.tree.I_CmsLazyOpenHandler;
081import org.opencms.gwt.client.util.CmsDomUtil;
082import org.opencms.gwt.client.util.CmsJsUtil;
083import org.opencms.gwt.client.util.CmsScriptCallbackHelper;
084import org.opencms.gwt.client.util.CmsStyleVariable;
085import org.opencms.gwt.shared.CmsCategoryBean;
086import org.opencms.gwt.shared.CmsCategoryTreeEntry;
087import org.opencms.gwt.shared.CmsGwtConstants;
088import org.opencms.gwt.shared.CmsListInfoBean;
089import org.opencms.gwt.shared.property.CmsClientProperty;
090import org.opencms.gwt.shared.property.CmsPropertyModification;
091import org.opencms.util.CmsPair;
092import org.opencms.util.CmsStringUtil;
093import org.opencms.util.CmsUUID;
094
095import java.util.ArrayList;
096import java.util.Arrays;
097import java.util.Collection;
098import java.util.Collections;
099import java.util.Comparator;
100import java.util.HashMap;
101import java.util.HashSet;
102import java.util.List;
103import java.util.Map;
104import java.util.Set;
105
106import com.google.common.base.Function;
107import com.google.common.collect.Lists;
108import com.google.common.collect.Multimap;
109import com.google.gwt.core.client.JsArrayString;
110import com.google.gwt.core.client.Scheduler;
111import com.google.gwt.core.client.Scheduler.ScheduledCommand;
112import com.google.gwt.dom.client.Style.Display;
113import com.google.gwt.event.dom.client.ClickEvent;
114import com.google.gwt.event.dom.client.ClickHandler;
115import com.google.gwt.event.logical.shared.OpenEvent;
116import com.google.gwt.user.client.Window;
117import com.google.gwt.user.client.rpc.AsyncCallback;
118import com.google.gwt.user.client.ui.FlowPanel;
119import com.google.gwt.user.client.ui.Label;
120import com.google.gwt.user.client.ui.RootPanel;
121import com.google.gwt.user.client.ui.Widget;
122
123/**
124 * Sitemap editor.<p>
125 *
126 * @since 8.0.0
127 */
128public final class CmsSitemapView extends A_CmsEntryPoint
129implements I_CmsSitemapChangeHandler, I_CmsSitemapLoadHandler {
130
131    /**
132     * The sitemap tree open handler.<p>
133     */
134    protected class TreeOpenHandler implements I_CmsLazyOpenHandler<CmsSitemapTreeItem> {
135
136        /** Flag indicating the tree is initializing. */
137        private boolean m_initializing;
138
139        /**
140         * @see org.opencms.gwt.client.ui.tree.I_CmsLazyOpenHandler#load(org.opencms.gwt.client.ui.tree.CmsLazyTreeItem, java.lang.Runnable)
141         */
142        public void load(final CmsSitemapTreeItem target, Runnable loadCallback) {
143
144            // not used
145        }
146
147        /**
148         * @see org.opencms.gwt.client.ui.tree.I_CmsLazyOpenHandler#onOpen(com.google.gwt.event.logical.shared.OpenEvent)
149         */
150        public void onOpen(OpenEvent<CmsSitemapTreeItem> event) {
151
152            CmsSitemapTreeItem target = event.getTarget();
153            if ((target.getLoadState() == CmsLazyTreeItem.LoadState.UNLOADED)) {
154                target.onStartLoading();
155                target.setOpen(false);
156                getController().getChildren(target.getEntryId(), true, null);
157            } else if (!m_initializing
158                && ((target.getChildren().getWidgetCount() > 0)
159                    && (((CmsSitemapTreeItem)target.getChild(
160                        0)).getLoadState() == CmsLazyTreeItem.LoadState.UNLOADED))) {
161                            // load grand children in advance
162                            getController().getChildren(target.getEntryId(), false, null);
163                        }
164        }
165
166        /**
167         * Sets the initializing flag.<p>
168         *
169         * @param initializing the initializing flag
170         */
171        protected void setInitializing(boolean initializing) {
172
173            m_initializing = initializing;
174        }
175    }
176
177    /** The download gallery type name. */
178    public static final String DOWNLOAD_GALLERY_TYPE = "downloadgallery";
179
180    /** The image gallery type name. */
181    public static final String IMAGE_GALLERY_TYPE = "imagegallery";
182
183    /** The singleton instance. */
184    private static CmsSitemapView m_instance;
185
186    /** Text metrics key. */
187    private static final String TM_SITEMAP = "Sitemap";
188
189    /** The displayed sitemap tree. */
190    protected CmsLazyTree<CmsSitemapTreeItem> m_tree;
191
192    /** The tree shown in category mode. */
193    private CmsTree<CmsTreeItem> m_categoryTree;
194
195    /** The controller. */
196    private CmsSitemapController m_controller;
197
198    /** The current sitemap editor mode. */
199    private EditorMode m_editorMode;
200
201    /** The gallery tree widget. */
202    private CmsTree<CmsGalleryTreeItem> m_galleryTree;
203
204    /** The gallery folder items by id. */
205    private Map<CmsUUID, CmsGalleryTreeItem> m_galleryTreeItems;
206
207    /** The gallery type items by type name. */
208    private Map<String, CmsGalleryTreeItem> m_galleryTypeItems;
209
210    /** The header. */
211    private CmsInfoHeader m_header;
212
213    /** The header container. */
214    private FlowPanel m_headerContainer;
215
216    /** Style variable which keeps track of whether we are in VFS mode or navigation mode. */
217    private CmsStyleVariable m_inNavigationStyle;
218
219    /** The locale comparison view. */
220    private FlowPanel m_localeComparison = new FlowPanel();
221
222    /** The model group pages root entry. */
223    private CmsModelPageTreeItem m_modelGroupRoot;
224
225    /** Model page data beans for the model page mode. */
226    private Map<CmsUUID, CmsModelPageEntry> m_modelPageData = new HashMap<CmsUUID, CmsModelPageEntry>();
227
228    /** Root tree item for the model page mode. */
229    private CmsModelPageTreeItem m_modelPageRoot;
230
231    /** Tree for the model page editor mode. */
232    private CmsTree<CmsModelPageTreeItem> m_modelPageTree;
233
234    /** The tree items for the model page mode, indexed by structure id. */
235    private Map<CmsUUID, CmsModelPageTreeItem> m_modelPageTreeItems = new HashMap<CmsUUID, CmsModelPageTreeItem>();
236
237    /** Flag to indicate that the tree needs to be refreshed. */
238    private boolean m_needTreeRefresh;
239
240    /** Label to display to no galleries in this sub site message. */
241    private Label m_noGalleriesLabel;
242
243    /** The tree open handler. */
244    private TreeOpenHandler m_openHandler;
245
246    /** The page. */
247    private FlowPanel m_page;
248
249    /** The parent model page root. */
250    private CmsModelPageTreeItem m_parentModelPageRoot;
251
252    /** The parent model page entries. */
253    private Map<CmsUUID, CmsModelPageTreeItem> m_parentModelPageTreeItems = new HashMap<CmsUUID, CmsModelPageTreeItem>();
254
255    /** The sitemap tree root item. */
256    private CmsSitemapTreeItem m_rootItem;
257
258    /** The sitemap toolbar. */
259    private CmsSitemapToolbar m_toolbar;
260
261    /** The registered tree items. */
262    private Map<CmsUUID, CmsSitemapTreeItem> m_treeItems;
263
264    /**
265     * Returns the instance.<p>
266     *
267     * @return the instance
268     */
269    public static CmsSitemapView getInstance() {
270
271        return m_instance;
272    }
273
274    /**
275     * Creates a new tree item from the given sitemap entry.<p>
276     *
277     * @param entry the sitemap entry
278     *
279     * @return the new created (still orphan) tree item
280     */
281    public CmsSitemapTreeItem create(CmsClientSitemapEntry entry) {
282
283        CmsSitemapTreeItem treeItem = new CmsSitemapTreeItem(entry);
284        CmsSitemapHoverbar.installOn(m_controller, treeItem, entry.getId());
285        // highlight the open path
286        if (isLastPage(entry)) {
287            treeItem.setBackgroundColor(Background.YELLOW);
288        }
289        m_treeItems.put(entry.getId(), treeItem);
290        return treeItem;
291    }
292
293    /**
294     * Creates a sitemap tree item from a client sitemap entry.<p>
295     *
296     * @param entry the entry from which the sitemap tree item should be created
297     *
298     * @return the new sitemap tree item
299     */
300    public CmsSitemapTreeItem createSitemapItem(CmsClientSitemapEntry entry) {
301
302        CmsSitemapTreeItem result = create(entry);
303        result.clearChildren();
304        for (CmsClientSitemapEntry child : entry.getSubEntries()) {
305            CmsSitemapTreeItem childItem = createSitemapItem(child);
306            if (!entry.isSubSitemapType()) {
307                result.addChild(childItem);
308            }
309        }
310        if (entry.getChildrenLoadedInitially()) {
311            result.onFinishLoading();
312        }
313        return result;
314    }
315
316    /**
317     * Displays the category data.<p>
318     *
319     * @param categoryData the category data
320     * @param openLocalCategories true if the local category tree should be opened
321     * @param openItemId the id of the item to open
322     */
323    public void displayCategoryData(
324        CmsSitemapCategoryData categoryData,
325        final boolean openLocalCategories,
326        CmsUUID openItemId) {
327
328        final Set<CmsUUID> openIds = new HashSet<CmsUUID>();
329        if (openLocalCategories) {
330            if (openItemId != null) {
331                openIds.add(openItemId);
332            }
333            for (Widget item : m_categoryTree) {
334                if (item instanceof CmsTreeItem) {
335                    ((CmsTreeItem)item).visit(new Function<CmsTreeItem, Boolean>() {
336
337                        public Boolean apply(CmsTreeItem input) {
338
339                            if (input.isOpen() && (input instanceof CmsCategoryTreeItem)) {
340                                openIds.add(((CmsCategoryTreeItem)input).getStructureId());
341                            }
342                            return null;
343                        }
344                    });
345                }
346            }
347        }
348
349        m_categoryTree.clear();
350
351        Multimap<Boolean, CmsCategoryTreeEntry> entries = categoryData.getEntriesIndexedByLocality();
352        String localLabel = Messages.get().key(Messages.GUI_CATEGORIES_LOCAL_0);
353        String nonlocalLabel = Messages.get().key(Messages.GUI_CATEGORIES_NONLOCAL_0);
354
355        String localSubtitle = Messages.get().key(Messages.GUI_CATEGORIES_LOCAL_SUBTITLE_0);
356        String nonlocalSubtitle = Messages.get().key(Messages.GUI_CATEGORIES_NONLOCAL_SUBTITLE_0);
357
358        CmsListInfoBean localRootInfo = new CmsListInfoBean(localLabel, localSubtitle, null);
359        localRootInfo.setResourceType("category");
360        localRootInfo.setBigIconClasses(CmsCategoryBean.BIG_ICON_CLASSES);
361        final CmsTreeItem localRoot = new CmsTreeItem(true, new CmsListItemWidget(localRootInfo));
362
363        CmsListInfoBean nonlocalRootInfo = new CmsListInfoBean(nonlocalLabel, nonlocalSubtitle, null);
364        nonlocalRootInfo.setResourceType("category");
365        nonlocalRootInfo.setBigIconClasses(CmsCategoryBean.BIG_ICON_CLASSES);
366        final CmsTreeItem nonlocalRoot = new CmsTreeItem(true, new CmsListItemWidget(nonlocalRootInfo));
367
368        m_categoryTree.add(nonlocalRoot);
369        m_categoryTree.add(localRoot);
370
371        for (Boolean isLocal : Arrays.asList(Boolean.FALSE, Boolean.TRUE)) {
372            for (CmsCategoryTreeEntry entry : entries.get(isLocal)) {
373                CmsTreeItem treeItem = createCategoryTreeItem(entry);
374                (isLocal.booleanValue() ? localRoot : nonlocalRoot).addChild(treeItem);
375            }
376        }
377
378        for (CmsTreeItem root : Arrays.asList(localRoot, nonlocalRoot)) {
379            final CmsTreeItem finalRoot = root;
380
381            root.visit(new Function<CmsTreeItem, Boolean>() {
382
383                @SuppressWarnings("synthetic-access")
384                public Boolean apply(CmsTreeItem input) {
385
386                    CmsUUID id = null;
387                    if (input == localRoot) {
388                        id = CmsUUID.getNullUUID();
389                    } else if ((input instanceof CmsCategoryTreeItem) && (localRoot == finalRoot)) {
390                        id = ((CmsCategoryTreeItem)input).getStructureId();
391
392                    }
393
394                    if ((id != null) && m_controller.isEditable()) {
395                        final CmsSitemapHoverbar hoverbar = CmsSitemapHoverbar.installOn(
396                            m_controller,
397                            input,
398                            id,
399                            false,
400                            !(id.isNullUUID()),
401                            new I_CmsContextMenuItemProvider() {
402
403                                public List<A_CmsSitemapMenuEntry> createContextMenu(CmsSitemapHoverbar hoverbar2) {
404
405                                    List<A_CmsSitemapMenuEntry> result = Lists.newArrayList();
406
407                                    result.add(new CmsChangeCategoryMenuEntry(hoverbar2));
408                                    result.add(new CmsDeleteCategoryMenuEntry(hoverbar2));
409                                    return result;
410                                }
411                            });
412                        if (input == localRoot) {
413                            hoverbar.setAlwaysVisible();
414                        }
415                        CmsPushButton newButton = new CmsPushButton();
416                        newButton.setTitle(Messages.get().key(Messages.GUI_SITEMAP_CONTEXT_MENU_CREATE_CATEGORY_0));
417                        newButton.setImageClass(I_CmsButton.ADD_SMALL);
418                        newButton.setButtonStyle(ButtonStyle.FONT_ICON, null);
419                        hoverbar.add(newButton);
420                        newButton.addClickHandler(new ClickHandler() {
421
422                            /**
423                             * @see com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt.event.dom.client.ClickEvent)
424                             */
425                            public void onClick(ClickEvent event) {
426
427                                hoverbar.hide();
428                                final CmsSitemapController controller = hoverbar.getController();
429                                final CmsUUID hoverbarId = hoverbar.getId();
430                                CmsCreateCategoryMenuEntry.askForNewCategoryInfo(
431                                    hoverbarId,
432                                    new AsyncCallback<CmsCategoryTitleAndName>() {
433
434                                        public void onFailure(Throwable caught) {
435
436                                            // do nothing
437                                        }
438
439                                        public void onSuccess(CmsCategoryTitleAndName result) {
440
441                                            controller.createCategory(hoverbarId, result.getTitle(), result.getName());
442                                        }
443                                    });
444                            }
445                        });
446                    }
447                    if (((finalRoot == localRoot) && openLocalCategories && openIds.contains(id))
448                        || (input == nonlocalRoot)
449                        || (input == localRoot)) {
450                        input.setOpen(true);
451                    }
452                    return null;
453                }
454            });
455        }
456    }
457
458    /**
459     * Displays the gallery view.<p>
460     *
461     * @param galleries the gallery data
462     */
463    public void displayGalleries(Map<CmsGalleryType, List<CmsGalleryFolderEntry>> galleries) {
464
465        m_galleryTree.clear();
466        m_galleryTreeItems.clear();
467        m_galleryTypeItems.clear();
468        CmsUUID galleriesFolderId = null;
469        if (getRootItem().getChild(m_controller.getData().getDefaultGalleryFolder()) != null) {
470            galleriesFolderId = ((CmsSitemapTreeItem)getRootItem().getChild(
471                m_controller.getData().getDefaultGalleryFolder())).getEntryId();
472        }
473        List<CmsGalleryType> types = new ArrayList<CmsGalleryType>(galleries.keySet());
474        Collections.sort(types, new Comparator<CmsGalleryType>() {
475
476            public int compare(CmsGalleryType o1, CmsGalleryType o2) {
477
478                return o1.getTitle().compareTo(o2.getTitle());
479            }
480        });
481        m_toolbar.setGalleryTypes(types);
482        boolean hasGalleries = false;
483        for (CmsGalleryType type : types) {
484            CmsGalleryTreeItem typeItem = new CmsGalleryTreeItem(type);
485            if (m_controller.isEditable() && m_controller.getData().isGalleryManager()) {
486                CmsHoverbarCreateGalleryButton createButton = new CmsHoverbarCreateGalleryButton(
487                    type.getTypeId(),
488                    galleriesFolderId);
489                CmsSitemapHoverbar hoverbar = CmsSitemapHoverbar.installOn(
490                    m_controller,
491                    typeItem,
492                    Collections.<Widget> singleton(createButton));
493                createButton.setHoverbar(hoverbar);
494                hoverbar.setAlwaysVisible();
495            }
496            m_galleryTypeItems.put(type.getResourceType(), typeItem);
497            addGalleryEntries(typeItem, galleries.get(type));
498            typeItem.setOpen(true);
499            hasGalleries = true;
500            m_galleryTree.addItem(typeItem);
501        }
502        // position image and download galleries at the top
503        if (m_galleryTypeItems.containsKey(DOWNLOAD_GALLERY_TYPE)) {
504            m_galleryTree.insertItem(m_galleryTypeItems.get(DOWNLOAD_GALLERY_TYPE), 0);
505        }
506        if (m_galleryTypeItems.containsKey(IMAGE_GALLERY_TYPE)) {
507            m_galleryTree.insertItem(m_galleryTypeItems.get(IMAGE_GALLERY_TYPE), 0);
508        }
509        m_galleryTree.truncate(TM_SITEMAP, 920);
510        if (hasGalleries) {
511            m_noGalleriesLabel.getElement().getStyle().setDisplay(Display.NONE);
512        } else {
513            m_noGalleriesLabel.getElement().getStyle().clearDisplay();
514        }
515    }
516
517    /**
518     * Displays the model page data in a tree.<p>
519     *
520     * @param modelPageData the model page data
521     */
522    public void displayModelPages(CmsModelInfo modelPageData) {
523
524        m_modelPageTree.clear();
525        m_modelPageTreeItems.clear();
526        m_parentModelPageTreeItems.clear();
527
528        if ((modelPageData.getParentModelPages() != null) && !modelPageData.getParentModelPages().isEmpty()) {
529            m_parentModelPageRoot = CmsModelPageTreeItem.createRootItem(
530                false,
531                Messages.get().key(Messages.GUI_PARENT_MODEL_PAGE_TREE_ROOT_TITLE_0),
532                "");
533
534            for (CmsModelPageEntry parentModel : modelPageData.getParentModelPages()) {
535                if (parentModel.isDisabled()) {
536                    continue;
537                }
538                CmsModelPageTreeItem treeItem = new CmsModelPageTreeItem(parentModel, false, true);
539                if (m_controller.isEditable()) {
540                    CmsSitemapHoverbar.installOn(
541                        m_controller,
542                        treeItem,
543                        parentModel.getStructureId(),
544                        parentModel.getSitePath(),
545                        parentModel.getSitePath() != null);
546                }
547                m_parentModelPageTreeItems.put(parentModel.getStructureId(), treeItem);
548                m_parentModelPageRoot.addChild(treeItem);
549                m_modelPageData.put(parentModel.getStructureId(), parentModel);
550            }
551            if (m_parentModelPageRoot.getChildren().getWidgetCount() > 0) {
552                m_modelPageTree.add(m_parentModelPageRoot);
553            } else {
554                m_parentModelPageRoot = null;
555            }
556        } else {
557            m_parentModelPageRoot = null;
558        }
559        m_modelPageRoot = CmsModelPageTreeItem.createRootItem(
560            false,
561            Messages.get().key(Messages.GUI_MODEL_PAGE_TREE_ROOT_TITLE_0),
562            Messages.get().key(Messages.GUI_MODEL_PAGE_TREE_ROOT_SUBTITLE_0));
563        if (m_controller.isEditable()) {
564            CmsHoverbarCreateModelPageButton createButton = new CmsHoverbarCreateModelPageButton(false);
565            CmsSitemapHoverbar hoverbar = CmsSitemapHoverbar.installOn(
566                m_controller,
567                m_modelPageRoot,
568                Collections.<Widget> singleton(createButton));
569            hoverbar.setAlwaysVisible();
570            createButton.setHoverbar(hoverbar);
571        }
572        m_modelPageTree.add(m_modelPageRoot);
573        for (CmsModelPageEntry entry : modelPageData.getModelPages()) {
574            if (m_parentModelPageTreeItems.containsKey(entry.getStructureId())) {
575                CmsModelPageTreeItem item = m_parentModelPageTreeItems.get(entry.getStructureId());
576                item.setDisabled(entry.isDisabled());
577            } else {
578                m_modelPageData.put(entry.getStructureId(), entry);
579                CmsModelPageTreeItem treeItem = new CmsModelPageTreeItem(entry, false, false);
580                if (m_controller.isEditable()) {
581                    CmsSitemapHoverbar.installOn(
582                        m_controller,
583                        treeItem,
584                        entry.getStructureId(),
585                        entry.getSitePath(),
586                        entry.getSitePath() != null);
587                }
588                m_modelPageTreeItems.put(entry.getStructureId(), treeItem);
589                m_modelPageRoot.addChild(treeItem);
590            }
591        }
592        m_modelPageRoot.setOpen(true);
593        m_modelGroupRoot = CmsModelPageTreeItem.createRootItem(
594            true,
595            Messages.get().key(Messages.GUI_MODEL_GROUP_PAGE_TREE_ROOT_TITLE_0),
596            Messages.get().key(Messages.GUI_MODEL_GROUP_PAGE_TREE_ROOT_SUBTITLE_0));
597        if (m_controller.isEditable()) {
598            CmsHoverbarCreateModelPageButton createModelGroupButton = new CmsHoverbarCreateModelPageButton(true);
599            CmsSitemapHoverbar modelGroupHoverbar = CmsSitemapHoverbar.installOn(
600                m_controller,
601                m_modelGroupRoot,
602                Collections.<Widget> singleton(createModelGroupButton));
603            modelGroupHoverbar.setAlwaysVisible();
604            createModelGroupButton.setHoverbar(modelGroupHoverbar);
605        }
606        m_modelPageTree.add(m_modelGroupRoot);
607        for (CmsModelPageEntry entry : modelPageData.getModelGroups()) {
608            CmsModelPageTreeItem treeItem = new CmsModelPageTreeItem(entry, true, false);
609            if (m_controller.isEditable()) {
610                CmsSitemapHoverbar.installOn(
611                    m_controller,
612                    treeItem,
613                    entry.getStructureId(),
614                    entry.getSitePath(),
615                    entry.getSitePath() != null);
616            }
617            m_modelPageTreeItems.put(entry.getStructureId(), treeItem);
618            m_modelGroupRoot.addChild(treeItem);
619        }
620        m_modelGroupRoot.setOpen(true);
621        m_modelPageTree.truncate(TM_SITEMAP, 920);
622    }
623
624    /**
625     * Displays a newly created gallery folder.<p>
626     *
627     * @param galleryFolder the gallery folder
628     */
629    public void displayNewGallery(CmsGalleryFolderEntry galleryFolder) {
630
631        String parent = CmsResource.getParentFolder(galleryFolder.getSitePath());
632        CmsSitemapTreeItem parentItem = getTreeItem(parent);
633        if (parentItem != null) {
634            CmsUUID parentId = parentItem.getEntryId();
635            m_controller.updateEntry(parentId);
636        } else {
637            m_controller.loadPath(parent);
638        }
639        CmsGalleryTreeItem typeItem = m_galleryTypeItems.get(galleryFolder.getResourceType());
640        CmsGalleryTreeItem folderItem = createGalleryFolderItem(galleryFolder);
641        if ((parentItem != null) && m_galleryTreeItems.containsKey(parentItem.getEntryId())) {
642            CmsGalleryTreeItem galleryParent = m_galleryTreeItems.get(parentItem.getEntryId());
643            galleryParent.addChild(folderItem);
644            galleryParent.setOpen(true);
645        } else {
646            typeItem.addChild(folderItem);
647        }
648        m_galleryTreeItems.put(galleryFolder.getStructureId(), folderItem);
649        typeItem.setOpen(true);
650        // in case the type item had been hidden
651        typeItem.getElement().getStyle().clearDisplay();
652        m_noGalleriesLabel.getElement().getStyle().setDisplay(Display.NONE);
653    }
654
655    /**
656     * Adds a new model page to the model page view.<p>
657     *
658     * @param modelPageData the data for the new model page
659     * @param isModelGroup in case of a model group page
660     */
661    public void displayNewModelPage(CmsModelPageEntry modelPageData, boolean isModelGroup) {
662
663        CmsModelPageTreeItem treeItem = new CmsModelPageTreeItem(modelPageData, isModelGroup, false);
664        CmsSitemapHoverbar.installOn(
665            m_controller,
666            treeItem,
667            modelPageData.getStructureId(),
668            modelPageData.getSitePath(),
669            modelPageData.getSitePath() != null);
670        m_modelPageTreeItems.put(modelPageData.getStructureId(), treeItem);
671        if (isModelGroup) {
672            m_modelGroupRoot.addChild(treeItem);
673        } else {
674            m_modelPageRoot.addChild(treeItem);
675        }
676    }
677
678    /**
679     * Ensures the given item is visible in the viewport.<p>
680     *
681     * @param item the item to see
682     */
683    public void ensureVisible(CmsSitemapTreeItem item) {
684
685        // open the tree
686        CmsTreeItem ti = item.getParentItem();
687        while (ti != null) {
688            ti.setOpen(true);
689            ti = ti.getParentItem();
690        }
691        // scroll
692        CmsDomUtil.ensureVisible(RootPanel.getBodyElement(), item.getElement(), 200);
693    }
694
695    /**
696     * Returns the controller.<p>
697     *
698     * @return the controller
699     */
700    public CmsSitemapController getController() {
701
702        return m_controller;
703    }
704
705    /**
706     * Returns the editor mode.<p>
707     *
708     * @return the editor mode
709     */
710    public EditorMode getEditorMode() {
711
712        return m_editorMode;
713    }
714
715    /**
716     * Returns the icon class for the given entry depending on the editor mode.<p>
717     *
718     * @param entry the entry to get the icon for
719     *
720     * @return the icon CSS class
721     */
722    public String getIconForEntry(CmsClientSitemapEntry entry) {
723
724        String result;
725        if (isNavigationMode()) {
726            if (m_controller.isDetailPage(entry.getId())) {
727                result = m_controller.getDetailPageInfo(entry.getId()).getIconClasses();
728            } else {
729                result = entry.getNavModeIcon();
730            }
731        } else {
732            result = entry.getVfsModeIcon();
733        }
734        return result;
735    }
736
737    /**
738     * Gets the model page bean with the given structure id.<p>
739     *
740     * @param id a structure id
741     *
742     * @return the model page bean with the given id
743     */
744    public CmsModelPageEntry getModelPageEntry(CmsUUID id) {
745
746        return m_modelPageData.get(id);
747    }
748
749    /**
750     * Gets the list of descendants of a path and splits it into two lists, one containing the sitemap entries whose children have
751     * already been loaded, and those whose children haven't been loaded.<p>
752     *
753     * @param path the path for which the open and closed descendants should be returned
754     *
755     * @return a pair whose first and second components are lists of open and closed descendant entries of the path, respectively
756     */
757    public CmsPair<List<CmsClientSitemapEntry>, List<CmsClientSitemapEntry>> getOpenAndClosedDescendants(String path) {
758
759        List<CmsClientSitemapEntry> descendants = m_controller.getLoadedDescendants(path);
760        List<CmsClientSitemapEntry> openDescendants = new ArrayList<CmsClientSitemapEntry>();
761        List<CmsClientSitemapEntry> closedDescendants = new ArrayList<CmsClientSitemapEntry>();
762        for (CmsClientSitemapEntry entry : descendants) {
763            CmsSitemapTreeItem treeItem = getTreeItem(entry.getSitePath());
764            List<CmsClientSitemapEntry> listToAddTo = treeItem.isLoaded() ? openDescendants : closedDescendants;
765            listToAddTo.add(entry);
766        }
767        return new CmsPair<List<CmsClientSitemapEntry>, List<CmsClientSitemapEntry>>(
768            openDescendants,
769            closedDescendants);
770
771    }
772
773    /**
774     * Gets the sitemap toolbar.<p>
775     *
776     * @return the sitemap toolbar
777     */
778    public CmsSitemapToolbar getToolbar() {
779
780        return m_toolbar;
781    }
782
783    /**
784     * Returns the tree.<p>
785     *
786     * @return the tree
787     */
788    public CmsLazyTree<CmsSitemapTreeItem> getTree() {
789
790        return m_tree;
791    }
792
793    /**
794     * Returns the tree entry with the given path.<p>
795     *
796     * @param entryId the id of the sitemap entry
797     *
798     * @return the tree entry with the given path, or <code>null</code> if not found
799     */
800    public CmsSitemapTreeItem getTreeItem(CmsUUID entryId) {
801
802        return m_treeItems.get(entryId);
803    }
804
805    /**
806     * Returns the tree entry with the given path.<p>
807     *
808     * @param path the path to look for
809     *
810     * @return the tree entry with the given path, or <code>null</code> if not found
811     */
812    public CmsSitemapTreeItem getTreeItem(String path) {
813
814        CmsSitemapData data = m_controller.getData();
815        CmsClientSitemapEntry root = data.getRoot();
816        String rootSitePath = root.getSitePath();
817        String remainingPath = path.substring(rootSitePath.length());
818
819        CmsSitemapTreeItem result = getRootItem();
820
821        String[] names = CmsStringUtil.splitAsArray(remainingPath, "/");
822        for (String name : names) {
823            if (CmsStringUtil.isEmptyOrWhitespaceOnly(name)) {
824                continue;
825            }
826            result = (CmsSitemapTreeItem)result.getChild(name);
827            if (result == null) {
828                return null;
829            }
830        }
831        return result;
832
833    }
834
835    /**
836     * Highlights the sitemap entry with the given path.<p>
837     *
838     * @param sitePath the sitemap path of the entry to highlight
839     */
840    public void highlightPath(String sitePath) {
841
842        openItemsOnPath(sitePath);
843        CmsSitemapTreeItem item = getTreeItem(sitePath);
844        if (item != null) {
845            item.highlightTemporarily(
846                1500,
847                isLastPage(item.getSitemapEntry()) ? Background.YELLOW : Background.DEFAULT);
848        }
849    }
850
851    /**
852     * Returns the disabled state of the given model page entry.<p>
853     *
854     * @param id the entry id
855     *
856     * @return the disabled state
857     */
858    public boolean isDisabledModelPageEntry(CmsUUID id) {
859
860        boolean result = false;
861        if (m_modelPageTreeItems.containsKey(id)) {
862            result = m_modelPageTreeItems.get(id).isDisabled();
863        } else if (m_parentModelPageTreeItems.containsKey(id)) {
864            result = m_parentModelPageTreeItems.get(id).isDisabled();
865        }
866        return result;
867    }
868
869    /**
870     * Returns if the current sitemap editor mode is galleries.<p>
871     *
872     * @return <code>true</code> if the current sitemap editor mode is galleries
873     */
874    public boolean isGalleryMode() {
875
876        return EditorMode.galleries == m_editorMode;
877    }
878
879    /**
880     * Returns if the given entry is a model group page.<p>
881     *
882     * @param entryId the entry id
883     *
884     * @return <code>true</code> if the given entry is a model group page
885     */
886    public boolean isModelGroupEntry(CmsUUID entryId) {
887
888        return m_modelPageTreeItems.containsKey(entryId) && m_modelPageTreeItems.get(entryId).isModelGroup();
889    }
890
891    /**
892     * Returns if the given entry is a template model page.<p>
893     *
894     * @param entryId the entry id
895     *
896     * @return <code>true</code> if the given entry is a template model page
897     */
898    public boolean isModelPageEntry(CmsUUID entryId) {
899
900        return m_modelPageData.containsKey(entryId);
901    }
902
903    /**
904     * Checks if the sitemap editor is in 'model page mode'.<p>
905     *
906     * @return true if we are in model page view
907     */
908    public boolean isModelPageMode() {
909
910        return EditorMode.modelpages == m_editorMode;
911    }
912
913    /**
914     * Returns if the current sitemap editor mode is navigation.<p>
915     *
916     * @return <code>true</code> if the current sitemap editor mode is navigation
917     */
918    public boolean isNavigationMode() {
919
920        return EditorMode.navigation == m_editorMode;
921    }
922
923    /**
924     * Returns if the given entry is a template model page inherited from the parent configuration.<p>
925     *
926     * @param entryId the entry id
927     *
928     * @return <code>true</code> if the given entry is a template model page
929     */
930    public boolean isParentModelPageEntry(CmsUUID entryId) {
931
932        return m_parentModelPageTreeItems.containsKey(entryId);
933    }
934
935    /**
936     * Returns true if we are not currently displaying the navigation or VFS tree.<p>
937     *
938     * @return true if we are not currently in navigation or VFS mode
939     */
940    public boolean isSpecialMode() {
941
942        return isGalleryMode() || (EditorMode.modelpages == m_editorMode);
943    }
944
945    /**
946     * Performs necessary async actions before actually setting the mode.<p>
947     *
948     * @param mode the mode
949     */
950    public void onBeforeSetEditorMode(final EditorMode mode) {
951
952        EditorMode oldMode = m_editorMode;
953        if ((oldMode == EditorMode.categories) && ((mode == EditorMode.vfs) || (mode == EditorMode.navigation))) {
954            final CmsRpcAction<Void> action = new CmsRpcAction<Void>() {
955
956                @Override
957                public void execute() {
958
959                    start(200, true);
960                    getController().refreshRoot(this);
961                }
962
963                @Override
964                protected void onResponse(Void result) {
965
966                    stop(false);
967                    setEditorMode(mode);
968                }
969            };
970            action.execute();
971        } else {
972            setEditorMode(mode);
973        }
974
975    }
976
977    /**
978     * @see org.opencms.ade.sitemap.client.control.I_CmsSitemapChangeHandler#onChange(org.opencms.ade.sitemap.client.control.CmsSitemapChangeEvent)
979     */
980    public void onChange(CmsSitemapChangeEvent changeEvent) {
981
982        CmsSitemapChange change = changeEvent.getChange();
983        switch (change.getChangeType()) {
984            case undelete:
985                return;
986            case delete:
987                CmsSitemapTreeItem item = getTreeItem(change.getEntryId());
988                item.getParentItem().removeChild(item);
989                break;
990            case create:
991                CmsClientSitemapEntry newEntry = m_controller.getEntryById(change.getEntryId());
992                CmsSitemapTreeItem newItem = createSitemapItem(newEntry);
993                getTreeItem(change.getParentId()).insertChild(newItem, newEntry.getPosition());
994                break;
995            case bumpDetailPage:
996                updateDetailPageView(m_controller.getEntryById(change.getEntryId()));
997                updateAll(m_controller.getEntryById(change.getEntryId()));
998                break;
999            case modify:
1000                if (change.hasChangedPosition() || change.hasNewParent()) {
1001                    CmsClientSitemapEntry entry = m_controller.getEntryById(change.getEntryId());
1002                    CmsSitemapTreeItem moveEntry = getTreeItem(change.getEntryId());
1003                    CmsSitemapTreeItem sourceParent = (CmsSitemapTreeItem)moveEntry.getParentItem();
1004                    getTree().setAnimationEnabled(false);
1005                    sourceParent.removeChild(moveEntry);
1006                    CmsSitemapTreeItem destParent = change.hasNewParent()
1007                    ? getTreeItem(change.getParentId())
1008                    : sourceParent;
1009                    if (entry.getPosition() < destParent.getChildCount()) {
1010                        destParent.insertChild(moveEntry, entry.getPosition());
1011                    } else {
1012                        destParent.addChild(moveEntry);
1013                    }
1014                    updateAll(entry);
1015                    ensureVisible(moveEntry);
1016                    getTree().setAnimationEnabled(true);
1017                    break;
1018                }
1019                //$FALL-THROUGH$
1020            case remove:
1021                updateAll(m_controller.getEntryById(change.getEntryId()));
1022                break;
1023            default:
1024        }
1025        if (m_editorMode == EditorMode.galleries) {
1026            applyChangeToGalleryTree(changeEvent);
1027        }
1028        if (m_editorMode == EditorMode.modelpages) {
1029            applyChangeToModelPages(changeEvent);
1030        }
1031    }
1032
1033    /**
1034     * @see org.opencms.ade.sitemap.client.control.I_CmsSitemapLoadHandler#onLoad(org.opencms.ade.sitemap.client.control.CmsSitemapLoadEvent)
1035     */
1036    public void onLoad(CmsSitemapLoadEvent event) {
1037
1038        CmsSitemapTreeItem target = getTreeItem(event.getEntry().getId());
1039        if (target != null) {
1040            if (event.getEntry().isSubSitemapType()) {
1041                return;
1042            }
1043            target.getTree().setAnimationEnabled(false);
1044            target.clearChildren();
1045            for (CmsClientSitemapEntry child : event.getEntry().getSubEntries()) {
1046                CmsSitemapTreeItem childItem = createSitemapItem(child);
1047                target.addChild(childItem);
1048            }
1049            target.onFinishLoading();
1050            target.getTree().setAnimationEnabled(true);
1051            if (event.isSetOpen()) {
1052                target.setOpen(true);
1053            }
1054        }
1055        m_controller.recomputeProperties();
1056    }
1057
1058    /**
1059     * @see com.google.gwt.core.client.EntryPoint#onModuleLoad()
1060     */
1061    @Override
1062    public void onModuleLoad() {
1063
1064        super.onModuleLoad();
1065        CmsBroadcastTimer.start();
1066        m_instance = this;
1067
1068        initVaadin();
1069        RootPanel rootPanel = RootPanel.get();
1070
1071        m_editorMode = EditorMode.navigation;
1072        // init
1073        I_CmsSitemapLayoutBundle.INSTANCE.sitemapCss().ensureInjected();
1074        I_CmsSitemapLayoutBundle.INSTANCE.clipboardCss().ensureInjected();
1075        I_CmsSitemapLayoutBundle.INSTANCE.sitemapItemCss().ensureInjected();
1076        I_CmsSitemapLayoutBundle.INSTANCE.propertiesCss().ensureInjected();
1077        I_CmsImageBundle.INSTANCE.buttonCss().ensureInjected();
1078        I_CmsLayoutBundle.INSTANCE.attributeEditorCss().ensureInjected();
1079
1080        rootPanel.addStyleName(I_CmsSitemapLayoutBundle.INSTANCE.sitemapCss().root());
1081        m_treeItems = new HashMap<CmsUUID, CmsSitemapTreeItem>();
1082        // controller
1083        m_controller = new CmsSitemapController();
1084
1085        if (m_controller.getData() == null) {
1086            CmsErrorDialog dialog = new CmsErrorDialog(Messages.get().key(Messages.GUI_ERROR_ON_SITEMAP_LOAD_0), null);
1087            dialog.center();
1088            return;
1089        }
1090        m_controller.addChangeHandler(this);
1091        m_controller.addLoadHandler(this);
1092
1093        // toolbar
1094        m_toolbar = new CmsSitemapToolbar(m_controller);
1095        m_toolbar.setAppTitle(Messages.get().key(Messages.GUI_SITEMAP_0));
1096        rootPanel.add(m_toolbar);
1097        CmsSitemapInfo info = m_controller.getData().getSitemapInfo();
1098        // header
1099        CmsInfoHeader header = new CmsInfoHeader(
1100            info.getTitle(),
1101            info.getDescription(),
1102            info.getSiteHost(),
1103            info.getSiteLocale(),
1104            m_controller.getData().getRoot().getVfsModeIcon());
1105
1106        FlowPanel headerContainer = new FlowPanel();
1107        m_headerContainer = headerContainer;
1108        m_header = header;
1109        FlowPanel localeHeaderContainer = new FlowPanel();
1110        headerContainer.add(m_header);
1111        headerContainer.add(localeHeaderContainer);
1112        localeHeaderContainer.getElement().setId(CmsGwtConstants.ID_LOCALE_HEADER_CONTAINER);
1113        headerContainer.addStyleName(I_CmsSitemapLayoutBundle.INSTANCE.sitemapCss().headerContainer());
1114
1115        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(m_controller.getData().getParentSitemap())) {
1116            CmsPushButton goToParentButton = new CmsPushButton();
1117            goToParentButton.setImageClass(I_CmsButton.EDIT_UP_SMALL);
1118            goToParentButton.setTitle(Messages.get().key(Messages.GUI_HOVERBAR_PARENT_0));
1119            goToParentButton.setButtonStyle(ButtonStyle.FONT_ICON, null);
1120            goToParentButton.addClickHandler(new ClickHandler() {
1121
1122                /**
1123                 * @see com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt.event.dom.client.ClickEvent)
1124                 */
1125                public void onClick(ClickEvent event) {
1126
1127                    getController().gotoParentSitemap();
1128                }
1129            });
1130            header.addButtonTopRight(goToParentButton);
1131        }
1132
1133        rootPanel.add(headerContainer);
1134        final FlowPanel page = new FlowPanel();
1135        m_page = page;
1136        page.setStyleName(I_CmsSitemapLayoutBundle.INSTANCE.sitemapCss().page());
1137        page.addStyleName(I_CmsSitemapLayoutBundle.INSTANCE.generalCss().cornerAll());
1138        rootPanel.add(page);
1139        // initial content
1140        final Label loadingLabel = new Label(
1141            org.opencms.gwt.client.Messages.get().key(org.opencms.gwt.client.Messages.GUI_LOADING_0));
1142        page.add(loadingLabel);
1143
1144        // initialize the tree
1145        m_openHandler = new TreeOpenHandler();
1146        m_tree = new CmsLazyTree<CmsSitemapTreeItem>(m_openHandler);
1147
1148        m_inNavigationStyle = new CmsStyleVariable(m_tree);
1149        m_inNavigationStyle.setValue(I_CmsSitemapLayoutBundle.INSTANCE.sitemapItemCss().navMode());
1150        if (m_controller.isEditable()) {
1151            // enable drag'n drop
1152            CmsDNDHandler dndHandler = new CmsDNDHandler(new CmsSitemapDNDController(m_controller, m_toolbar));
1153            dndHandler.addTarget(m_tree);
1154            m_tree.setDNDHandler(dndHandler);
1155            m_tree.setDropEnabled(true);
1156            m_tree.setDNDTakeAll(true);
1157        }
1158        m_tree.truncate(TM_SITEMAP, 920);
1159        m_tree.setAnimationEnabled(true);
1160        page.add(m_tree);
1161
1162        m_galleryTree = new CmsTree<CmsGalleryTreeItem>();
1163        m_modelPageTree = new CmsTree<CmsModelPageTreeItem>();
1164        m_galleryTree.addStyleName(I_CmsSitemapLayoutBundle.INSTANCE.sitemapItemCss().galleriesMode());
1165        m_modelPageTree.addStyleName(I_CmsSitemapLayoutBundle.INSTANCE.sitemapItemCss().modelPageMode());
1166
1167        m_categoryTree = new CmsTree<CmsTreeItem>();
1168
1169        m_galleryTreeItems = new HashMap<CmsUUID, CmsGalleryTreeItem>();
1170        m_galleryTypeItems = new HashMap<String, CmsGalleryTreeItem>();
1171        page.add(m_galleryTree);
1172        page.add(m_modelPageTree);
1173        page.add(m_categoryTree);
1174        page.add(m_localeComparison);
1175        m_localeComparison.setVisible(false);
1176        m_localeComparison.getElement().setId(CmsGwtConstants.ID_LOCALE_COMPARISON);
1177        m_noGalleriesLabel = new Label();
1178        m_noGalleriesLabel.setStyleName(I_CmsLayoutBundle.INSTANCE.generalCss().textMedium());
1179        m_noGalleriesLabel.getElement().setInnerHTML(Messages.get().key(Messages.GUI_NO_GALLERIES_AVAILABLE_0));
1180        m_noGalleriesLabel.getElement().getStyle().setDisplay(Display.NONE);
1181        page.add(m_noGalleriesLabel);
1182
1183        CmsClientSitemapEntry root = m_controller.getData().getRoot();
1184        m_rootItem = createSitemapItem(root);
1185        m_rootItem.onFinishLoading();
1186        m_rootItem.setOpen(true);
1187        m_tree.addItem(m_rootItem);
1188        // draw tree items
1189        Scheduler.get().scheduleDeferred(new ScheduledCommand() {
1190
1191            public void execute() {
1192
1193                initiateTreeItems(page, loadingLabel);
1194            }
1195        });
1196        setEditorMode(m_controller.getData().getEditorMode());
1197        CmsScriptCallbackHelper localeComparePropertyEditorCallback = new CmsScriptCallbackHelper() {
1198
1199            @Override
1200            public void run() {
1201
1202                JsArrayString args = m_arguments.cast();
1203                String id = args.get(0);
1204                String rootId = args.get(1);
1205                CmsSitemapView.getInstance().getController().openPropertyDialogForVaadin(
1206                    new CmsUUID(id),
1207                    new CmsUUID(rootId));
1208            }
1209        };
1210        localeComparePropertyEditorCallback.installCallbackOnWindow(CmsGwtConstants.LOCALECOMPARE_EDIT_PROPERTIES);
1211
1212    }
1213
1214    /**
1215     * Removes deleted entry widget reference.<p>
1216     *
1217     * @param entry the entry being deleted
1218     */
1219    public void removeDeleted(CmsClientSitemapEntry entry) {
1220
1221        for (CmsClientSitemapEntry child : entry.getSubEntries()) {
1222            removeDeleted(child);
1223        }
1224        m_treeItems.remove(entry.getId());
1225    }
1226
1227    /**
1228     * Sets the editor mode.<p>
1229     *
1230     * @param editorMode the editor mode to set
1231     */
1232    public void setEditorMode(EditorMode editorMode) {
1233
1234        if (editorMode != m_editorMode) {
1235            EditorMode oldEditorMode = m_editorMode;
1236            m_editorMode = editorMode;
1237            m_controller.setEditorModeInSession(m_editorMode);
1238            if (oldEditorMode == EditorMode.compareLocales) {
1239                // reload sitemap editor because we may need to move to a different site
1240                reloadForLocaleCompareRoot(m_editorMode);
1241                return;
1242            }
1243            switch (m_editorMode) {
1244                case galleries:
1245                    m_tree.getElement().getStyle().setDisplay(Display.NONE);
1246                    setGalleriesVisible(true);
1247                    m_toolbar.setNewGalleryEnabled(false, "");
1248                    setCategoriesVisible(false);
1249                    setModelPagesVisible(false);
1250                    setHeaderVisible(true);
1251                    m_localeComparison.setVisible(false);
1252                    m_toolbar.setClipboardEnabled(false, Messages.get().key(Messages.GUI_TOOLBAR_CLIPBOARD_DISABLE_0));
1253                    getController().loadGalleries();
1254                    break;
1255                case navigation:
1256                    m_tree.getElement().getStyle().clearDisplay();
1257                    setGalleriesVisible(false);
1258                    setModelPagesVisible(false);
1259                    setCategoriesVisible(false);
1260                    setHeaderVisible(true);
1261                    m_localeComparison.setVisible(false);
1262                    m_toolbar.setNewEnabled(true, null);
1263
1264                    m_inNavigationStyle.setValue(I_CmsSitemapLayoutBundle.INSTANCE.sitemapItemCss().navMode());
1265                    break;
1266                case vfs:
1267                    m_tree.getElement().getStyle().clearDisplay();
1268                    setGalleriesVisible(false);
1269                    setCategoriesVisible(false);
1270                    setHeaderVisible(true);
1271                    m_localeComparison.setVisible(false);
1272                    m_toolbar.setNewEnabled(false, Messages.get().key(Messages.GUI_TOOLBAR_NEW_DISABLE_0));
1273                    m_toolbar.setClipboardEnabled(true, null);
1274                    m_inNavigationStyle.setValue(I_CmsSitemapLayoutBundle.INSTANCE.sitemapItemCss().vfsMode());
1275                    setModelPagesVisible(false);
1276                    break;
1277                case modelpages:
1278                    m_tree.getElement().getStyle().setDisplay(Display.NONE);
1279                    setGalleriesVisible(false);
1280                    setCategoriesVisible(false);
1281                    setModelPagesVisible(true);
1282                    setHeaderVisible(true);
1283                    m_localeComparison.setVisible(false);
1284                    m_toolbar.setNewEnabled(false, Messages.get().key(Messages.GUI_TOOLBAR_NEW_DISABLE_0));
1285                    m_toolbar.setClipboardEnabled(false, Messages.get().key(Messages.GUI_TOOLBAR_CLIPBOARD_DISABLE_0));
1286                    getController().loadModelPages();
1287
1288                    break;
1289                case categories:
1290                    m_tree.getElement().getStyle().setDisplay(Display.NONE);
1291                    setGalleriesVisible(false);
1292                    setModelPagesVisible(false);
1293                    setCategoriesVisible(true);
1294                    setHeaderVisible(true);
1295                    m_localeComparison.setVisible(false);
1296                    m_toolbar.setNewEnabled(false, Messages.get().key(Messages.GUI_TOOLBAR_NEW_DISABLE_0));
1297                    m_toolbar.setClipboardEnabled(false, Messages.get().key(Messages.GUI_TOOLBAR_CLIPBOARD_DISABLE_0));
1298                    getController().loadCategories(false, null);
1299                    break;
1300                case compareLocales:
1301                    m_tree.getElement().getStyle().setDisplay(Display.NONE);
1302                    setGalleriesVisible(false);
1303                    setModelPagesVisible(false);
1304                    setCategoriesVisible(false);
1305                    setHeaderVisible(false);
1306                    m_toolbar.setNewEnabled(false, Messages.get().key(Messages.GUI_TOOLBAR_NEW_DISABLE_0));
1307                    m_toolbar.setClipboardEnabled(false, Messages.get().key(Messages.GUI_TOOLBAR_CLIPBOARD_DISABLE_0));
1308                    m_localeComparison.setVisible(true);
1309                    m_controller.getData().getRoot().getId();
1310                    CmsJsUtil.callNamedFunctionWithString(
1311                        CmsGwtConstants.CALLBACK_REFRESH_LOCALE_COMPARISON,
1312                        "" + m_controller.getData().getRoot().getId());
1313                    break;
1314
1315                default:
1316
1317            }
1318            // check if the tree has been drawn yet
1319            getRootItem().updateEditorMode();
1320            m_toolbar.setMode(editorMode);
1321        }
1322    }
1323
1324    /**
1325     * Sets the visibility of the normal siteamp header (the header can be hidden so that the Vaadin code can display its own header).<p>
1326     *
1327     * @param visible true if the normal header should be visible
1328     */
1329    public void setHeaderVisible(boolean visible) {
1330
1331        String style = I_CmsSitemapLayoutBundle.INSTANCE.sitemapCss().headerContainerVaadinMode();
1332        if (visible) {
1333            m_headerContainer.removeStyleName(style);
1334        } else {
1335            m_headerContainer.addStyleName(style);
1336        }
1337    }
1338
1339    /**
1340     * Updates the detail page view for a given changed entry.<p>
1341     *
1342     * @param entry the entry which was changed
1343     */
1344    public void updateDetailPageView(CmsClientSitemapEntry entry) {
1345
1346        CmsDetailPageTable detailPageTable = m_controller.getDetailPageTable();
1347        List<CmsUUID> idsToUpdate = new ArrayList<CmsUUID>();
1348        if (m_controller.isDetailPage(entry)) {
1349            idsToUpdate.add(entry.getId());
1350            idsToUpdate.addAll(detailPageTable.getAllIds());
1351        }
1352        updateEntriesById(idsToUpdate);
1353    }
1354
1355    /**
1356     * Updates the entries whose id is in the given list of ids.<p>
1357     *
1358     * @param ids a list of sitemap entry ids
1359     */
1360    public void updateEntriesById(Collection<CmsUUID> ids) {
1361
1362        Map<CmsUUID, CmsClientSitemapEntry> entries = m_controller.getEntriesById(ids);
1363        for (CmsClientSitemapEntry entry : entries.values()) {
1364            CmsSitemapTreeItem item = CmsSitemapTreeItem.getItemById(entry.getId());
1365            item.updateEntry(entry);
1366        }
1367    }
1368
1369    /**
1370     * Updates the disabled state for the given model page.<p>
1371     *
1372     * @param entryId the model page id
1373     * @param disabled the disabled state
1374     */
1375    public void updateModelPageDisabledState(CmsUUID entryId, boolean disabled) {
1376
1377        if (m_modelPageTreeItems.containsKey(entryId)) {
1378            m_modelPageTreeItems.get(entryId).setDisabled(disabled);
1379        } else if (m_parentModelPageTreeItems.containsKey(entryId)) {
1380            m_parentModelPageTreeItems.get(entryId).setDisabled(disabled);
1381        }
1382    }
1383
1384    /**
1385     * Makes sure corresponding sitemap entries are loaded when the gallery tree is opened.<p>
1386     *
1387     * @param parent the parent gallery tree item
1388     */
1389    protected void ensureEntriesLoaded(CmsGalleryTreeItem parent) {
1390
1391        CmsList<? extends I_CmsListItem> children = parent.getChildren();
1392        Set<String> parentPaths = new HashSet<String>();
1393        for (Widget listItem : children) {
1394            CmsGalleryTreeItem treeItem = (CmsGalleryTreeItem)listItem;
1395            CmsUUID entryId = treeItem.getEntryId();
1396            if (m_controller.getEntryById(entryId) == null) {
1397                parentPaths.add(CmsResource.getParentFolder(treeItem.getSitePath()));
1398            }
1399        }
1400        for (String parentPath : parentPaths) {
1401            m_controller.loadPath(parentPath);
1402        }
1403    }
1404
1405    /**
1406     * Gets the sitemap tree item widget which represents the root of the current sitemap.<p>
1407     *
1408     * @return the root sitemap tree item widget
1409     */
1410    protected CmsSitemapTreeItem getRootItem() {
1411
1412        return m_rootItem;
1413    }
1414
1415    /**
1416     * Shows/hides the category view.<p>
1417     *
1418     * @param visible true if the categories should be made visible
1419     */
1420    protected void setCategoriesVisible(boolean visible) {
1421
1422        setElementVisible(m_categoryTree, visible);
1423
1424    }
1425
1426    /**
1427     * Shows or hides the element for a widget.<p>
1428     *
1429     * @param widget the widget to show or hide
1430     * @param visible true if the widget should be shown
1431     */
1432    protected void setElementVisible(Widget widget, boolean visible) {
1433
1434        if (visible) {
1435            widget.getElement().getStyle().clearDisplay();
1436        } else {
1437            widget.getElement().getStyle().setDisplay(Display.NONE);
1438        }
1439
1440    }
1441
1442    /**
1443     * Shows or hides the tree for the gallery mode.<p>
1444     *
1445     * @param visible true if the tree should be shown
1446     */
1447    protected void setGalleriesVisible(boolean visible) {
1448
1449        if (visible) {
1450            m_noGalleriesLabel.getElement().getStyle().clearDisplay();
1451            m_galleryTree.getElement().getStyle().clearDisplay();
1452        } else {
1453            m_galleryTree.getElement().getStyle().setDisplay(Display.NONE);
1454            m_noGalleriesLabel.getElement().getStyle().setDisplay(Display.NONE);
1455
1456        }
1457    }
1458
1459    /**
1460     * Shows or hides the tree for the model page mode.<p>
1461     *
1462     * @param visible true if the model pages should be shown
1463     */
1464    protected void setModelPagesVisible(boolean visible) {
1465
1466        setElementVisible(m_modelPageTree, visible);
1467    }
1468
1469    /**
1470     * Recursively creates the widget tree for a given category bean.<p>
1471     *
1472     * @param entry the category bean
1473     *
1474     * @return the widget for that category bean, with widgets for its descendants attached
1475     */
1476    CmsCategoryTreeItem createCategoryTreeItem(CmsCategoryTreeEntry entry) {
1477
1478        CmsCategoryTreeItem result = new CmsCategoryTreeItem(entry);
1479        for (CmsCategoryTreeEntry child : entry.getChildren()) {
1480            result.addChild(createCategoryTreeItem(child));
1481        }
1482        return result;
1483    }
1484
1485    /**
1486     * Builds the tree items initially.<p>
1487     *
1488     * @param page the page
1489     * @param loadingLabel the loading label, will be removed when finished
1490     */
1491    void initiateTreeItems(FlowPanel page, Label loadingLabel) {
1492
1493        CmsSitemapTreeItem rootItem = getRootItem();
1494        m_controller.addPropertyUpdateHandler(new CmsStatusIconUpdateHandler());
1495        m_controller.recomputeProperties();
1496        rootItem.updateSitePath();
1497        // check if editable
1498        if (!m_controller.isEditable()) {
1499            // notify user
1500            CmsNotification.get().sendSticky(
1501                CmsNotification.Type.WARNING,
1502                Messages.get().key(Messages.GUI_NO_EDIT_NOTIFICATION_1, m_controller.getData().getNoEditReason()));
1503        }
1504        String openPath = m_controller.getData().getOpenPath();
1505        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(openPath)) {
1506            m_openHandler.setInitializing(true);
1507            openItemsOnPath(openPath);
1508            m_openHandler.setInitializing(false);
1509        }
1510        page.remove(loadingLabel);
1511    }
1512
1513    /**
1514     * Adds the gallery tree items to the parent.<p>
1515     *
1516     * @param parent the parent item
1517     * @param galleries the gallery folder entries
1518     */
1519    private void addGalleryEntries(CmsGalleryTreeItem parent, List<CmsGalleryFolderEntry> galleries) {
1520
1521        for (CmsGalleryFolderEntry galleryFolder : galleries) {
1522            CmsGalleryTreeItem folderItem = createGalleryFolderItem(galleryFolder);
1523            parent.addChild(folderItem);
1524            m_galleryTreeItems.put(galleryFolder.getStructureId(), folderItem);
1525            addGalleryEntries(folderItem, galleryFolder.getSubGalleries());
1526        }
1527    }
1528
1529    /**
1530     * Applies the given change to the gallery view.<p>
1531     *
1532     * @param changeEvent the change event
1533     */
1534    private void applyChangeToGalleryTree(CmsSitemapChangeEvent changeEvent) {
1535
1536        CmsSitemapChange change = changeEvent.getChange();
1537        switch (change.getChangeType()) {
1538            case delete:
1539                CmsGalleryTreeItem deleteItem = m_galleryTreeItems.get(change.getEntryId());
1540                if (deleteItem != null) {
1541                    deleteItem.removeFromParent();
1542                }
1543
1544                break;
1545
1546            case undelete:
1547            case create:
1548                String typeName = m_controller.getGalleryType(
1549                    Integer.valueOf(change.getNewResourceTypeId())).getResourceType();
1550                if (typeName != null) {
1551                    CmsGalleryFolderEntry galleryFolder = new CmsGalleryFolderEntry();
1552                    galleryFolder.setSitePath(change.getSitePath());
1553                    galleryFolder.setResourceType(typeName);
1554                    galleryFolder.setStructureId(change.getEntryId());
1555                    galleryFolder.setOwnProperties(change.getOwnProperties());
1556                    galleryFolder.setIconClasses(
1557                        m_controller.getGalleryType(Integer.valueOf(change.getNewResourceTypeId())).getBigIconClasses());
1558                    CmsGalleryTreeItem folderItem = new CmsGalleryTreeItem(galleryFolder);
1559                    CmsSitemapHoverbar.installOn(m_controller, folderItem, galleryFolder.getStructureId());
1560                    m_galleryTypeItems.get(typeName).addChild(folderItem);
1561                    m_galleryTreeItems.put(galleryFolder.getStructureId(), folderItem);
1562                }
1563                break;
1564
1565            case modify:
1566                CmsGalleryTreeItem changeItem = m_galleryTreeItems.get(change.getEntryId());
1567                if (changeItem != null) {
1568                    CmsListItemWidget widget = changeItem.getListItemWidget();
1569                    for (CmsPropertyModification mod : change.getPropertyChanges()) {
1570                        if (mod.getName().equals(CmsClientProperty.PROPERTY_TITLE)) {
1571                            widget.setTitleLabel(mod.getValue());
1572                        }
1573                    }
1574                    String oldPath = changeItem.getSitePath();
1575                    if ((change.getName() != null) && !oldPath.endsWith("/" + change.getName())) {
1576                        String newPath = CmsResource.getParentFolder(oldPath) + change.getName() + "/";
1577                        changeItem.updateSitePath(newPath);
1578                    }
1579
1580                }
1581                break;
1582            case bumpDetailPage:
1583            case clipboardOnly:
1584            case remove:
1585            default:
1586                // nothing to do
1587        }
1588    }
1589
1590    /**
1591     * Applies sitemap changes to the model page mode.<p>
1592     *
1593     * @param changeEvent the change event to apply to the model page mode
1594     */
1595    private void applyChangeToModelPages(CmsSitemapChangeEvent changeEvent) {
1596
1597        CmsSitemapChange change = changeEvent.getChange();
1598        switch (change.getChangeType()) {
1599            case delete:
1600
1601                CmsModelPageTreeItem deleteItem = m_modelPageTreeItems.get(change.getEntryId());
1602                if (deleteItem != null) {
1603                    deleteItem.removeFromParent();
1604                }
1605
1606                break;
1607
1608            case undelete:
1609            case create:
1610                String typeName = m_controller.getGalleryType(
1611                    Integer.valueOf(change.getNewResourceTypeId())).getResourceType();
1612                if (typeName != null) {
1613                    CmsModelPageEntry modelPage = new CmsModelPageEntry();
1614                    modelPage.setSitePath(change.getSitePath());
1615                    modelPage.setResourceType(typeName);
1616                    modelPage.setStructureId(change.getEntryId());
1617                    modelPage.setOwnProperties(change.getOwnProperties());
1618                    CmsModelPageTreeItem folderItem = new CmsModelPageTreeItem(modelPage, false, false);
1619                    CmsSitemapHoverbar.installOn(m_controller, folderItem, modelPage.getStructureId());
1620                    m_modelPageRoot.addChild(folderItem);
1621                    m_modelPageTreeItems.put(modelPage.getStructureId(), folderItem);
1622                }
1623                break;
1624
1625            case modify:
1626                CmsModelPageTreeItem changeItem = m_modelPageTreeItems.get(change.getEntryId());
1627                if (changeItem != null) {
1628                    CmsListItemWidget widget = changeItem.getListItemWidget();
1629                    for (CmsPropertyModification mod : change.getPropertyChanges()) {
1630                        if (mod.getName().equals(CmsClientProperty.PROPERTY_TITLE)) {
1631                            widget.setTitleLabel(mod.getValue());
1632                        }
1633                    }
1634                    String oldPath = changeItem.getSitePath();
1635                    if ((change.getName() != null) && !oldPath.endsWith("/" + change.getName())) {
1636                        String newPath = CmsResource.getParentFolder(oldPath) + change.getName() + "/";
1637                        changeItem.updateSitePath(newPath);
1638                    }
1639
1640                }
1641                break;
1642            case bumpDetailPage:
1643            case clipboardOnly:
1644            case remove:
1645            default:
1646                // nothing to do
1647        }
1648    }
1649
1650    /**
1651     * Create a gallery folder tree item.<p>
1652     *
1653     * @param galleryFolder the gallery folder
1654     *
1655     * @return the tree item
1656     */
1657    private CmsGalleryTreeItem createGalleryFolderItem(CmsGalleryFolderEntry galleryFolder) {
1658
1659        CmsGalleryTreeItem folderItem = new CmsGalleryTreeItem(galleryFolder);
1660
1661        CmsSitemapHoverbar.installOn(
1662            m_controller,
1663            folderItem,
1664            galleryFolder.getStructureId(),
1665            galleryFolder.getSitePath(),
1666            true);
1667        return folderItem;
1668    }
1669
1670    /**
1671     * Helper method to get all sitemap tree items from the root to a given path.<p>
1672     *
1673     * For example, if the root item has the site path '/root/', and the value of path is
1674     * '/root/a/b/', the sitemap tree items corresponding to '/root/', '/root/a/' and '/root/a/b'
1675     * will be returned (in that order).<p>
1676     *
1677     * @param path the path for which the sitemap tree items should be returned
1678     *
1679     * @return the sitemap tree items on the path
1680     */
1681    private List<CmsSitemapTreeItem> getItemsOnPath(String path) {
1682
1683        List<CmsSitemapTreeItem> result = new ArrayList<CmsSitemapTreeItem>();
1684
1685        CmsSitemapData data = m_controller.getData();
1686        CmsClientSitemapEntry root = data.getRoot();
1687        String rootSitePath = root.getSitePath();
1688        String remainingPath = path.substring(rootSitePath.length());
1689
1690        CmsSitemapTreeItem currentItem = getRootItem();
1691        result.add(currentItem);
1692
1693        String[] names = CmsStringUtil.splitAsArray(remainingPath, "/");
1694        for (String name : names) {
1695            if (currentItem == null) {
1696                break;
1697            }
1698            if (CmsStringUtil.isEmptyOrWhitespaceOnly(name)) {
1699                continue;
1700            }
1701            currentItem = (CmsSitemapTreeItem)currentItem.getChild(name);
1702            if (currentItem != null) {
1703                result.add(currentItem);
1704            }
1705        }
1706        return result;
1707    }
1708
1709    /**
1710     * Initializes the Vaadin part of the sitemap editor.<p>
1711     */
1712    private native void initVaadin() /*-{
1713        $wnd.initVaadin();
1714    }-*/;
1715
1716    /**
1717     * Checks if the given entry represents the last opened page.<p>
1718     *
1719     * @param entry the entry to check
1720     *
1721     * @return <code>true</code> if the given entry is the last opened page
1722     */
1723    private boolean isLastPage(CmsClientSitemapEntry entry) {
1724
1725        return ((entry.isInNavigation() && (entry.getId().toString().equals(m_controller.getData().getReturnCode())))
1726            || ((entry.getDefaultFileId() != null)
1727                && entry.getDefaultFileId().toString().equals(m_controller.getData().getReturnCode())));
1728    }
1729
1730    /**
1731     * Opens all sitemap tree items on a path, except the last one.<p>
1732     *
1733     * @param path the path for which all intermediate sitemap items should be opened
1734     */
1735    private void openItemsOnPath(String path) {
1736
1737        List<CmsSitemapTreeItem> itemsOnPath = getItemsOnPath(path);
1738        for (CmsSitemapTreeItem item : itemsOnPath) {
1739            item.setOpen(true);
1740        }
1741    }
1742
1743    /**
1744     * Reloads the sitemap editor for the currently selected locale when leaving locale compare mode.<p>
1745     *
1746     * @param editorMode the new editor mode
1747     */
1748    private void reloadForLocaleCompareRoot(final EditorMode editorMode) {
1749
1750        final String localeRootIdStr = CmsJsUtil.getAttributeString(
1751            CmsJsUtil.getWindow(),
1752            CmsGwtConstants.VAR_LOCALE_ROOT);
1753        CmsRpcAction<String> action = new CmsRpcAction<String>() {
1754
1755            @SuppressWarnings("synthetic-access")
1756            @Override
1757            public void execute() {
1758
1759                start(0, false);
1760                m_controller.getService().prepareReloadSitemap(new CmsUUID(localeRootIdStr), editorMode, this);
1761            }
1762
1763            @Override
1764            protected void onResponse(String result) {
1765
1766                stop(false);
1767                if (result != null) {
1768                    Window.Location.assign(result);
1769                } else {
1770                    Window.Location.reload();
1771                }
1772            }
1773        };
1774        action.execute();
1775
1776    }
1777
1778    /**
1779     * Updates the entry and it's children's view.<p>
1780     *
1781     * @param entry the entry to update
1782     */
1783    private void updateAll(CmsClientSitemapEntry entry) {
1784
1785        CmsSitemapTreeItem item = getTreeItem(entry.getId());
1786        if (item != null) {
1787            item.updateEntry(entry);
1788            item.updateSitePath(entry.getSitePath());
1789            for (CmsClientSitemapEntry child : entry.getSubEntries()) {
1790                updateAll(child);
1791            }
1792        }
1793    }
1794}