org.opencms.ui.apps.CmsFileExplorer.java Source code

Java tutorial

Introduction

Here is the source code for org.opencms.ui.apps.CmsFileExplorer.java

Source

/*
 * This library is part of OpenCms -
 * the Open Source Content Management System
 *
 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com)
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * For further information about Alkacon Software, please see the
 * company website: http://www.alkacon.com
 *
 * For further information about OpenCms, please see the
 * project website: http://www.opencms.org
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package org.opencms.ui.apps;

import org.opencms.db.CmsUserSettings;
import org.opencms.file.CmsObject;
import org.opencms.file.CmsProject;
import org.opencms.file.CmsResource;
import org.opencms.file.CmsResourceFilter;
import org.opencms.file.CmsVfsResourceNotFoundException;
import org.opencms.main.CmsException;
import org.opencms.main.CmsLog;
import org.opencms.main.OpenCms;
import org.opencms.security.CmsPermissionSet;
import org.opencms.ui.A_CmsUI;
import org.opencms.ui.CmsVaadinUtils;
import org.opencms.ui.FontOpenCms;
import org.opencms.ui.I_CmsDialogContext;
import org.opencms.ui.I_CmsDialogContext.ContextType;
import org.opencms.ui.I_CmsUpdateListener;
import org.opencms.ui.actions.CmsCopyDialogAction;
import org.opencms.ui.actions.CmsPropertiesDialogAction;
import org.opencms.ui.actions.I_CmsWorkplaceAction;
import org.opencms.ui.components.A_CmsFocusShortcutListener;
import org.opencms.ui.components.CmsErrorDialog;
import org.opencms.ui.components.CmsFileTable;
import org.opencms.ui.components.CmsResourceIcon;
import org.opencms.ui.components.CmsResourceTableProperty;
import org.opencms.ui.components.CmsToolBar;
import org.opencms.ui.components.CmsUploadButton;
import org.opencms.ui.components.CmsUploadButton.I_UploadListener;
import org.opencms.ui.components.I_CmsWindowCloseListener;
import org.opencms.ui.components.OpenCmsTheme;
import org.opencms.ui.components.extensions.CmsUploadAreaExtension;
import org.opencms.ui.contextmenu.CmsResourceContextMenuBuilder;
import org.opencms.ui.dialogs.CmsCopyMoveDialog;
import org.opencms.ui.dialogs.CmsDeleteDialog;
import org.opencms.ui.dialogs.CmsNewDialog;
import org.opencms.util.CmsStringUtil;
import org.opencms.util.CmsUUID;
import org.opencms.workplace.explorer.CmsResourceUtil;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.logging.Log;

import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.vaadin.data.Item;
import com.vaadin.data.Property.ValueChangeEvent;
import com.vaadin.data.Property.ValueChangeListener;
import com.vaadin.data.util.HierarchicalContainer;
import com.vaadin.data.util.IndexedContainer;
import com.vaadin.event.Action;
import com.vaadin.event.FieldEvents.BlurEvent;
import com.vaadin.event.FieldEvents.FocusEvent;
import com.vaadin.event.FieldEvents.TextChangeEvent;
import com.vaadin.event.FieldEvents.TextChangeListener;
import com.vaadin.event.ItemClickEvent;
import com.vaadin.event.ItemClickEvent.ItemClickListener;
import com.vaadin.event.ShortcutAction;
import com.vaadin.event.ShortcutAction.KeyCode;
import com.vaadin.event.dd.DragAndDropEvent;
import com.vaadin.event.dd.DropHandler;
import com.vaadin.event.dd.acceptcriteria.AcceptCriterion;
import com.vaadin.event.dd.acceptcriteria.ServerSideCriterion;
import com.vaadin.navigator.ViewChangeListener;
import com.vaadin.server.Page;
import com.vaadin.server.Sizeable.Unit;
import com.vaadin.shared.ui.combobox.FilteringMode;
import com.vaadin.ui.AbstractSelect.AbstractSelectTargetDetails;
import com.vaadin.ui.Button;
import com.vaadin.ui.Button.ClickEvent;
import com.vaadin.ui.Button.ClickListener;
import com.vaadin.ui.ComboBox;
import com.vaadin.ui.CssLayout;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.HorizontalSplitPanel;
import com.vaadin.ui.Notification;
import com.vaadin.ui.Table;
import com.vaadin.ui.Table.TableDragMode;
import com.vaadin.ui.TextField;
import com.vaadin.ui.Tree;
import com.vaadin.ui.Tree.CollapseEvent;
import com.vaadin.ui.Tree.CollapseListener;
import com.vaadin.ui.Tree.ExpandEvent;
import com.vaadin.ui.Tree.ExpandListener;
import com.vaadin.ui.Tree.ItemStyleGenerator;
import com.vaadin.ui.Tree.TreeDragMode;
import com.vaadin.ui.UI;
import com.vaadin.ui.themes.ValoTheme;

/**
 * The file explorer app.<p>
 */
public class CmsFileExplorer
        implements I_CmsWorkplaceApp, I_CmsCachableApp, ViewChangeListener, I_CmsWindowCloseListener,
        I_CmsHasShortcutActions, I_CmsContextProvider, CmsFileTable.I_FolderSelectHandler {

    /** The drop handler for copy/move operations. */
    public class ExplorerDropHandler implements DropHandler {

        /** The serial version id. */
        private static final long serialVersionUID = 5392136127699472654L;

        /** The copy move action. */
        final I_CmsWorkplaceAction m_copyMoveAction = new CmsCopyDialogAction();

        /**
         * @see com.vaadin.event.dd.DropHandler#drop(com.vaadin.event.dd.DragAndDropEvent)
         */
        public void drop(DragAndDropEvent dragEvent) {

            try {
                CmsExplorerDialogContext context = getContext(dragEvent);
                if (m_copyMoveAction.isActive(context)) {
                    CmsCopyMoveDialog dialog = new CmsCopyMoveDialog(context,
                            CmsCopyMoveDialog.DialogMode.copy_and_move);
                    dialog.setTargetFolder(getTargetId(dragEvent));
                    context.start(CmsVaadinUtils.getMessageText(org.opencms.ui.Messages.GUI_DIALOGTITLE_COPYMOVE_0),
                            dialog);
                }
            } catch (Exception e) {
                LOG.error("Moving resource failed", e);
            }

        }

        /**
         * @see com.vaadin.event.dd.DropHandler#getAcceptCriterion()
         */
        public AcceptCriterion getAcceptCriterion() {

            return new ServerSideCriterion() {

                private static final long serialVersionUID = 1L;

                public boolean accept(DragAndDropEvent dragEvent) {

                    try {
                        if (!m_copyMoveAction.isActive(getContext(dragEvent))) {
                            return false;
                        }
                    } catch (CmsException e) {
                        LOG.error("Drag an drop evaluation failed", e);
                        return false;
                    }
                    CmsUUID targetId = getTargetId(dragEvent);
                    return mayDrop(targetId);
                }
            };
        }

        /**
         * Returns the drag target id.<p>
         *
         * @param dragEvent the drag event
         *
         * @return the drag target id
         */
        protected CmsUUID getTargetId(DragAndDropEvent dragEvent) {

            CmsUUID targetId = null;
            if (dragEvent.getTargetDetails() instanceof AbstractSelectTargetDetails) {
                AbstractSelectTargetDetails target = (AbstractSelectTargetDetails) dragEvent.getTargetDetails();
                targetId = (CmsUUID) target.getItemIdOver();
            }
            try {
                CmsObject cms = A_CmsUI.getCmsObject();
                CmsResource target = cms.readResource(targetId);
                if (target.isFile()) {
                    targetId = null;
                }
            } catch (CmsException e) {
                targetId = null;
                LOG.debug("Checking drop target failed, use current folder.", e);
            }

            if (targetId == null) {
                targetId = getCurrentFolder();
            }
            return targetId;
        }

        /**
         * Evaluates if a drop on the given target is allowed.<p>
         *
         * @param targetId the target id
         *
         * @return <code>true</code> if the resources may be dropped to the given target
         */
        protected boolean mayDrop(CmsUUID targetId) {

            boolean result = false;
            try {
                CmsObject cms = A_CmsUI.getCmsObject();
                CmsResource target = cms.readResource(targetId);
                result = cms.hasPermissions(target, CmsPermissionSet.ACCESS_WRITE, false,
                        CmsResourceFilter.ONLY_VISIBLE_NO_DELETED);
            } catch (Exception e) {
                LOG.debug("Checking folder write permissions failed", e);
            }
            return result;
        }

        /**
         * Returns the dialog context to use.<p>
         *
         * @param dragEvent the drag event
         *
         * @return the dialog context
         *
         * @throws CmsException if reading the drag resource fails
         */
        CmsExplorerDialogContext getContext(DragAndDropEvent dragEvent) throws CmsException {

            List<CmsResource> resources;
            if ((dragEvent.getTransferable().getSourceComponent() instanceof Table)
                    && !m_fileTable.getSelectedResources().isEmpty()) {
                resources = m_fileTable.getSelectedResources();
            } else {
                CmsObject cms = A_CmsUI.getCmsObject();
                CmsUUID sourceId = (CmsUUID) dragEvent.getTransferable().getData("itemId");
                CmsResource source = cms.readResource(sourceId);
                resources = Collections.singletonList(source);
            }
            CmsExplorerDialogContext context = new CmsExplorerDialogContext(ContextType.fileTable, m_fileTable,
                    CmsFileExplorer.this, resources);
            return context;
        }
    }

    /**
     * File tree expand listener.<p>
     */
    public class TreeExpandListener implements ExpandListener {

        /** The serial version id. */
        private static final long serialVersionUID = 1L;
        /**
         * The path fragment being opened.
         * Will override folder visibility in case the the target path is visible to the user.
         **/
        private String m_openPathFragment;

        /**
         * @see com.vaadin.ui.Tree.ExpandListener#nodeExpand(com.vaadin.ui.Tree.ExpandEvent)
         */
        public void nodeExpand(ExpandEvent event) {

            selectTreeItem((CmsUUID) event.getItemId());
            readTreeLevel((CmsUUID) event.getItemId(), m_openPathFragment);
        }

        /**
         * Sets the open path fragment.<p>
         *
         * @param openPathFragment the open path fragment
         */
        public void setOpenPathFragment(String openPathFragment) {

            m_openPathFragment = openPathFragment;
        }
    }

    /** Bean representing the file explorer navigation substate. */
    static class StateBean {

        /** Current folder. */
        private String m_folder;

        /** Project id. */
        private String m_projectId;

        /** The site root. */
        private String m_siteRoot;

        /**
         * Creates a new state bean.<p>
         *
         * @param siteRoot the site root
         * @param folder the folder
         * @param projectId the project id
         */
        public StateBean(String siteRoot, String folder, String projectId) {
            m_siteRoot = siteRoot;
            m_folder = folder;
            m_projectId = projectId;
            if ("".equals(m_siteRoot)) {
                m_siteRoot = "/";
            }
        }

        /**
         * Parses the state bean from a string.<p>
         *
         * @param state the state string
         * @return the state bean
         */
        public static StateBean parse(String state) {

            List<String> fields = CmsStringUtil.splitAsList(state, "!!");
            if (fields.size() >= 3) {
                String projectId = fields.get(0);
                String siteRoot = fields.get(1);
                String folder = fields.get(2);
                return new StateBean(siteRoot, folder, projectId);
            } else {
                return new StateBean(null, null, null);
            }
        }

        /**
         * Converts state bean to a string.<p>
         *
         * @return the string format of the state
         */
        public String asString() {

            String result = m_projectId + "!!" + m_siteRoot + "!!" + m_folder + "!!";
            return result;
        }

        /**
         * Returns the folderId.<p>
         *
         * @return the folderId
         */
        public String getFolder() {

            return m_folder;
        }

        /**
         * Returns the projectId.<p>
         *
         * @return the projectId
         */
        public String getProjectId() {

            return m_projectId;
        }

        /**
         * Returns the siteRoot.<p>
         *
         * @return the siteRoot
         */
        public String getSiteRoot() {

            return m_siteRoot;
        }
    }

    /** The in line editable resource properties. */
    public static final Collection<CmsResourceTableProperty> INLINE_EDIT_PROPERTIES = Arrays.asList(
            CmsResourceTableProperty.PROPERTY_RESOURCE_NAME, CmsResourceTableProperty.PROPERTY_TITLE,
            CmsResourceTableProperty.PROPERTY_NAVIGATION_TEXT, CmsResourceTableProperty.PROPERTY_COPYRIGHT,
            CmsResourceTableProperty.PROPERTY_CACHE);

    /** The initial split position between folder tree and file table. */
    public static final int LAYOUT_SPLIT_POSITION = 399;

    /** The opened paths session attribute name. */
    public static final String OPENED_PATHS = "explorer-opened-paths";

    /** Site selector caption property. */
    public static final String SITE_CAPTION = "site_caption";

    /** Site selector site root property. */
    public static final String SITE_ROOT = "site_root";

    /** The state separator string. */
    public static final String STATE_SEPARATOR = "!!";

    /** Threshold for updating the complete folder after file changes. */
    public static final int UPDATE_FOLDER_THRESHOLD = 200;

    /** Logger instance for this class. */
    static final Log LOG = CmsLog.getLog(CmsFileExplorer.class);

    /** The delete shortcut. */
    private static final Action ACTION_DELETE = new ShortcutAction("Del", ShortcutAction.KeyCode.DELETE, null);

    /** The open parent folder shortcut. */
    private static final Action ACTION_FOLDER_UP = new ShortcutAction("Alt+ArrowUp",
            ShortcutAction.KeyCode.ARROW_UP, new int[] { ShortcutAction.ModifierKey.ALT });

    /** The edit properties shortcut. */
    private static final Action ACTION_PROPERTIES = new ShortcutAction("Alt+Enter", ShortcutAction.KeyCode.ENTER,
            new int[] { ShortcutAction.ModifierKey.ALT });

    /** The rename shortcut. */
    private static final Action ACTION_RENAME = new ShortcutAction("F2", ShortcutAction.KeyCode.F2, null);

    /** The select all shortcut. */
    private static final Action ACTION_SELECT_ALL = new ShortcutAction("Ctrl+A", ShortcutAction.KeyCode.A,
            new int[] { ShortcutAction.ModifierKey.CTRL });

    /** The switch online shortcut. */
    private static final Action ACTION_SWITCH_ONLINE = new ShortcutAction("Ctrl+O", ShortcutAction.KeyCode.O,
            new int[] { ShortcutAction.ModifierKey.CTRL });

    /** The files and folder resource filter. */
    private static final CmsResourceFilter FILES_N_FOLDERS = CmsResourceFilter.ONLY_VISIBLE;

    /** The folders resource filter. */
    private static final CmsResourceFilter FOLDERS = CmsResourceFilter.ONLY_VISIBLE_NO_DELETED.addRequireFolder();

    /** The serial version id. */
    private static final long serialVersionUID = 1L;

    /** The UI context. */
    protected I_CmsAppUIContext m_appContext;

    /** Saved explorer state used by dialogs after they have finished. */
    protected String m_savedExplorerState = "";

    /** The table containing the contents of the current folder. */
    CmsFileTable m_fileTable;

    /** The info path. */
    TextField m_infoPath;

    /** The explorer shortcuts. */
    Map<Action, Runnable> m_shortcutActions;

    /** The bread crumb click listener. */
    private ClickListener m_crumbListener;

    /** The path bread crumb container. */
    private CssLayout m_crumbs;

    /** The currently viewed folder. */
    private CmsUUID m_currentFolder;

    /** The current app state. */
    private String m_currentState;

    /** The tree expand listener. */
    private TreeExpandListener m_expandListener;

    /** The folder tree. */
    private Tree m_fileTree;

    /** The first visible file table item index. */
    private int m_firstVisibleTableItemIndex;

    /** The last context menu resources. */
    private List<CmsResource> m_lastDialogContextResources;

    /** The quick launch location cache. */
    private CmsQuickLaunchLocationCache m_locationCache;

    /** The new button. */
    private Button m_newButton;

    /** The publish button. */
    private Button m_publishButton;

    /** The search field. */
    private TextField m_searchField;

    /** the currently selected file tree folder, may be null. */
    private CmsUUID m_selectTreeFolder;

    /** The site selector. */
    private ComboBox m_siteSelector;

    /** The folder tree data container. */
    private HierarchicalContainer m_treeContainer;

    /** The upload drop area extension. */
    private CmsUploadAreaExtension m_uploadArea;

    /** The upload button. */
    private CmsUploadButton m_uploadButton;

    /**
     * Constructor.<p>
     */
    public CmsFileExplorer() {
        m_shortcutActions = new HashMap<Action, Runnable>();
        m_shortcutActions.put(ACTION_DELETE, new Runnable() {

            public void run() {

                if (!m_fileTable.getSelectedIds().isEmpty()) {
                    I_CmsDialogContext context1 = getDialogContext();
                    context1.start("Delete", new CmsDeleteDialog(context1));
                }
            }
        });

        m_shortcutActions.put(ACTION_FOLDER_UP, new Runnable() {

            public void run() {

                showParentFolder();
            }
        });

        m_shortcutActions.put(ACTION_PROPERTIES, new Runnable() {

            public void run() {

                I_CmsWorkplaceAction propAction = new CmsPropertiesDialogAction();
                I_CmsDialogContext context = getDialogContext();
                if (propAction.getVisibility(context).isActive()) {
                    propAction.executeAction(context);
                }
            }
        });

        m_shortcutActions.put(ACTION_RENAME, new Runnable() {

            public void run() {

                CmsExplorerDialogContext context = getDialogContext();
                if (context.isPropertyEditable(CmsResourceTableProperty.PROPERTY_RESOURCE_NAME)) {
                    context.editProperty(CmsResourceTableProperty.PROPERTY_RESOURCE_NAME);
                }
            }
        });

        m_shortcutActions.put(ACTION_SELECT_ALL, new Runnable() {

            public void run() {

                m_fileTable.selectAll();
            }
        });

        m_shortcutActions.put(ACTION_SWITCH_ONLINE, new Runnable() {

            public void run() {

                toggleOnlineOffline();
            }
        });

        m_fileTable = new CmsFileTable(this);
        m_fileTable.setSizeFull();
        m_fileTable.setMenuBuilder(new CmsResourceContextMenuBuilder());
        m_fileTable.setFolderSelectHandler(this);
        m_uploadArea = new CmsUploadAreaExtension(m_fileTable);
        m_uploadArea.addUploadListener(new I_UploadListener() {

            public void onUploadFinished(List<String> uploadedFiles) {

                updateAll(true);
            }
        });
        m_treeContainer = new HierarchicalContainer();
        addTreeContainerProperties(CmsResourceTableProperty.PROPERTY_RESOURCE_NAME,
                CmsResourceTableProperty.PROPERTY_STATE, CmsResourceTableProperty.PROPERTY_TREE_CAPTION,
                CmsResourceTableProperty.PROPERTY_INSIDE_PROJECT,
                CmsResourceTableProperty.PROPERTY_RELEASED_NOT_EXPIRED, CmsResourceTableProperty.PROPERTY_DISABLED);
        m_fileTree = new Tree();
        m_fileTree.addStyleName(OpenCmsTheme.SIMPLE_DRAG);
        m_fileTree.addStyleName(OpenCmsTheme.FULL_WIDTH_PADDING);
        m_fileTree.setWidth("100%");
        m_fileTree.setContainerDataSource(m_treeContainer);
        //    m_fileTree.setItemIconPropertyId(CmsResourceTableProperty.PROPERTY_TYPE_ICON_RESOURCE);
        m_fileTree.setItemCaptionPropertyId(CmsResourceTableProperty.PROPERTY_TREE_CAPTION);
        //        m_fileTree.setCaptionAsHtml(true);
        m_fileTree.setHtmlContentAllowed(true);
        m_expandListener = new TreeExpandListener();
        m_fileTree.addExpandListener(m_expandListener);
        m_fileTree.addCollapseListener(new CollapseListener() {

            private static final long serialVersionUID = 1L;

            public void nodeCollapse(CollapseEvent event) {

                selectTreeItem((CmsUUID) event.getItemId());
                clearTreeLevel((CmsUUID) event.getItemId());
            }
        });

        m_fileTree.addItemClickListener(new ItemClickListener() {

            private static final long serialVersionUID = 1L;

            public void itemClick(ItemClickEvent event) {

                handleFileTreeClick(event);
            }
        });

        m_fileTree.setItemStyleGenerator(new ItemStyleGenerator() {

            private static final long serialVersionUID = 1L;

            public String getStyle(Tree source, Object itemId) {

                return CmsFileTable.getStateStyle(source.getContainerDataSource().getItem(itemId));
            }
        });
        m_fileTree.addValueChangeListener(new ValueChangeListener() {

            private static final long serialVersionUID = 1L;

            public void valueChange(ValueChangeEvent event) {

                handleFileTreeValueChange();
            }

        });

        m_fileTree.setNullSelectionAllowed(false);

        // init drag and drop
        ExplorerDropHandler handler = new ExplorerDropHandler();
        m_fileTable.setDropHandler(handler);
        m_fileTable.setDragMode(TableDragMode.MULTIROW);
        m_fileTree.setDropHandler(handler);
        m_fileTree.setDragMode(TreeDragMode.NONE);

        m_siteSelector = createSiteSelect(A_CmsUI.getCmsObject());
        m_infoPath = new TextField();
        A_CmsFocusShortcutListener shortcutListener = new A_CmsFocusShortcutListener("Open path", KeyCode.ENTER,
                null) {

            private static final long serialVersionUID = 1L;

            @Override
            public void blur(BlurEvent event) {

                super.blur(event);
                showCrumbs(true);
            }

            @Override
            public void focus(FocusEvent event) {

                super.focus(event);
                showCrumbs(false);
            }

            @Override
            public void handleAction(Object sender, Object target) {

                openPath(m_infoPath.getValue());
            }
        };
        shortcutListener.installOn(m_infoPath);

        m_crumbs = new CssLayout();
        m_crumbs.setPrimaryStyleName(OpenCmsTheme.CRUMBS);
        m_crumbListener = new ClickListener() {

            private static final long serialVersionUID = 1L;

            public void buttonClick(ClickEvent event) {

                openPath((String) event.getButton().getData());
            }
        };

        m_searchField = new TextField();
        m_searchField.setIcon(FontOpenCms.FILTER);
        m_searchField.setInputPrompt(
                Messages.get().getBundle(UI.getCurrent().getLocale()).key(Messages.GUI_EXPLORER_FILTER_0));
        m_searchField.addStyleName(ValoTheme.TEXTFIELD_INLINE_ICON);
        m_searchField.addTextChangeListener(new TextChangeListener() {

            private static final long serialVersionUID = 1L;

            public void textChange(TextChangeEvent event) {

                filterTable(event.getText());

            }
        });

        m_locationCache = CmsQuickLaunchLocationCache.getLocationCache(CmsAppWorkplaceUi.get().getHttpSession());
        String startSite = CmsAppWorkplaceUi.get().getWorkplaceSettings().getUserSettings().getStartSite();
        // remove trailing slashes
        while (startSite.endsWith("/")) {
            startSite = startSite.substring(0, startSite.length() - 1);
        }
        if (m_locationCache.getFileExplorerLocation(startSite) == null) {
            // add the configured start folder for the start site
            String startFolder = CmsAppWorkplaceUi.get().getWorkplaceSettings().getUserSettings().getStartFolder();
            m_locationCache.setFileExplorerLocation(startSite, startFolder);
        }
    }

    /**
     * @see com.vaadin.navigator.ViewChangeListener#afterViewChange(com.vaadin.navigator.ViewChangeListener.ViewChangeEvent)
     */
    public void afterViewChange(ViewChangeEvent event) {

        m_fileTable.setFirstVisibleItemIndex(m_firstVisibleTableItemIndex);
    }

    /**
     * @see com.vaadin.navigator.ViewChangeListener#beforeViewChange(com.vaadin.navigator.ViewChangeListener.ViewChangeEvent)
     */
    public boolean beforeViewChange(ViewChangeEvent event) {

        m_firstVisibleTableItemIndex = m_fileTable.getFirstVisibleItemIndex();

        OpenCms.getWorkplaceAppManager().storeAppSettings(A_CmsUI.getCmsObject(), CmsFileExplorerSettings.class,
                m_fileTable.getTableSettings());
        return true;
    }

    /**
     * Changes to the given site and path.<p>
     *
     * @param siteRoot the site root
     * @param path the path inside the site
     */
    public void changeSite(String siteRoot, String path) {

        changeSite(siteRoot, path, false);

    }

    /**
     * Switches to the requested site.<p>
     *
     * @param siteRoot the site root
     * @param path the folder path to open
     * @param force force the path change, even if we are currently in the same site
     */
    public void changeSite(String siteRoot, String path, boolean force) {

        CmsObject cms = A_CmsUI.getCmsObject();
        String currentSiteRoot = cms.getRequestContext().getSiteRoot();
        if (force || !currentSiteRoot.equals(siteRoot)) {
            CmsAppWorkplaceUi.get().changeSite(siteRoot);
            if (path == null) {
                path = m_locationCache.getFileExplorerLocation(siteRoot);
                if (CmsStringUtil.isEmptyOrWhitespaceOnly(siteRoot)
                        && ((path == null) || !path.startsWith("/system"))) {
                    // switching to the root site and previous root site folder is not below /system
                    // -> stay in the current folder
                    path = m_locationCache.getFileExplorerLocation(currentSiteRoot);
                    if (path != null) {
                        path = CmsStringUtil.joinPaths(currentSiteRoot, path);
                    }
                }
            }
            openPath(path, true);
            m_siteSelector.select(siteRoot);
        }
    }

    /**
     * Gets all ids of resources in current folder.<p>
     *
     * @return the
     */
    public List<CmsUUID> getAllIds() {

        return m_fileTable.getAllIds();
    }

    /**
     * Returns the current folder id.<p>
     *
     * @return the current folder structure id
     */
    public CmsUUID getCurrentFolder() {

        return m_currentFolder;
    }

    /**
     * @see org.opencms.ui.apps.I_CmsContextProvider#getDialogContext()
     */
    public CmsExplorerDialogContext getDialogContext() {

        List<CmsResource> resources = m_fileTable.getSelectedResources();
        m_lastDialogContextResources = resources;
        CmsExplorerDialogContext context = new CmsExplorerDialogContext(ContextType.fileTable, m_fileTable, this,
                resources);
        context.setEditableProperties(INLINE_EDIT_PROPERTIES);
        return context;
    }

    /**
     * @see org.opencms.ui.apps.I_CmsHasShortcutActions#getShortcutActions()
     */
    public Map<Action, Runnable> getShortcutActions() {

        return m_shortcutActions;
    }

    /**
     * @see org.opencms.ui.apps.I_CmsWorkplaceApp#initUI(org.opencms.ui.apps.I_CmsAppUIContext)
     */
    public void initUI(I_CmsAppUIContext context) {

        m_appContext = context;
        m_appContext.setMenuDialogContext(
                new CmsExplorerDialogContext(ContextType.appToolbar, m_fileTable, this, null));
        HorizontalSplitPanel sp = new HorizontalSplitPanel();
        sp.setSizeFull();
        sp.setFirstComponent(m_fileTree);
        CmsFileExplorerSettings settings;
        try {
            settings = OpenCms.getWorkplaceAppManager().getAppSettings(A_CmsUI.getCmsObject(),
                    CmsFileExplorerSettings.class);

            m_fileTable.setTableState(settings);
        } catch (Exception e) {
            LOG.error("Error while reading file explorer settings from user.", e);
        }
        sp.setSecondComponent(m_fileTable);

        sp.setSplitPosition(LAYOUT_SPLIT_POSITION, Unit.PIXELS);

        context.setAppContent(sp);
        context.showInfoArea(true);
        HorizontalLayout inf = new HorizontalLayout();
        inf.setSizeFull();
        inf.setSpacing(true);
        inf.setMargin(true);
        m_siteSelector.setWidth("379px");
        inf.addComponent(m_siteSelector);
        CssLayout crumbWrapper = new CssLayout();
        crumbWrapper.setSizeFull();
        crumbWrapper.setPrimaryStyleName(OpenCmsTheme.CRUMB_WRAPPER);
        crumbWrapper.addComponent(m_crumbs);

        m_infoPath.setWidth("100%");
        crumbWrapper.addComponent(m_infoPath);
        inf.addComponent(crumbWrapper);
        inf.setExpandRatio(crumbWrapper, 1);

        m_searchField.setWidth("200px");
        inf.addComponent(m_searchField);
        context.setAppInfo(inf);

        initToolbarButtons(context);
        m_fileTable.updateColumnWidths(A_CmsUI.get().getPage().getBrowserWindowWidth() - LAYOUT_SPLIT_POSITION);
    }

    /**
     * @see org.opencms.ui.apps.I_CmsCachableApp#isCachable()
     */
    public boolean isCachable() {

        return true;
    }

    /**
     * @see org.opencms.ui.components.CmsFileTable.I_FolderSelectHandler#onFolderSelect(org.opencms.util.CmsUUID)
     */
    public void onFolderSelect(CmsUUID itemId) {

        expandCurrentFolder();
        if (m_fileTree.getItem(itemId) != null) {
            m_fileTree.select(itemId);
        }
        try {
            readFolder(itemId);
        } catch (CmsException e) {
            CmsErrorDialog.showErrorDialog(
                    CmsVaadinUtils.getMessageText(Messages.ERR_EXPLORER_CAN_NOT_READ_RESOURCE_1, itemId), e);
            LOG.error(e.getLocalizedMessage(), e);
        }
    }

    /**
     * @see org.opencms.ui.apps.I_CmsCachableApp#onRestoreFromCache()
     */
    public void onRestoreFromCache() {

        if (m_lastDialogContextResources != null) {
            List<CmsUUID> updateIds = Lists.newArrayList();
            for (CmsResource resource : m_lastDialogContextResources) {
                updateIds.add(resource.getStructureId());
            }
            update(updateIds);
        }

    }

    /**
     * Call if site and or project have been changed.<p>
     *
     * @param project the project
     * @param siteRoot the site root
     */
    public void onSiteOrProjectChange(CmsProject project, String siteRoot) {

        if ((siteRoot != null) && !siteRoot.equals(getSiteRootFromState())) {
            changeSite(siteRoot, null, true);
        } else if ((project != null) && !project.getUuid().equals(getProjectIdFromState())) {
            openPath(getPathFromState(), true);
        }
        m_appContext.updateOnChange();
        setToolbarButtonsEnabled(!CmsAppWorkplaceUi.isOnlineProject());
    }

    /**
     * @see org.opencms.ui.apps.I_CmsWorkplaceApp#onStateChange(java.lang.String)
     */
    public void onStateChange(String state) {

        state = normalizeState(state);
        if ((m_currentState == null) || !m_currentState.equals(state)) {
            m_currentState = state;
            CmsObject cms = A_CmsUI.getCmsObject();
            String siteRoot = getSiteRootFromState();
            String path = getPathFromState();

            CmsUUID projectId = getProjectIdFromState();
            if ((projectId != null) && !cms.getRequestContext().getCurrentProject().getUuid().equals(projectId)) {
                try {
                    CmsProject project = cms.readProject(projectId);
                    cms.getRequestContext().setCurrentProject(project);
                    CmsAppWorkplaceUi.get().getWorkplaceSettings().setProject(project.getUuid());
                } catch (CmsException e) {
                    LOG.warn("Error reading project from history state", e);
                }
                changeSite(siteRoot, path, true);
            } else if ((siteRoot != null) && !siteRoot.equals(cms.getRequestContext().getSiteRoot())) {
                changeSite(siteRoot, path);
            } else {
                openPath(path, true);
            }
        }
    }

    /**
     * @see org.opencms.ui.components.I_CmsWindowCloseListener#onWindowClose()
     */
    public void onWindowClose() {

        OpenCms.getWorkplaceAppManager().storeAppSettings(A_CmsUI.getCmsObject(), CmsFileExplorerSettings.class,
                m_fileTable.getTableSettings());
    }

    /**
     * Fills the file table with the resources from the given path.<p>
     *
     * @param sitePath a folder site path
     */
    public void populateFileTable(String sitePath) {

        CmsObject cms = A_CmsUI.getCmsObject();
        m_searchField.clear();
        m_firstVisibleTableItemIndex = 0;
        try {
            List<CmsResource> folderResources = cms.readResources(sitePath, FILES_N_FOLDERS, false);
            m_fileTable.fillTable(cms, folderResources);
        } catch (CmsException e) {
            CmsErrorDialog.showErrorDialog(e);
            LOG.error(e.getLocalizedMessage(), e);
        }
    }

    /**
     * Updates the table entries with the given ids.<p>
     *
     * @param ids the ids of the table entries to update
     */
    public void update(Collection<CmsUUID> ids) {

        try {
            CmsResource currentFolderRes = A_CmsUI.getCmsObject().readResource(m_currentFolder,
                    CmsResourceFilter.ALL);
            boolean updateFolder = m_fileTable.getItemCount() < UPDATE_FOLDER_THRESHOLD;
            Set<CmsUUID> removeIds = new HashSet<CmsUUID>();
            for (CmsUUID id : ids) {
                boolean remove = false;
                try {
                    CmsResource resource = A_CmsUI.getCmsObject().readResource(id, CmsResourceFilter.ALL);

                    remove = !CmsResource.getParentFolder(resource.getRootPath())
                            .equals(currentFolderRes.getRootPath());

                } catch (CmsVfsResourceNotFoundException e) {
                    remove = true;
                    LOG.debug("Could not read update resource " + id, e);
                }
                if (remove) {
                    removeIds.add(id);
                }

            }
            for (CmsUUID id : ids) {
                if (!updateFolder) {
                    m_fileTable.update(id, removeIds.contains(id));
                }
                updateTree(id);
            }
            if (updateFolder) {
                updateCurrentFolder(removeIds);
            }
            m_fileTable.updateSorting();
            m_fileTable.clearSelection();
        } catch (CmsException e) {
            CmsErrorDialog.showErrorDialog(e);
        }
    }

    /**
     * Updates display for all contents of the current folder.<p>
     *
     * @param clearFilter <code>true</code> to clear the search filter
     */
    public void updateAll(boolean clearFilter) {

        try {
            readFolder(m_currentFolder, clearFilter);
            Set<Object> ids = Sets.newHashSet();
            ids.addAll(m_fileTree.getItemIds());
            for (Object id : ids) {
                updateTree((CmsUUID) id);
            }
        } catch (CmsException e) {
            CmsErrorDialog.showErrorDialog(
                    CmsVaadinUtils.getMessageText(Messages.ERR_EXPLORER_CAN_NOT_READ_RESOURCE_1, m_currentFolder),
                    e);
            LOG.error(e.getLocalizedMessage(), e);
        }
    }

    /**
     * Updates the give tree item.<p>
     *
     * @param cms the cms context
     * @param id the item id
     */
    public void updateResourceInTree(CmsObject cms, CmsUUID id) {

        try {
            CmsResource resource = cms.readResource(id, CmsResourceFilter.IGNORE_EXPIRATION);
            if (resource.isFile()) {
                return;
            }
            // ensuring to only add items belonging to the current site
            if (!resource.getRootPath().startsWith(cms.getRequestContext().getSiteRoot())) {
                m_treeContainer.removeItemRecursively(id);
                return;
            }

            CmsResource parent = cms.readParentFolder(id);
            CmsUUID parentId = null;
            if (parent != null) {
                parentId = parent.getStructureId();
            } else {
                LOG.debug("Parent for resource '" + resource.getRootPath() + "' is null");
            }
            Item resourceItem = m_treeContainer.getItem(id);
            if (resourceItem != null) {
                // use the root path as name in case of the root item
                String resName = parentId == null ? resource.getRootPath() : resource.getName();
                resourceItem.getItemProperty(CmsResourceTableProperty.PROPERTY_RESOURCE_NAME).setValue(resName);
                CmsResourceUtil resUtil = new CmsResourceUtil(cms, resource);
                String iconHTML = CmsResourceIcon.getTreeCaptionHTML(resName, resUtil, null, false);
                resourceItem.getItemProperty(CmsResourceTableProperty.PROPERTY_TREE_CAPTION).setValue(iconHTML);
                resourceItem.getItemProperty(CmsResourceTableProperty.PROPERTY_STATE).setValue(resource.getState());
                if (parentId != null) {
                    m_treeContainer.setParent(resource.getStructureId(), parentId);
                }
            } else {
                addTreeItem(cms, resource, parentId, false, m_treeContainer);
            }
            m_fileTree.markAsDirty();
        } catch (CmsVfsResourceNotFoundException e) {
            m_treeContainer.removeItemRecursively(id);
            LOG.debug(e.getLocalizedMessage(), e);
        } catch (CmsException e) {
            CmsErrorDialog.showErrorDialog(e);
            LOG.error(e.getLocalizedMessage(), e);
        }
    }

    /**
     * Updates the tree items with the given ids.<p>
     *
     * @param id the
     */
    public void updateTree(CmsUUID id) {

        CmsObject cms = A_CmsUI.getCmsObject();
        updateResourceInTree(cms, id);

    }

    /**
     * Clears the given tree level.<p>
     *
     * @param parentId the parent id
     */
    protected void clearTreeLevel(CmsUUID parentId) {

        // create a new list to avoid concurrent modifications
        Collection<?> children = m_treeContainer.getChildren(parentId);
        // may be null when monkey clicking
        if (children != null) {
            List<Object> childIds = new ArrayList<Object>(m_treeContainer.getChildren(parentId));
            for (Object childId : childIds) {
                m_treeContainer.removeItemRecursively(childId);
            }
        }
    }

    /**
     * Reads the given folder.<p>
     *
     * @param folderId the folder id
     *
     * @throws CmsException in case reading the folder fails
     */
    protected void readFolder(CmsUUID folderId) throws CmsException {

        readFolder(folderId, true);
    }

    /**
     * Reads the given folder.<p>
     *
     * @param folderId the folder id
     * @param clearFilter <code>true</code> to clear the search filter
     *
     * @throws CmsException in case reading the folder fails
     */
    protected void readFolder(CmsUUID folderId, boolean clearFilter) throws CmsException {

        CmsObject cms = A_CmsUI.getCmsObject();
        if (clearFilter) {
            m_searchField.clear();
        }
        CmsResource folder = cms.readResource(folderId, FOLDERS);
        m_currentFolder = folderId;
        String folderPath = cms.getSitePath(folder);
        if (OpenCms.getSiteManager().isSharedFolder(cms.getRequestContext().getSiteRoot())) {
            folderPath = folderPath.substring(cms.getRequestContext().getSiteRoot().length());
        }
        setPathInfo(folderPath);
        List<CmsResource> childResources = cms.readResources(folder, FILES_N_FOLDERS, false);
        m_fileTable.fillTable(cms, childResources, clearFilter);
        boolean hasFolderChild = false;
        for (CmsResource child : childResources) {
            if (child.isFolder()) {
                hasFolderChild = true;
                break;
            }
        }
        m_treeContainer.setChildrenAllowed(folderId, hasFolderChild);
        String sitePath = folder.getRootPath().equals(cms.getRequestContext().getSiteRoot() + "/") ? ""
                : folderPath;

        String state = new StateBean(cms.getRequestContext().getSiteRoot(), sitePath,
                cms.getRequestContext().getCurrentProject().getUuid().toString()).asString();

        if (!(state).equals(m_currentState)) {
            m_currentState = state;
            CmsAppWorkplaceUi.get().changeCurrentAppState(m_currentState);
        }
        m_locationCache.setFileExplorerLocation(cms.getRequestContext().getSiteRoot(), sitePath);
        m_uploadButton.setTargetFolder(folder.getRootPath());
        m_uploadArea.setTargetFolder(folder.getRootPath());
        if (!m_fileTree.isExpanded(folderId)) {
            expandCurrentFolder();
        }
        if (!m_fileTree.isSelected(folderId)) {
            m_fileTree.select(folderId);
        }
    }

    /**
     * Updates the current folder and removes the given resource items.<p>
     *
     * @param removeIds the resource item ids to remove
     */
    protected void updateCurrentFolder(Collection<CmsUUID> removeIds) {

        for (CmsUUID removeId : removeIds) {
            m_fileTable.update(removeId, true);
        }
        CmsObject cms = A_CmsUI.getCmsObject();
        try {
            CmsResource folder = cms.readResource(m_currentFolder, FOLDERS);
            List<CmsResource> childResources = cms.readResources(cms.getSitePath(folder), FILES_N_FOLDERS, false);
            for (CmsResource child : childResources) {
                m_fileTable.update(child.getStructureId(), false);
            }

        } catch (CmsException e) {
            CmsErrorDialog.showErrorDialog(e);
            LOG.error(e.getLocalizedMessage(), e);
        }
    }

    /**
     * Expands the currently viewed folder in the tree.<p>
     */
    void expandCurrentFolder() {

        if (m_currentFolder != null) {
            m_treeContainer.setChildrenAllowed(m_currentFolder, true);
            m_fileTree.expandItem(m_currentFolder);
        }
    }

    /**
     * Filters the file table.<p>
     *
     * @param search the search term
     */
    void filterTable(String search) {

        m_fileTable.filterTable(search);
    }

    /**
     * Handles the file tree click events.<p>
     *
     * @param event the event
     */
    void handleFileTreeClick(ItemClickEvent event) {

        Item resourceItem = m_treeContainer.getItem(event.getItemId());
        if ((resourceItem.getItemProperty(CmsResourceTableProperty.PROPERTY_DISABLED).getValue() == null)
                || !((Boolean) resourceItem.getItemProperty(CmsResourceTableProperty.PROPERTY_DISABLED).getValue())
                        .booleanValue()) {
            // don't handle disabled item clicks
            try {
                readFolder((CmsUUID) event.getItemId());
            } catch (CmsException e) {
                CmsErrorDialog.showErrorDialog(CmsVaadinUtils
                        .getMessageText(Messages.ERR_EXPLORER_CAN_NOT_READ_RESOURCE_1, event.getItemId()), e);
                LOG.error(e.getLocalizedMessage(), e);
            }
        }
    }

    /**
     * Handles the file tree value changes.<p>
     */
    void handleFileTreeValueChange() {

        Item resourceItem = m_treeContainer.getItem(m_fileTree.getValue());
        if (resourceItem != null) {
            if ((resourceItem.getItemProperty(CmsResourceTableProperty.PROPERTY_DISABLED).getValue() != null)
                    && ((Boolean) resourceItem.getItemProperty(CmsResourceTableProperty.PROPERTY_DISABLED)
                            .getValue()).booleanValue()) {
                // in case the folder is disabled due to missing visibility, reset the value to the previous one
                m_fileTree.setValue(m_selectTreeFolder);

            } else {
                m_selectTreeFolder = (CmsUUID) m_fileTree.getValue();
            }
        }
    }

    /**
     * Opens the new resource dialog.<p>
     */
    void onClickNew() {

        try {
            CmsObject cms = A_CmsUI.getCmsObject();

            CmsResource folderRes = cms.readResource(m_currentFolder, CmsResourceFilter.IGNORE_EXPIRATION);
            I_CmsDialogContext newDialogContext = getDialogContext();

            CmsNewDialog newDialog = new CmsNewDialog(folderRes, newDialogContext);
            newDialogContext.start(
                    CmsVaadinUtils.getMessageText(org.opencms.ui.Messages.GUI_NEWRESOURCEDIALOG_TITLE_0),
                    newDialog);

        } catch (Exception e) {
            LOG.error(e.getLocalizedMessage(), e);
        }
    }

    /**
     * Opens the given site path.<p>
     *
     * @param path the path
     */
    void openPath(String path) {

        openPath(path, false);
    }

    /**
     * Opens the given site path.<p>
     *
     * @param path the path
     * @param initTree <code>true</code> in case the tree needs to be initialized
     */
    void openPath(String path, boolean initTree) {

        if (path == null) {
            String siteRoot = A_CmsUI.getCmsObject().getRequestContext().getSiteRoot();
            path = m_locationCache.getFileExplorerLocation(siteRoot);
            if (path == null) {
                path = "";
            } else if (OpenCms.getSiteManager().startsWithShared(path)) {
                path = path.substring(siteRoot.length());
            }
        }
        boolean existsPath = CmsStringUtil.isNotEmptyOrWhitespaceOnly(path)
                && A_CmsUI.getCmsObject().existsResource(path, FILES_N_FOLDERS);

        if (path.startsWith("/")) {
            // remove a leading slash to avoid an empty first path fragment
            path = path.substring(1);
        }

        String[] pathItems = path.split("/");

        if (initTree) {
            try {
                initFolderTree(existsPath);
            } catch (CmsException e) {
                CmsErrorDialog.showErrorDialog(
                        CmsVaadinUtils.getMessageText(Messages.ERR_EXPLORER_CAN_NOT_OPEN_PATH_1, path), e);
                LOG.error(e.getLocalizedMessage(), e);
                return;
            }
        }

        Collection<?> rootItems = m_treeContainer.rootItemIds();
        if (rootItems.size() != 1) {
            throw new RuntimeException("Illeagal state, folder tree has " + rootItems.size() + " children");
        }

        CmsUUID folderId = (CmsUUID) rootItems.iterator().next();
        if (initTree) {
            if (existsPath) {
                m_expandListener.setOpenPathFragment(pathItems[0]);
            }
            m_fileTree.expandItem(folderId);
            m_expandListener.setOpenPathFragment(null);
        }
        Collection<?> children = m_treeContainer.getChildren(folderId);
        for (int i = 0; i < pathItems.length; i++) {
            if (CmsStringUtil.isEmptyOrWhitespaceOnly(pathItems[i])) {
                continue;
            }
            CmsUUID level = null;
            if (children != null) {
                for (Object id : children) {
                    if (m_treeContainer.getItem(id).getItemProperty(CmsResourceTableProperty.PROPERTY_RESOURCE_NAME)
                            .getValue().equals(pathItems[i])) {
                        level = (CmsUUID) id;
                        m_expandListener.setOpenPathFragment(
                                existsPath && (i < (pathItems.length - 1)) ? pathItems[i + 1] : null);
                        m_fileTree.expandItem(level);
                        m_expandListener.setOpenPathFragment(null);
                        break;
                    }
                }
            }
            if ((level == null) || level.equals(folderId)) {
                break;
            }
            folderId = level;
            children = m_treeContainer.getChildren(folderId);
        }
        try {
            readFolder(folderId);
        } catch (CmsException e) {
            CmsErrorDialog.showErrorDialog(
                    CmsVaadinUtils.getMessageText(Messages.ERR_EXPLORER_CAN_NOT_OPEN_PATH_1, path), e);
            LOG.error(e.getLocalizedMessage(), e);
            return;
        }
    }

    /**
     * Reads the given tree level.<p>
     *
     * @param parentId the parent id
     * @param openPathFragment the open path fragment
     */
    void readTreeLevel(CmsUUID parentId, String openPathFragment) {

        CmsObject cms = A_CmsUI.getCmsObject();
        boolean openedFragment = false;
        try {
            CmsResource parent = cms.readResource(parentId, CmsResourceFilter.IGNORE_EXPIRATION);
            String folderPath = cms.getSitePath(parent);
            List<CmsResource> folderResources = cms.readResources(folderPath, FOLDERS, false);

            // sets the parent to leaf mode, in case no child folders are present
            m_treeContainer.setChildrenAllowed(parentId, !folderResources.isEmpty());

            for (CmsResource resource : folderResources) {
                addTreeItem(cms, resource, parentId, false, m_treeContainer);
                openedFragment = openedFragment || resource.getName().equals(openPathFragment);
            }
            if (!openedFragment && (openPathFragment != null)) {
                CmsResource resource = cms.readResource(CmsStringUtil.joinPaths(folderPath, openPathFragment),
                        CmsResourceFilter.IGNORE_EXPIRATION);
                addTreeItem(cms, resource, parentId, true, m_treeContainer);
            }

            m_fileTree.markAsDirtyRecursive();
        } catch (CmsException e) {
            CmsErrorDialog.showErrorDialog(e);
            LOG.error(e.getLocalizedMessage(), e);
        }
    }

    /**
     * Selects the item with the given id.<p>
     *
     * @param itemId the item id
     */
    void selectTreeItem(CmsUUID itemId) {

        m_fileTree.select(itemId);
    }

    /**
     * Shows and hides the path bread crumb.<p>
     *
     * @param show <code>true</code> to show the bread crumb
     */
    void showCrumbs(boolean show) {

        if (show) {
            m_crumbs.removeStyleName(OpenCmsTheme.HIDDEN);
        } else {
            m_crumbs.addStyleName(OpenCmsTheme.HIDDEN);
        }
    }

    /**
     * Shows the parent folder, if available.<p>
     */
    void showParentFolder() {

        CmsUUID parentId = (CmsUUID) m_treeContainer.getParent(m_currentFolder);
        if (parentId != null) {
            Item resourceItem = m_treeContainer.getItem(parentId);
            if ((resourceItem.getItemProperty(CmsResourceTableProperty.PROPERTY_DISABLED).getValue() == null)
                    || !((Boolean) resourceItem.getItemProperty(CmsResourceTableProperty.PROPERTY_DISABLED)
                            .getValue()).booleanValue()) {
                // don't open disabled parent folders
                try {
                    readFolder(parentId);
                    m_fileTree.select(parentId);
                } catch (CmsException e) {
                    CmsErrorDialog.showErrorDialog(
                            CmsVaadinUtils.getMessageText(Messages.ERR_EXPLORER_CAN_NOT_READ_RESOURCE_1, parentId),
                            e);
                    LOG.error(e.getLocalizedMessage(), e);
                }
            }
        }
    }

    /**
     * Toggles between the online and the offline project.<p>
     */
    void toggleOnlineOffline() {

        CmsObject cms = A_CmsUI.getCmsObject();
        CmsProject targetProject = null;
        if (cms.getRequestContext().getCurrentProject().isOnlineProject()) {
            targetProject = A_CmsUI.get().getLastOfflineProject();
            if (targetProject == null) {
                CmsUserSettings userSettings = new CmsUserSettings(cms);
                try {
                    CmsProject project = cms.readProject(userSettings.getStartProject());
                    if (!project.isOnlineProject() && OpenCms.getOrgUnitManager()
                            .getAllAccessibleProjects(cms, project.getOuFqn(), false).contains(project)) {
                        targetProject = project;
                    }
                } catch (CmsException e) {
                    LOG.debug("Error reading user start project.", e);
                }
                if (targetProject == null) {
                    List<CmsProject> availableProjects = CmsVaadinUtils.getAvailableProjects(cms);
                    for (CmsProject project : availableProjects) {
                        if (!project.isOnlineProject()) {
                            targetProject = project;
                            break;
                        }
                    }
                }
            }
        } else {
            try {
                targetProject = cms.readProject(CmsProject.ONLINE_PROJECT_ID);
            } catch (CmsException e) {
                LOG.debug("Error reading online project.", e);
            }
        }
        if (targetProject != null) {
            A_CmsUI.get().changeProject(targetProject);
            onSiteOrProjectChange(targetProject, null);
            Notification.show(
                    CmsVaadinUtils.getMessageText(Messages.GUI_SWITCHED_TO_PROJECT_1, targetProject.getName()));
        }
    }

    /**
     * Adds the given properties to the tree container.<p>
     *
     * @param properties the properties tom add
     */
    private void addTreeContainerProperties(CmsResourceTableProperty... properties) {

        for (CmsResourceTableProperty property : properties) {
            m_treeContainer.addContainerProperty(property, property.getColumnType(), property.getDefaultValue());
        }
    }

    /**
     * Adds an item to the folder tree.<p>
     *
     * @param cms the cms context
     * @param resource the folder resource
     * @param parentId the parent folder id
     * @param disabled in case the item is disabled, used for non visible parent folders
     * @param container the data container
     */
    private void addTreeItem(CmsObject cms, CmsResource resource, CmsUUID parentId, boolean disabled,
            HierarchicalContainer container) {

        CmsResourceUtil resUtil = new CmsResourceUtil(cms, resource);
        Item resourceItem = container.getItem(resource.getStructureId());
        if (resourceItem == null) {
            resourceItem = container.addItem(resource.getStructureId());
        }

        // use the root path as name in case of the root item
        String resName = parentId == null ? resource.getRootPath() : resource.getName();
        resourceItem.getItemProperty(CmsResourceTableProperty.PROPERTY_RESOURCE_NAME).setValue(resName);
        resourceItem.getItemProperty(CmsResourceTableProperty.PROPERTY_STATE).setValue(resource.getState());

        resourceItem.getItemProperty(CmsResourceTableProperty.PROPERTY_INSIDE_PROJECT)
                .setValue(Boolean.valueOf(resUtil.isInsideProject()));
        resourceItem.getItemProperty(CmsResourceTableProperty.PROPERTY_RELEASED_NOT_EXPIRED)
                .setValue(Boolean.valueOf(resUtil.isReleasedAndNotExpired()));
        String iconHTML = CmsResourceIcon.getTreeCaptionHTML(resName, resUtil, null, false);

        resourceItem.getItemProperty(CmsResourceTableProperty.PROPERTY_TREE_CAPTION).setValue(iconHTML);
        if (disabled) {
            resourceItem.getItemProperty(CmsResourceTableProperty.PROPERTY_DISABLED).setValue(Boolean.TRUE);
        }
        if (parentId != null) {
            container.setChildrenAllowed(parentId, true);
            container.setParent(resource.getStructureId(), parentId);
        }
    }

    /**
     * Creates the site selector combo box.<p>
     *
     * @param cms the current cms context
     *
     * @return the combo box
     */
    private ComboBox createSiteSelect(CmsObject cms) {

        final IndexedContainer availableSites = CmsVaadinUtils.getAvailableSitesContainer(cms, SITE_CAPTION);
        ComboBox combo = new ComboBox(null, availableSites);
        combo.setTextInputAllowed(true);
        combo.setNullSelectionAllowed(false);
        combo.setWidth("200px");
        combo.setInputPrompt(
                Messages.get().getBundle(UI.getCurrent().getLocale()).key(Messages.GUI_EXPLORER_CLICK_TO_EDIT_0));
        combo.setItemCaptionPropertyId(SITE_CAPTION);
        combo.select(cms.getRequestContext().getSiteRoot());
        combo.setFilteringMode(FilteringMode.CONTAINS);
        combo.addValueChangeListener(new ValueChangeListener() {

            private static final long serialVersionUID = 1L;

            public void valueChange(ValueChangeEvent event) {

                String value = (String) event.getProperty().getValue();
                if (availableSites.containsId(value)) {
                    changeSite(value, null);
                    m_appContext.updateOnChange();
                    availableSites.removeAllContainerFilters();
                }
            }
        });
        if (Page.getCurrent().getBrowserWindowHeight() > 650) {
            combo.setPageLength(20);
        }
        return combo;
    }

    /**
     * Returns the site path from the current state.<p>
     *
     * @return the site path
     */
    private String getPathFromState() {

        return StateBean.parse(m_currentState).getFolder();
    }

    /**
     * Returns the project id from the current state.<p>
     *
     * @return the project id
     */
    private CmsUUID getProjectIdFromState() {

        String projectIdStr = StateBean.parse(m_currentState).getProjectId();
        if (CmsUUID.isValidUUID(projectIdStr)) {
            return new CmsUUID(projectIdStr);

        }
        return null;

    }

    /**
     * Returns the site root from the current state.<p>
     *
     * @return the site root
     */
    private String getSiteRootFromState() {

        String siteRoot = StateBean.parse(m_currentState).getSiteRoot();
        return siteRoot;
    }

    /**
     * Populates the folder tree.<p>
     *
     * @param showInvisible to show non visible root folder as disabled
     *
     * @throws CmsException in case reading the root folder fails
     */
    private void initFolderTree(boolean showInvisible) throws CmsException {

        CmsObject cms = A_CmsUI.getCmsObject();
        m_treeContainer.removeAllItems();
        try {
            CmsResource siteRoot = cms.readResource("/", FOLDERS);
            addTreeItem(cms, siteRoot, null, false, m_treeContainer);
        } catch (CmsException e) {
            if (showInvisible) {
                CmsResource siteRoot = cms.readResource("/", CmsResourceFilter.IGNORE_EXPIRATION);
                addTreeItem(cms, siteRoot, null, true, m_treeContainer);
            } else {
                throw e;
            }
        }

    }

    /**
     * Initializes the app specific toolbar buttons.<p>
     *
     * @param context the UI context
     */
    private void initToolbarButtons(I_CmsAppUIContext context) {

        m_publishButton = context.addPublishButton(new I_CmsUpdateListener<String>() {

            public void onUpdate(List<String> updatedItems) {

                updateAll(false);
            }

        });

        m_newButton = CmsToolBar.createButton(FontOpenCms.WAND,
                CmsVaadinUtils.getMessageText(Messages.GUI_NEW_RESOURCE_TITLE_0));
        if (CmsAppWorkplaceUi.isOnlineProject()) {
            m_newButton.setEnabled(false);
            m_newButton.setDescription(CmsVaadinUtils.getMessageText(Messages.GUI_TOOLBAR_NOT_AVAILABLE_ONLINE_0));
        }
        m_newButton.addClickListener(new ClickListener() {

            private static final long serialVersionUID = 1L;

            public void buttonClick(ClickEvent event) {

                onClickNew();
            }

        });

        context.addToolbarButton(m_newButton);

        m_uploadButton = new CmsUploadButton(FontOpenCms.UPLOAD, "/");
        m_uploadButton.addStyleName(ValoTheme.BUTTON_BORDERLESS);
        m_uploadButton.addStyleName(OpenCmsTheme.TOOLBAR_BUTTON);
        if (CmsAppWorkplaceUi.isOnlineProject()) {
            m_uploadButton.setEnabled(false);
            m_uploadButton
                    .setDescription(CmsVaadinUtils.getMessageText(Messages.GUI_TOOLBAR_NOT_AVAILABLE_ONLINE_0));
        } else {
            m_uploadButton.setDescription(CmsVaadinUtils.getMessageText(Messages.GUI_UPLOAD_BUTTON_TITLE_0));
        }
        m_uploadButton.addUploadListener(new I_UploadListener() {

            public void onUploadFinished(List<String> uploadedFiles) {

                updateAll(true);
            }
        });

        context.addToolbarButton(m_uploadButton);
    }

    /**
     * Normalizes the state string. Ensuring it starts with the right number of slashes resolving an issue with the vaadin state handling.<p>
     *
     * @param state the state string
     *
     * @return the normalized state string
     */
    private String normalizeState(String state) {

        String result = "";
        if (state.contains(STATE_SEPARATOR)) {
            result = state;
            while (result.startsWith("/")) {
                result = result.substring(1);
            }
        }
        return result;
    }

    /**
     * Sets the current path info.<p>
     *
     * @param path the path
     */
    private void setPathInfo(String path) {

        m_infoPath.setValue(path);

        // generate the path bread crumb
        m_crumbs.removeAllComponents();
        int i = path.indexOf("/");
        String openPath = "";
        while (i >= 0) {
            String fragment = path.substring(0, i + 1);
            openPath += fragment;
            path = path.substring(i + 1);
            i = path.indexOf("/");
            Button crumb = new Button(fragment, m_crumbListener);
            crumb.setData(openPath);
            crumb.addStyleName(ValoTheme.BUTTON_LINK);
            m_crumbs.addComponent(crumb);
        }
    }

    /**
     * Sets the toolbar buttons enabled.<p>
     *
     * @param enabled the enabled flag
     */
    private void setToolbarButtonsEnabled(boolean enabled) {

        m_publishButton.setEnabled(enabled);
        m_newButton.setEnabled(enabled);
        m_uploadButton.setEnabled(enabled);
        if (enabled) {
            m_publishButton.setDescription(CmsVaadinUtils.getMessageText(Messages.GUI_PUBLISH_BUTTON_TITLE_0));
            m_newButton.setDescription(CmsVaadinUtils.getMessageText(Messages.GUI_NEW_RESOURCE_TITLE_0));
            m_uploadButton.setDescription(CmsVaadinUtils.getMessageText(Messages.GUI_UPLOAD_BUTTON_TITLE_0));
        } else {
            m_publishButton
                    .setDescription(CmsVaadinUtils.getMessageText(Messages.GUI_TOOLBAR_NOT_AVAILABLE_ONLINE_0));
            m_newButton.setDescription(CmsVaadinUtils.getMessageText(Messages.GUI_TOOLBAR_NOT_AVAILABLE_ONLINE_0));
            m_uploadButton
                    .setDescription(CmsVaadinUtils.getMessageText(Messages.GUI_TOOLBAR_NOT_AVAILABLE_ONLINE_0));
        }
    }
}