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.ui.apps;
029
030import org.opencms.db.CmsUserSettings;
031import org.opencms.file.CmsObject;
032import org.opencms.file.CmsProject;
033import org.opencms.file.CmsResource;
034import org.opencms.file.CmsResourceFilter;
035import org.opencms.file.CmsVfsResourceNotFoundException;
036import org.opencms.main.CmsException;
037import org.opencms.main.CmsLog;
038import org.opencms.main.OpenCms;
039import org.opencms.security.CmsPermissionSet;
040import org.opencms.site.CmsSiteManagerImpl;
041import org.opencms.ui.A_CmsUI;
042import org.opencms.ui.CmsVaadinUtils;
043import org.opencms.ui.FontOpenCms;
044import org.opencms.ui.I_CmsDialogContext;
045import org.opencms.ui.I_CmsDialogContext.ContextType;
046import org.opencms.ui.I_CmsUpdateListener;
047import org.opencms.ui.actions.CmsCopyDialogAction;
048import org.opencms.ui.actions.CmsPropertiesDialogAction;
049import org.opencms.ui.actions.I_CmsWorkplaceAction;
050import org.opencms.ui.components.A_CmsFocusShortcutListener;
051import org.opencms.ui.components.CmsErrorDialog;
052import org.opencms.ui.components.CmsExtendedSiteSelector;
053import org.opencms.ui.components.CmsFileTable;
054import org.opencms.ui.components.CmsResourceIcon;
055import org.opencms.ui.components.CmsResourceTable;
056import org.opencms.ui.components.CmsResourceTableProperty;
057import org.opencms.ui.components.CmsToolBar;
058import org.opencms.ui.components.CmsUploadButton;
059import org.opencms.ui.components.CmsUploadButton.I_UploadListener;
060import org.opencms.ui.components.I_CmsWindowCloseListener;
061import org.opencms.ui.components.OpenCmsTheme;
062import org.opencms.ui.components.extensions.CmsUploadAreaExtension;
063import org.opencms.ui.contextmenu.CmsResourceContextMenuBuilder;
064import org.opencms.ui.dialogs.CmsCopyMoveDialog;
065import org.opencms.ui.dialogs.CmsDeleteDialog;
066import org.opencms.ui.dialogs.CmsNewDialog;
067import org.opencms.util.CmsFileUtil;
068import org.opencms.util.CmsStringUtil;
069import org.opencms.util.CmsUUID;
070import org.opencms.workplace.CmsWorkplace;
071import org.opencms.workplace.explorer.CmsResourceUtil;
072
073import java.io.IOException;
074import java.nio.charset.StandardCharsets;
075import java.util.ArrayList;
076import java.util.Arrays;
077import java.util.Collection;
078import java.util.Collections;
079import java.util.HashMap;
080import java.util.HashSet;
081import java.util.List;
082import java.util.Map;
083import java.util.Set;
084
085import org.apache.commons.lang.RandomStringUtils;
086import org.apache.commons.logging.Log;
087
088import com.google.common.collect.Lists;
089import com.google.common.collect.Sets;
090import com.vaadin.event.Action;
091import com.vaadin.event.FieldEvents.BlurEvent;
092import com.vaadin.event.FieldEvents.FocusEvent;
093import com.vaadin.event.ShortcutAction;
094import com.vaadin.event.ShortcutAction.KeyCode;
095import com.vaadin.event.dd.DragAndDropEvent;
096import com.vaadin.event.dd.DropHandler;
097import com.vaadin.event.dd.acceptcriteria.AcceptCriterion;
098import com.vaadin.event.dd.acceptcriteria.ServerSideCriterion;
099import com.vaadin.navigator.ViewChangeListener;
100import com.vaadin.server.Page;
101import com.vaadin.server.Sizeable.Unit;
102import com.vaadin.ui.Button;
103import com.vaadin.ui.Button.ClickEvent;
104import com.vaadin.ui.Button.ClickListener;
105import com.vaadin.ui.CssLayout;
106import com.vaadin.ui.HorizontalSplitPanel;
107import com.vaadin.ui.JavaScript;
108import com.vaadin.ui.Notification;
109import com.vaadin.ui.UI;
110import com.vaadin.ui.themes.ValoTheme;
111import com.vaadin.v7.data.Container;
112import com.vaadin.v7.data.Item;
113import com.vaadin.v7.data.Property.ValueChangeEvent;
114import com.vaadin.v7.data.Property.ValueChangeListener;
115import com.vaadin.v7.data.util.BeanItemContainer;
116import com.vaadin.v7.data.util.HierarchicalContainer;
117import com.vaadin.v7.event.FieldEvents.TextChangeEvent;
118import com.vaadin.v7.event.FieldEvents.TextChangeListener;
119import com.vaadin.v7.event.ItemClickEvent;
120import com.vaadin.v7.event.ItemClickEvent.ItemClickListener;
121import com.vaadin.v7.shared.ui.combobox.FilteringMode;
122import com.vaadin.v7.ui.AbstractSelect.AbstractSelectTargetDetails;
123import com.vaadin.v7.ui.ComboBox;
124import com.vaadin.v7.ui.HorizontalLayout;
125import com.vaadin.v7.ui.Table;
126import com.vaadin.v7.ui.Table.TableDragMode;
127import com.vaadin.v7.ui.TextField;
128import com.vaadin.v7.ui.Tree;
129import com.vaadin.v7.ui.Tree.CollapseEvent;
130import com.vaadin.v7.ui.Tree.CollapseListener;
131import com.vaadin.v7.ui.Tree.ExpandEvent;
132import com.vaadin.v7.ui.Tree.ExpandListener;
133import com.vaadin.v7.ui.Tree.ItemStyleGenerator;
134import com.vaadin.v7.ui.Tree.TreeDragMode;
135
136/**
137 * The file explorer app.<p>
138 */
139@SuppressWarnings("deprecation")
140public class CmsFileExplorer
141implements I_CmsWorkplaceApp, I_CmsCachableApp, ViewChangeListener, I_CmsWindowCloseListener, I_CmsHasShortcutActions,
142I_CmsContextProvider, CmsFileTable.I_FolderSelectHandler, CmsResourceTable.ColumnSettingChangeHandler {
143
144    /** The drop handler for copy/move operations. */
145    public class ExplorerDropHandler implements DropHandler {
146
147        /** The serial version id. */
148        private static final long serialVersionUID = 5392136127699472654L;
149
150        /** The copy move action. */
151        transient final I_CmsWorkplaceAction m_copyMoveAction = new CmsCopyDialogAction();
152
153        /**
154         * @see com.vaadin.event.dd.DropHandler#drop(com.vaadin.event.dd.DragAndDropEvent)
155         */
156        public void drop(DragAndDropEvent dragEvent) {
157
158            try {
159                CmsExplorerDialogContext context = getContext(dragEvent);
160                if (m_copyMoveAction.isActive(context)) {
161                    CmsCopyMoveDialog dialog = new CmsCopyMoveDialog(
162                        context,
163                        CmsCopyMoveDialog.DialogMode.copy_and_move);
164                    dialog.setTargetFolder(getTargetId(dragEvent));
165                    context.start(
166                        CmsVaadinUtils.getMessageText(org.opencms.ui.Messages.GUI_DIALOGTITLE_COPYMOVE_0),
167                        dialog);
168                }
169            } catch (Exception e) {
170                LOG.error("Moving resource failed", e);
171            }
172
173        }
174
175        /**
176         * @see com.vaadin.event.dd.DropHandler#getAcceptCriterion()
177         */
178        public AcceptCriterion getAcceptCriterion() {
179
180            return new ServerSideCriterion() {
181
182                private static final long serialVersionUID = 1L;
183
184                public boolean accept(DragAndDropEvent dragEvent) {
185
186                    try {
187                        if (!m_copyMoveAction.isActive(getContext(dragEvent))) {
188                            return false;
189                        }
190                    } catch (CmsException e) {
191                        LOG.error("Drag an drop evaluation failed", e);
192                        return false;
193                    }
194                    CmsUUID targetId = getTargetId(dragEvent);
195                    return mayDrop(targetId);
196                }
197            };
198        }
199
200        /**
201         * Returns the drag target id.<p>
202         *
203         * @param dragEvent the drag event
204         *
205         * @return the drag target id
206         */
207        protected CmsUUID getTargetId(DragAndDropEvent dragEvent) {
208
209            CmsUUID targetId = null;
210            if (dragEvent.getTargetDetails() instanceof AbstractSelectTargetDetails) {
211                AbstractSelectTargetDetails target = (AbstractSelectTargetDetails)dragEvent.getTargetDetails();
212                Object itemOverId = target.getItemIdOver();
213                if (itemOverId instanceof CmsUUID) {
214                    targetId = (CmsUUID)itemOverId;
215                } else if (itemOverId instanceof String) {
216                    targetId = m_fileTable.getUUIDFromItemID((String)itemOverId);
217                }
218            }
219            try {
220                CmsObject cms = A_CmsUI.getCmsObject();
221                CmsResource target = cms.readResource(targetId);
222                if (target.isFile()) {
223                    targetId = null;
224                }
225            } catch (CmsException e) {
226                targetId = null;
227                LOG.debug("Checking drop target failed, use current folder.", e);
228            }
229
230            if (targetId == null) {
231                targetId = getCurrentFolder();
232            }
233            return targetId;
234        }
235
236        /**
237         * Evaluates if a drop on the given target is allowed.<p>
238         *
239         * @param targetId the target id
240         *
241         * @return <code>true</code> if the resources may be dropped to the given target
242         */
243        protected boolean mayDrop(CmsUUID targetId) {
244
245            boolean result = false;
246            try {
247                CmsObject cms = A_CmsUI.getCmsObject();
248                CmsResource target = cms.readResource(targetId);
249                result = cms.hasPermissions(
250                    target,
251                    CmsPermissionSet.ACCESS_WRITE,
252                    false,
253                    CmsResourceFilter.ONLY_VISIBLE_NO_DELETED);
254            } catch (Exception e) {
255                LOG.debug("Checking folder write permissions failed", e);
256            }
257            return result;
258        }
259
260        /**
261         * Returns the dialog context to use.<p>
262         *
263         * @param dragEvent the drag event
264         *
265         * @return the dialog context
266         *
267         * @throws CmsException if reading the drag resource fails
268         */
269        CmsExplorerDialogContext getContext(DragAndDropEvent dragEvent) throws CmsException {
270
271            List<CmsResource> resources;
272            if ((dragEvent.getTransferable().getSourceComponent() instanceof Table)
273                && !m_fileTable.getSelectedResources().isEmpty()) {
274                resources = m_fileTable.getSelectedResources();
275            } else {
276                CmsObject cms = A_CmsUI.getCmsObject();
277                CmsUUID sourceId = m_fileTable.getUUIDFromItemID((String)dragEvent.getTransferable().getData("itemId"));
278                CmsResource source = cms.readResource(sourceId, CmsResourceFilter.IGNORE_EXPIRATION);
279                resources = Collections.singletonList(source);
280            }
281            CmsExplorerDialogContext context = new CmsExplorerDialogContext(
282                ContextType.fileTable,
283                m_fileTable,
284                CmsFileExplorer.this,
285                resources);
286            return context;
287        }
288    }
289
290    /**
291     * File tree expand listener.<p>
292     */
293    public class TreeExpandListener implements ExpandListener {
294
295        /** The serial version id. */
296        private static final long serialVersionUID = 1L;
297        /**
298         * The path fragment being opened.
299         * Will override folder visibility in case the the target path is visible to the user.
300         **/
301        private String m_openPathFragment;
302
303        /**
304         * @see com.vaadin.ui.Tree.ExpandListener#nodeExpand(com.vaadin.ui.Tree.ExpandEvent)
305         */
306        public void nodeExpand(ExpandEvent event) {
307
308            selectTreeItem((CmsUUID)event.getItemId());
309            readTreeLevel((CmsUUID)event.getItemId(), m_openPathFragment);
310        }
311
312        /**
313         * Sets the open path fragment.<p>
314         *
315         * @param openPathFragment the open path fragment
316         */
317        public void setOpenPathFragment(String openPathFragment) {
318
319            m_openPathFragment = openPathFragment;
320        }
321    }
322
323    /** Bean representing the file explorer navigation substate. */
324    static class StateBean {
325
326        /** Current folder. */
327        private String m_folder;
328
329        /** Project id. */
330        private String m_projectId;
331
332        /**selected resource.*/
333        private String m_selectedResource;
334
335        /** The site root. */
336        private String m_siteRoot;
337
338        /**
339         * Creates a new state bean.<p>
340         *
341         * @param siteRoot the site root
342         * @param folder the folder
343         * @param projectId the project id
344         */
345        public StateBean(String siteRoot, String folder, String projectId) {
346
347            m_siteRoot = siteRoot;
348            m_folder = folder;
349            m_projectId = projectId;
350            if ("".equals(m_siteRoot)) {
351                m_siteRoot = "/";
352            }
353        }
354
355        /**
356         * Parses the state bean from a string.<p>
357         *
358         * @param state the state string
359         * @return the state bean
360         */
361        public static StateBean parse(String state) {
362
363            List<String> fields = CmsStringUtil.splitAsList(state, A_CmsWorkplaceApp.PARAM_SEPARATOR);
364            if (fields.size() >= 3) {
365                String projectId = fields.get(0);
366                String siteRoot = fields.get(1);
367                String folder = fields.get(2);
368                StateBean ret = new StateBean(siteRoot, folder, projectId);
369                if (fields.size() == 4) {
370                    ret.setSelectedResource(fields.get(3));
371                }
372                return ret;
373            } else {
374                return new StateBean(null, null, null);
375            }
376        }
377
378        /**
379         * Converts state bean to a string.<p>
380         *
381         * @return the string format of the state
382         */
383        public String asString() {
384
385            String result = m_projectId
386                + A_CmsWorkplaceApp.PARAM_SEPARATOR
387                + m_siteRoot
388                + A_CmsWorkplaceApp.PARAM_SEPARATOR
389                + m_folder
390                + A_CmsWorkplaceApp.PARAM_SEPARATOR;
391            return result;
392        }
393
394        /**
395         * Returns the folderId.<p>
396         *
397         * @return the folderId
398         */
399        public String getFolder() {
400
401            return m_folder;
402        }
403
404        /**
405         * Returns the projectId.<p>
406         *
407         * @return the projectId
408         */
409        public String getProjectId() {
410
411            return m_projectId;
412        }
413
414        /**
415         * Returns the resource to select, empty if no one was set.<p>
416         *
417         * @return UUID as string
418         */
419        public String getSelectedResource() {
420
421            return m_selectedResource == null ? "" : m_selectedResource;
422        }
423
424        /**
425         * Returns the siteRoot.<p>
426         *
427         * @return the siteRoot
428         */
429        public String getSiteRoot() {
430
431            return m_siteRoot;
432        }
433
434        /**
435         * Sets a resource to be selected.<p>
436         *
437         * @param resource to get selected
438         */
439        public void setSelectedResource(String resource) {
440
441            m_selectedResource = resource;
442        }
443    }
444
445    /** The file explorer attribute key. */
446    public static final String ATTR_KEY = "CmsFileExplorer";
447
448    /** The in line editable resource properties. */
449    public static final Collection<CmsResourceTableProperty> INLINE_EDIT_PROPERTIES = Arrays.asList(
450        CmsResourceTableProperty.PROPERTY_RESOURCE_NAME,
451        CmsResourceTableProperty.PROPERTY_TITLE,
452        CmsResourceTableProperty.PROPERTY_NAVIGATION_TEXT,
453        CmsResourceTableProperty.PROPERTY_COPYRIGHT,
454        CmsResourceTableProperty.PROPERTY_CACHE);
455
456    /** The initial split position between folder tree and file table. */
457    public static final int LAYOUT_SPLIT_POSITION = 399;
458
459    /** The opened paths session attribute name. */
460    public static final String OPENED_PATHS = "explorer-opened-paths";
461
462    /** Site selector caption property. */
463    public static final String SITE_CAPTION = "site_caption";
464
465    /** Site selector site root property. */
466    public static final String SITE_ROOT = "site_root";
467
468    /** Threshold for updating the complete folder after file changes. */
469    public static final int UPDATE_FOLDER_THRESHOLD = 200;
470
471    /** Logger instance for this class. */
472    static final Log LOG = CmsLog.getLog(CmsFileExplorer.class);
473
474    /** The delete shortcut. */
475    private static final Action ACTION_DELETE = new ShortcutAction("Del", ShortcutAction.KeyCode.DELETE, null);
476
477    /** The open parent folder shortcut. */
478    private static final Action ACTION_FOLDER_UP = new ShortcutAction(
479        "Alt+ArrowUp",
480        ShortcutAction.KeyCode.ARROW_UP,
481        new int[] {ShortcutAction.ModifierKey.ALT});
482
483    /** The edit properties shortcut. */
484    private static final Action ACTION_PROPERTIES = new ShortcutAction(
485        "Alt+Enter",
486        ShortcutAction.KeyCode.ENTER,
487        new int[] {ShortcutAction.ModifierKey.ALT});
488
489    /** The rename shortcut. */
490    private static final Action ACTION_RENAME = new ShortcutAction("F2", ShortcutAction.KeyCode.F2, null);
491
492    /** The select all shortcut. */
493    private static final Action ACTION_SELECT_ALL = new ShortcutAction(
494        "Ctrl+A",
495        ShortcutAction.KeyCode.A,
496        new int[] {ShortcutAction.ModifierKey.CTRL});
497
498    /** The select all shortcut, (using Apple CMD as modifier). */
499    private static final Action ACTION_SELECT_ALL_CMD = new ShortcutAction(
500        "CMD+A",
501        ShortcutAction.KeyCode.A,
502        new int[] {ShortcutAction.ModifierKey.META});
503
504    /** The switch online shortcut. */
505    private static final Action ACTION_SWITCH_ONLINE = new ShortcutAction(
506        "Ctrl+O",
507        ShortcutAction.KeyCode.O,
508        new int[] {ShortcutAction.ModifierKey.CTRL});
509
510    /** The switch online shortcut, (using Apple CMD as modifier). */
511    private static final Action ACTION_SWITCH_ONLINE_CMD = new ShortcutAction(
512        "CMD+O",
513        ShortcutAction.KeyCode.O,
514        new int[] {ShortcutAction.ModifierKey.META});
515
516    /** The files and folder resource filter. */
517    private static final CmsResourceFilter FILES_N_FOLDERS = CmsResourceFilter.ONLY_VISIBLE;
518
519    /** The folders resource filter. */
520    private static final CmsResourceFilter FOLDERS = CmsResourceFilter.ONLY_VISIBLE_NO_DELETED.addRequireFolder();
521
522    /** The serial version id. */
523    private static final long serialVersionUID = 1L;
524
525    /** The UI context. */
526    protected I_CmsAppUIContext m_appContext;
527
528    /** Saved explorer state used by dialogs after they have finished. */
529    protected String m_savedExplorerState = "";
530
531    /** The table containing the contents of the current folder. */
532    CmsFileTable m_fileTable;
533
534    /** The info path. */
535    com.vaadin.ui.TextField m_infoPath;
536
537    /** The explorer shortcuts. */
538    Map<Action, Runnable> m_shortcutActions;
539
540    /** The bread crumb click listener. */
541    private ClickListener m_crumbListener;
542
543    /** The path bread crumb container. */
544    private CssLayout m_crumbs;
545
546    /** The currently viewed folder. */
547    private CmsUUID m_currentFolder;
548
549    /** The current app state. */
550    private String m_currentState;
551
552    /** The tree expand listener. */
553    private TreeExpandListener m_expandListener;
554
555    /** The folder tree. */
556    private Tree m_fileTree;
557
558    /** The first visible file table item index. */
559    private int m_firstVisibleTableItemIndex;
560
561    /** The last context menu resources. */
562    private List<CmsResource> m_lastDialogContextResources;
563
564    /** The quick launch location cache. */
565    private CmsQuickLaunchLocationCache m_locationCache;
566
567    /** The new button. */
568    private Button m_newButton;
569
570    /** The publish button. */
571    private Button m_publishButton;
572
573    /** The search field. */
574    private TextField m_searchField;
575
576    /** the currently selected file tree folder, may be null. */
577    private CmsUUID m_selectTreeFolder;
578
579    /** The site selector. */
580    private ComboBox m_siteSelector;
581
582    /** Button for uploading to folders with special upload actions. */
583    private Button m_specialUploadButton;
584
585    /** The folder tree data container. */
586    private HierarchicalContainer m_treeContainer;
587
588    /** Upload action for the current folder. */
589    private String m_uploadAction;
590
591    /** The upload drop area extension. */
592    private CmsUploadAreaExtension m_uploadArea;
593
594    /** The upload button. */
595    private CmsUploadButton m_uploadButton;
596
597    /** The current upload folder. */
598    private CmsResource m_uploadFolder;
599
600    /**
601     * Constructor.<p>
602     */
603    public CmsFileExplorer() {
604
605        m_shortcutActions = new HashMap<Action, Runnable>();
606        m_shortcutActions.put(ACTION_DELETE, new Runnable() {
607
608            public void run() {
609
610                if (!m_fileTable.getSelectedIds().isEmpty()) {
611                    I_CmsDialogContext context1 = getDialogContext();
612                    context1.start("Delete", new CmsDeleteDialog(context1));
613                }
614            }
615        });
616
617        m_shortcutActions.put(ACTION_FOLDER_UP, new Runnable() {
618
619            public void run() {
620
621                showParentFolder();
622            }
623        });
624
625        m_shortcutActions.put(ACTION_PROPERTIES, new Runnable() {
626
627            public void run() {
628
629                I_CmsWorkplaceAction propAction = new CmsPropertiesDialogAction();
630                I_CmsDialogContext context = getDialogContext();
631                if (propAction.getVisibility(context).isActive()) {
632                    propAction.executeAction(context);
633                }
634            }
635        });
636
637        m_shortcutActions.put(ACTION_RENAME, new Runnable() {
638
639            public void run() {
640
641                CmsExplorerDialogContext context = getDialogContext();
642                if (context.isPropertyEditable(CmsResourceTableProperty.PROPERTY_RESOURCE_NAME)) {
643                    context.editProperty(CmsResourceTableProperty.PROPERTY_RESOURCE_NAME);
644                }
645            }
646        });
647        Runnable selectAll = new Runnable() {
648
649            public void run() {
650
651                m_fileTable.selectAll();
652            }
653        };
654        m_shortcutActions.put(ACTION_SELECT_ALL, selectAll);
655        m_shortcutActions.put(ACTION_SELECT_ALL_CMD, selectAll);
656
657        Runnable switchOnline = new Runnable() {
658
659            public void run() {
660
661                toggleOnlineOffline();
662            }
663        };
664        m_shortcutActions.put(ACTION_SWITCH_ONLINE, switchOnline);
665        m_shortcutActions.put(ACTION_SWITCH_ONLINE_CMD, switchOnline);
666
667        m_fileTable = new CmsFileTable(this);
668        m_fileTable.setColumnSettingChangeHandler(this);
669        m_fileTable.setSizeFull();
670        m_fileTable.setMenuBuilder(new CmsResourceContextMenuBuilder());
671        m_fileTable.setFolderSelectHandler(this);
672        m_uploadArea = new CmsUploadAreaExtension(m_fileTable);
673        m_uploadArea.addUploadListener(new I_UploadListener() {
674
675            public void onUploadFinished(List<String> uploadedFiles) {
676
677                updateAll(true);
678            }
679        });
680        m_treeContainer = new HierarchicalContainer();
681        addTreeContainerProperties(
682            CmsResourceTableProperty.PROPERTY_RESOURCE_NAME,
683            CmsResourceTableProperty.PROPERTY_STATE,
684            CmsResourceTableProperty.PROPERTY_TREE_CAPTION,
685            CmsResourceTableProperty.PROPERTY_INSIDE_PROJECT,
686            CmsResourceTableProperty.PROPERTY_RELEASED_NOT_EXPIRED,
687            CmsResourceTableProperty.PROPERTY_DISABLED);
688        m_fileTree = new Tree();
689        m_fileTree.addStyleName(OpenCmsTheme.SIMPLE_DRAG);
690        m_fileTree.addStyleName(OpenCmsTheme.FULL_WIDTH_PADDING);
691        m_fileTree.setWidth("100%");
692        m_fileTree.setContainerDataSource(m_treeContainer);
693        //            m_fileTree.setItemIconPropertyId(CmsResourceTableProperty.PROPERTY_TYPE_ICON_RESOURCE);
694        m_fileTree.setItemCaptionPropertyId(CmsResourceTableProperty.PROPERTY_TREE_CAPTION);
695        //        m_fileTree.setCaptionAsHtml(true);
696        m_fileTree.setHtmlContentAllowed(true);
697        m_expandListener = new TreeExpandListener();
698        m_fileTree.addExpandListener(m_expandListener);
699        m_fileTree.addCollapseListener(new CollapseListener() {
700
701            private static final long serialVersionUID = 1L;
702
703            public void nodeCollapse(CollapseEvent event) {
704
705                selectTreeItem((CmsUUID)event.getItemId());
706                clearTreeLevel((CmsUUID)event.getItemId());
707            }
708        });
709
710        m_fileTree.addItemClickListener(new ItemClickListener() {
711
712            private static final long serialVersionUID = 1L;
713
714            public void itemClick(ItemClickEvent event) {
715
716                handleFileTreeClick(event);
717            }
718        });
719
720        m_fileTree.setItemStyleGenerator(new ItemStyleGenerator() {
721
722            private static final long serialVersionUID = 1L;
723
724            public String getStyle(Tree source, Object itemId) {
725
726                return CmsFileTable.getStateStyle(source.getContainerDataSource().getItem(itemId));
727            }
728        });
729        m_fileTree.addValueChangeListener(new ValueChangeListener() {
730
731            private static final long serialVersionUID = 1L;
732
733            public void valueChange(ValueChangeEvent event) {
734
735                handleFileTreeValueChange();
736            }
737
738        });
739
740        m_fileTree.setNullSelectionAllowed(false);
741
742        // init drag and drop
743        ExplorerDropHandler handler = new ExplorerDropHandler();
744        m_fileTable.setDropHandler(handler);
745        m_fileTable.setDragMode(TableDragMode.MULTIROW);
746        m_fileTree.setDropHandler(handler);
747        m_fileTree.setDragMode(TreeDragMode.NONE);
748
749        m_siteSelector = createSiteSelect(A_CmsUI.getCmsObject());
750        m_infoPath = new com.vaadin.ui.TextField();
751        A_CmsFocusShortcutListener shortcutListener = new A_CmsFocusShortcutListener("Open path", KeyCode.ENTER, null) {
752
753            private static final long serialVersionUID = 1L;
754
755            @Override
756            public void blur(BlurEvent event) {
757
758                super.blur(event);
759                showCrumbs(true);
760            }
761
762            @Override
763            public void focus(FocusEvent event) {
764
765                super.focus(event);
766                showCrumbs(false);
767            }
768
769            @Override
770            public void handleAction(Object sender, Object target) {
771
772                openPath(m_infoPath.getValue());
773            }
774        };
775        shortcutListener.installOn(m_infoPath);
776
777        m_crumbs = new CssLayout();
778        m_crumbs.setPrimaryStyleName(OpenCmsTheme.CRUMBS);
779        m_crumbListener = new ClickListener() {
780
781            private static final long serialVersionUID = 1L;
782
783            public void buttonClick(ClickEvent event) {
784
785                openPath((String)event.getButton().getData());
786            }
787        };
788
789        m_searchField = new TextField();
790        m_searchField.setIcon(FontOpenCms.FILTER);
791        m_searchField.setInputPrompt(
792            Messages.get().getBundle(UI.getCurrent().getLocale()).key(Messages.GUI_EXPLORER_FILTER_0));
793        m_searchField.addStyleName(ValoTheme.TEXTFIELD_INLINE_ICON);
794        m_searchField.addTextChangeListener(new TextChangeListener() {
795
796            private static final long serialVersionUID = 1L;
797
798            public void textChange(TextChangeEvent event) {
799
800                filterTable(event.getText());
801
802            }
803        });
804
805        m_locationCache = CmsQuickLaunchLocationCache.getLocationCache(CmsAppWorkplaceUi.get().getHttpSession());
806        String startSite = CmsWorkplace.getStartSiteRoot(
807            A_CmsUI.getCmsObject(),
808            CmsAppWorkplaceUi.get().getWorkplaceSettings());
809        // remove trailing slashes
810        while (startSite.endsWith("/")) {
811            startSite = startSite.substring(0, startSite.length() - 1);
812        }
813        if (m_locationCache.getFileExplorerLocation(startSite) == null) {
814            // add the configured start folder for the start site
815            String startFolder = CmsAppWorkplaceUi.get().getWorkplaceSettings().getUserSettings().getStartFolder();
816            m_locationCache.setFileExplorerLocation(startSite, startFolder);
817        }
818    }
819
820    /**
821     * @see com.vaadin.navigator.ViewChangeListener#afterViewChange(com.vaadin.navigator.ViewChangeListener.ViewChangeEvent)
822     */
823    public void afterViewChange(ViewChangeEvent event) {
824
825        m_fileTable.setFirstVisibleItemIndex(m_firstVisibleTableItemIndex);
826    }
827
828    /**
829     * @see com.vaadin.navigator.ViewChangeListener#beforeViewChange(com.vaadin.navigator.ViewChangeListener.ViewChangeEvent)
830     */
831    public boolean beforeViewChange(ViewChangeEvent event) {
832
833        m_firstVisibleTableItemIndex = m_fileTable.getFirstVisibleItemIndex();
834
835        OpenCms.getWorkplaceAppManager().storeAppSettings(
836            A_CmsUI.getCmsObject(),
837            CmsFileExplorerSettings.class,
838            m_fileTable.getTableSettings());
839        return true;
840    }
841
842    /**
843     * Changes to the given site and path.<p>
844     *
845     * @param siteRoot the site root
846     * @param path the path inside the site
847     */
848    public void changeSite(String siteRoot, String path) {
849
850        changeSite(siteRoot, path, false);
851
852    }
853
854    /**
855     * Switches to the requested site.<p>
856     *
857     * @param siteRoot the site root
858     * @param path the folder path to open
859     * @param force force the path change, even if we are currently in the same site
860     */
861    public void changeSite(String siteRoot, String path, boolean force) {
862
863        CmsObject cms = A_CmsUI.getCmsObject();
864        String currentSiteRoot = cms.getRequestContext().getSiteRoot();
865        if (force || !currentSiteRoot.equals(siteRoot) || (path != null)) {
866            CmsAppWorkplaceUi.get().changeSite(siteRoot);
867            if (path == null) {
868                path = m_locationCache.getFileExplorerLocation(siteRoot);
869                if (CmsStringUtil.isEmptyOrWhitespaceOnly(siteRoot)
870                    && ((path == null) || !path.startsWith("/system"))) {
871                    // switching to the root site and previous root site folder is not below /system
872                    // -> stay in the current folder
873                    path = m_locationCache.getFileExplorerLocation(currentSiteRoot);
874                    if (path != null) {
875                        path = CmsStringUtil.joinPaths(currentSiteRoot, path);
876                    }
877                }
878            }
879            openPath(path, true);
880            Container container = m_siteSelector.getContainerDataSource();
881            CmsExtendedSiteSelector.SiteSelectorOption optionToSelect = null;
882            for (Object id : container.getItemIds()) {
883                CmsExtendedSiteSelector.SiteSelectorOption option = (CmsExtendedSiteSelector.SiteSelectorOption)id;
884                if ((option != null) && CmsStringUtil.comparePaths(option.getSite(), siteRoot)) {
885                    optionToSelect = option;
886                    break;
887                }
888            }
889            m_siteSelector.select(optionToSelect);
890        }
891    }
892
893    /**
894     * Clears the file table selection.
895     */
896    public void clearSelection() {
897
898        m_fileTable.clearSelection();
899    }
900
901    /**
902     * Gets all ids of resources in current folder.<p>
903     *
904     * @return the
905     */
906    public List<CmsUUID> getAllIds() {
907
908        return m_fileTable.getAllIds();
909    }
910
911    /**
912     * Returns the current folder id.<p>
913     *
914     * @return the current folder structure id
915     */
916    public CmsUUID getCurrentFolder() {
917
918        return m_currentFolder;
919    }
920
921    /**
922     * @see org.opencms.ui.apps.I_CmsContextProvider#getDialogContext()
923     */
924    public CmsExplorerDialogContext getDialogContext() {
925
926        List<CmsResource> resources = m_fileTable.getSelectedResources();
927        m_lastDialogContextResources = resources;
928        CmsExplorerDialogContext context = new CmsExplorerDialogContext(
929            ContextType.fileTable,
930            m_fileTable,
931            this,
932            resources);
933        context.setEditableProperties(INLINE_EDIT_PROPERTIES);
934        return context;
935    }
936
937    /**
938     * @see org.opencms.ui.apps.I_CmsHasShortcutActions#getShortcutActions()
939     */
940    public Map<Action, Runnable> getShortcutActions() {
941
942        return m_shortcutActions;
943    }
944
945    /**
946     * @see org.opencms.ui.apps.I_CmsWorkplaceApp#initUI(org.opencms.ui.apps.I_CmsAppUIContext)
947     */
948    public void initUI(I_CmsAppUIContext context) {
949
950        m_appContext = context;
951        m_appContext.setAttribute(ATTR_KEY, this);
952        m_appContext.setMenuDialogContext(
953            new CmsExplorerDialogContext(ContextType.appToolbar, m_fileTable, this, null));
954        HorizontalSplitPanel sp = new HorizontalSplitPanel();
955        sp.setSizeFull();
956        sp.setFirstComponent(m_fileTree);
957        CmsFileExplorerSettings settings;
958        try {
959            settings = OpenCms.getWorkplaceAppManager().getAppSettings(
960                A_CmsUI.getCmsObject(),
961                CmsFileExplorerSettings.class);
962
963            m_fileTable.setTableState(settings);
964        } catch (Exception e) {
965            LOG.error("Error while reading file explorer settings from user.", e);
966        }
967        sp.setSecondComponent(m_fileTable);
968
969        sp.setSplitPosition(LAYOUT_SPLIT_POSITION, Unit.PIXELS);
970
971        context.setAppContent(sp);
972        context.showInfoArea(true);
973        HorizontalLayout inf = new HorizontalLayout();
974        inf.setSizeFull();
975        inf.setSpacing(true);
976        inf.setMargin(true);
977        m_siteSelector.setWidth("379px");
978        inf.addComponent(m_siteSelector);
979        CssLayout crumbWrapper = new CssLayout();
980        crumbWrapper.setSizeFull();
981        crumbWrapper.setPrimaryStyleName(OpenCmsTheme.CRUMB_WRAPPER);
982        crumbWrapper.addComponent(m_crumbs);
983
984        m_infoPath.setWidth("100%");
985        crumbWrapper.addComponent(m_infoPath);
986        inf.addComponent(crumbWrapper);
987        inf.setExpandRatio(crumbWrapper, 1);
988
989        m_searchField.setWidth("200px");
990        inf.addComponent(m_searchField);
991        context.setAppInfo(inf);
992
993        initToolbarButtons(context);
994        try {
995            JavaScript.getCurrent().execute(
996                new String(
997                    CmsFileUtil.readFully(getClass().getResourceAsStream("update-crumb-wrapper-parent.js")),
998                    StandardCharsets.UTF_8));
999        } catch (IOException e) {}
1000        m_fileTable.updateColumnWidths(A_CmsUI.get().getPage().getBrowserWindowWidth() - LAYOUT_SPLIT_POSITION);
1001    }
1002
1003    /**
1004     * @see org.opencms.ui.apps.I_CmsCachableApp#isCachable()
1005     */
1006    public boolean isCachable() {
1007
1008        return true;
1009    }
1010
1011    /**
1012     * @see org.opencms.ui.components.CmsResourceTable.ColumnSettingChangeHandler#onColumnSettingsChanged()
1013     */
1014    @Override
1015    public void onColumnSettingsChanged() {
1016
1017        OpenCms.getWorkplaceAppManager().storeAppSettings(
1018            A_CmsUI.getCmsObject(),
1019            CmsFileExplorerSettings.class,
1020            m_fileTable.getTableSettings());
1021    }
1022
1023    /**
1024     * @see org.opencms.ui.components.CmsFileTable.I_FolderSelectHandler#onFolderSelect(org.opencms.util.CmsUUID)
1025     */
1026    public void onFolderSelect(CmsUUID itemId) {
1027
1028        expandCurrentFolder();
1029        if (m_fileTree.getItem(itemId) != null) {
1030            m_fileTree.select(itemId);
1031        }
1032        try {
1033            readFolder(itemId);
1034        } catch (CmsException e) {
1035            CmsErrorDialog.showErrorDialog(
1036                CmsVaadinUtils.getMessageText(Messages.ERR_EXPLORER_CAN_NOT_READ_RESOURCE_1, itemId),
1037                e);
1038            LOG.error(e.getLocalizedMessage(), e);
1039        }
1040    }
1041
1042    /**
1043     * @see org.opencms.ui.apps.I_CmsCachableApp#onRestoreFromCache()
1044     */
1045    public void onRestoreFromCache() {
1046
1047        if (m_lastDialogContextResources != null) {
1048            List<CmsUUID> updateIds = Lists.newArrayList();
1049            for (CmsResource resource : m_lastDialogContextResources) {
1050                updateIds.add(resource.getStructureId());
1051            }
1052            update(updateIds);
1053        }
1054
1055    }
1056
1057    /**
1058     * Call if site and or project have been changed.<p>
1059     *
1060     * @param project the project
1061     * @param siteRoot the site root
1062     */
1063    public void onSiteOrProjectChange(CmsProject project, String siteRoot) {
1064
1065        if ((siteRoot != null) && !siteRoot.equals(getSiteRootFromState())) {
1066            changeSite(siteRoot, null, true);
1067        } else if ((project != null) && !project.getUuid().equals(getProjectIdFromState())) {
1068            openPath(getPathFromState(), true);
1069        }
1070        m_appContext.updateOnChange();
1071        setToolbarButtonsEnabled(!CmsAppWorkplaceUi.isOnlineProject());
1072    }
1073
1074    /**
1075     * @see org.opencms.ui.apps.I_CmsWorkplaceApp#onStateChange(java.lang.String)
1076     */
1077    public void onStateChange(String state) {
1078
1079        state = normalizeState(state);
1080        if ((m_currentState == null) || !m_currentState.equals(state)) {
1081            m_currentState = state;
1082            CmsObject cms = A_CmsUI.getCmsObject();
1083            String siteRoot = getSiteRootFromState();
1084            String path = getPathFromState();
1085
1086            CmsUUID projectId = getProjectIdFromState();
1087            if ((projectId != null) && !cms.getRequestContext().getCurrentProject().getUuid().equals(projectId)) {
1088                try {
1089                    CmsProject project = cms.readProject(projectId);
1090                    cms.getRequestContext().setCurrentProject(project);
1091                    CmsAppWorkplaceUi.get().getWorkplaceSettings().setProject(project.getUuid());
1092                } catch (CmsException e) {
1093                    LOG.warn("Error reading project from history state", e);
1094                }
1095                changeSite(siteRoot, path, true);
1096            } else if ((siteRoot != null)
1097                && !CmsStringUtil.comparePaths(siteRoot, cms.getRequestContext().getSiteRoot())) {
1098                String saveState = m_currentState;
1099                changeSite(siteRoot, path);
1100                if (!getSelectionFromState(saveState).isEmpty()) {
1101                    m_fileTable.setValue(Collections.singleton(getSelectionFromState(saveState)));
1102                }
1103            } else {
1104                String saveState = m_currentState;
1105                openPath(path, true);
1106                if (!getSelectionFromState(saveState).isEmpty()) {
1107                    m_fileTable.setValue(Collections.singleton(getSelectionFromState(saveState)));
1108                }
1109            }
1110        }
1111    }
1112
1113    /**
1114     * @see org.opencms.ui.components.I_CmsWindowCloseListener#onWindowClose()
1115     */
1116    public void onWindowClose() {
1117
1118        OpenCms.getWorkplaceAppManager().storeAppSettings(
1119            A_CmsUI.getCmsObject(),
1120            CmsFileExplorerSettings.class,
1121            m_fileTable.getTableSettings());
1122    }
1123
1124    /**
1125     * Fills the file table with the resources from the given path.<p>
1126     *
1127     * @param sitePath a folder site path
1128     */
1129    public void populateFileTable(String sitePath) {
1130
1131        CmsObject cms = A_CmsUI.getCmsObject();
1132        m_searchField.clear();
1133        m_firstVisibleTableItemIndex = 0;
1134        try {
1135            List<CmsResource> folderResources = cms.readResources(sitePath, FILES_N_FOLDERS, false);
1136            m_fileTable.fillTable(cms, folderResources);
1137        } catch (CmsException e) {
1138            CmsErrorDialog.showErrorDialog(e);
1139            LOG.error(e.getLocalizedMessage(), e);
1140        }
1141    }
1142
1143    /**
1144     * Updates the table entries with the given ids.<p>
1145     *
1146     * @param ids the ids of the table entries to update
1147     */
1148    public void update(Collection<CmsUUID> ids) {
1149
1150        try {
1151            CmsResource currentFolderRes = A_CmsUI.getCmsObject().readResource(m_currentFolder, CmsResourceFilter.ALL);
1152            boolean updateFolder = m_fileTable.getItemCount() < UPDATE_FOLDER_THRESHOLD;
1153            Set<CmsUUID> removeIds = new HashSet<CmsUUID>();
1154            for (CmsUUID id : ids) {
1155                boolean remove = false;
1156                try {
1157                    CmsResource resource = A_CmsUI.getCmsObject().readResource(id, CmsResourceFilter.ALL);
1158
1159                    remove = !CmsResource.getParentFolder(resource.getRootPath()).equals(
1160                        currentFolderRes.getRootPath());
1161
1162                } catch (CmsVfsResourceNotFoundException e) {
1163                    remove = true;
1164                    LOG.debug("Could not read update resource " + id, e);
1165                }
1166                if (remove) {
1167                    removeIds.add(id);
1168                }
1169
1170            }
1171            for (CmsUUID id : ids) {
1172                updateTree(id);
1173            }
1174            if (updateFolder) {
1175                updateCurrentFolder(removeIds);
1176            } else {
1177                m_fileTable.update(removeIds, true);
1178                HashSet<CmsUUID> updateIds = new HashSet<CmsUUID>(ids);
1179                ids.removeAll(removeIds);
1180                m_fileTable.update(updateIds, false);
1181            }
1182            m_fileTable.updateSorting();
1183            m_fileTable.clearSelection();
1184        } catch (CmsException e) {
1185            CmsErrorDialog.showErrorDialog(e);
1186        }
1187        if (!CmsStringUtil.isEmptyOrWhitespaceOnly(m_searchField.getValue())) {
1188            filterTable(m_searchField.getValue());
1189        }
1190    }
1191
1192    /**
1193     * Updates display for all contents of the current folder.<p>
1194     *
1195     * @param clearFilter <code>true</code> to clear the search filter
1196     */
1197    public void updateAll(boolean clearFilter) {
1198
1199        try {
1200            readFolder(m_currentFolder, clearFilter);
1201            Set<Object> ids = Sets.newHashSet();
1202            ids.addAll(m_fileTree.getItemIds());
1203            for (Object id : ids) {
1204                updateTree((CmsUUID)id);
1205            }
1206        } catch (CmsException e) {
1207            CmsErrorDialog.showErrorDialog(
1208                CmsVaadinUtils.getMessageText(Messages.ERR_EXPLORER_CAN_NOT_READ_RESOURCE_1, m_currentFolder),
1209                e);
1210            LOG.error(e.getLocalizedMessage(), e);
1211        }
1212    }
1213
1214    /**
1215     * Updates the give tree item.<p>
1216     *
1217     * @param cms the cms context
1218     * @param id the item id
1219     */
1220    public void updateResourceInTree(CmsObject cms, CmsUUID id) {
1221
1222        try {
1223            CmsResource resource = cms.readResource(id, CmsResourceFilter.IGNORE_EXPIRATION);
1224            if (resource.isFile()) {
1225                return;
1226            }
1227            // ensuring to only add items belonging to the current site
1228            if (!resource.getRootPath().startsWith(cms.getRequestContext().getSiteRoot())) {
1229                m_treeContainer.removeItemRecursively(id);
1230                return;
1231            }
1232
1233            CmsResource parent = cms.readParentFolder(id);
1234            CmsUUID parentId = null;
1235            if (parent != null) {
1236                parentId = parent.getStructureId();
1237            } else {
1238                LOG.debug("Parent for resource '" + resource.getRootPath() + "' is null");
1239            }
1240            Item resourceItem = m_treeContainer.getItem(id);
1241            if (resourceItem != null) {
1242                // use the root path as name in case of the root item
1243                String resName = parentId == null ? resource.getRootPath() : resource.getName();
1244                resourceItem.getItemProperty(CmsResourceTableProperty.PROPERTY_RESOURCE_NAME).setValue(resName);
1245                CmsResourceUtil resUtil = new CmsResourceUtil(cms, resource);
1246                String iconHTML = CmsResourceIcon.getTreeCaptionHTML(resName, resUtil, null, false);
1247                resourceItem.getItemProperty(CmsResourceTableProperty.PROPERTY_TREE_CAPTION).setValue(iconHTML);
1248                resourceItem.getItemProperty(CmsResourceTableProperty.PROPERTY_STATE).setValue(resource.getState());
1249                if (parentId != null) {
1250                    m_treeContainer.setParent(resource.getStructureId(), parentId);
1251                }
1252            } else {
1253                addTreeItem(cms, resource, parentId, false, m_treeContainer);
1254            }
1255            m_fileTree.markAsDirty();
1256        } catch (CmsVfsResourceNotFoundException e) {
1257            m_treeContainer.removeItemRecursively(id);
1258            LOG.debug(e.getLocalizedMessage(), e);
1259        } catch (CmsException e) {
1260            CmsErrorDialog.showErrorDialog(e);
1261            LOG.error(e.getLocalizedMessage(), e);
1262        }
1263    }
1264
1265    /**
1266     * Updates the tree items with the given ids.<p>
1267     *
1268     * @param id the
1269     */
1270    public void updateTree(CmsUUID id) {
1271
1272        CmsObject cms = A_CmsUI.getCmsObject();
1273        updateResourceInTree(cms, id);
1274
1275    }
1276
1277    /**
1278     * Clears the given tree level.<p>
1279     *
1280     * @param parentId the parent id
1281     */
1282    protected void clearTreeLevel(CmsUUID parentId) {
1283
1284        // create a new list to avoid concurrent modifications
1285        Collection<?> children = m_treeContainer.getChildren(parentId);
1286        // may be null when monkey clicking
1287        if (children != null) {
1288            List<Object> childIds = new ArrayList<Object>(m_treeContainer.getChildren(parentId));
1289            for (Object childId : childIds) {
1290                m_treeContainer.removeItemRecursively(childId);
1291            }
1292        }
1293    }
1294
1295    /**
1296     * Reads the given folder.<p>
1297     *
1298     * @param folderId the folder id
1299     *
1300     * @throws CmsException in case reading the folder fails
1301     */
1302    protected void readFolder(CmsUUID folderId) throws CmsException {
1303
1304        readFolder(folderId, true);
1305    }
1306
1307    /**
1308     * Reads the given folder.<p>
1309     *
1310     * @param folderId the folder id
1311     * @param clearFilter <code>true</code> to clear the search filter
1312     *
1313     * @throws CmsException in case reading the folder fails
1314     */
1315    protected void readFolder(CmsUUID folderId, boolean clearFilter) throws CmsException {
1316
1317        CmsObject cms = A_CmsUI.getCmsObject();
1318        if (clearFilter) {
1319            m_searchField.clear();
1320        }
1321        CmsResource folder = cms.readResource(folderId, FOLDERS);
1322        m_currentFolder = folderId;
1323        String folderPath = cms.getSitePath(folder);
1324        if (OpenCms.getSiteManager().isSharedFolder(cms.getRequestContext().getSiteRoot())) {
1325            folderPath = folderPath.substring(cms.getRequestContext().getSiteRoot().length());
1326        }
1327        setPathInfo(folderPath);
1328        List<CmsResource> childResources = cms.readResources(folder, FILES_N_FOLDERS, false);
1329        m_fileTable.fillTable(cms, childResources, clearFilter);
1330        boolean hasFolderChild = false;
1331        for (CmsResource child : childResources) {
1332            if (child.isFolder()) {
1333                hasFolderChild = true;
1334                break;
1335            }
1336        }
1337        m_treeContainer.setChildrenAllowed(folderId, hasFolderChild);
1338        String sitePath = folder.getRootPath().equals(cms.getRequestContext().getSiteRoot() + "/") ? "" : folderPath;
1339        String state = new StateBean(
1340            cms.getRequestContext().getSiteRoot(),
1341            sitePath,
1342            cms.getRequestContext().getCurrentProject().getUuid().toString()).asString();
1343        if (LOG.isDebugEnabled()) {
1344            String p = RandomStringUtils.randomAlphanumeric(5) + " readFolder ";
1345            LOG.debug(p + "siteRoot = " + cms.getRequestContext().getSiteRoot());
1346            LOG.debug(p + "folder = " + folder.getRootPath());
1347            LOG.debug(p + "folderPath = " + folderPath);
1348            LOG.debug(p + "sitePath = " + sitePath);
1349            LOG.debug(p + "state = " + state);
1350            LOG.debug(p + "m_currentState = " + m_currentState);
1351            LOG.debug(p + "m_currentState.asString = " + StateBean.parse(m_currentState).asString());
1352        }
1353        if ((m_currentState == null) || !(state).equals(StateBean.parse(m_currentState).asString())) {
1354            m_currentState = state;
1355            CmsAppWorkplaceUi.get().changeCurrentAppState(m_currentState);
1356        }
1357        m_locationCache.setFileExplorerLocation(cms.getRequestContext().getSiteRoot(), sitePath);
1358        updateUploadButton(folder);
1359        if (!m_fileTree.isExpanded(folderId)) {
1360            expandCurrentFolder();
1361        }
1362        if (!m_fileTree.isSelected(folderId)) {
1363            m_fileTree.select(folderId);
1364        }
1365    }
1366
1367    /**
1368     * Updates the current folder and removes the given resource items.<p>
1369     *
1370     * @param removeIds the resource item ids to remove
1371     */
1372    protected void updateCurrentFolder(Collection<CmsUUID> removeIds) {
1373
1374        m_fileTable.update(removeIds, true);
1375        CmsObject cms = A_CmsUI.getCmsObject();
1376        try {
1377            // current folder may be filtered, so we clear the filters and restore them later
1378            // to make updates work for filtered out resources
1379            m_fileTable.saveFilters();
1380            m_fileTable.clearFilters();
1381            CmsResource folder = cms.readResource(m_currentFolder, FOLDERS);
1382            List<CmsResource> childResources = cms.readResources(cms.getSitePath(folder), FILES_N_FOLDERS, false);
1383            Set<CmsUUID> ids = new HashSet<CmsUUID>();
1384            for (CmsResource child : childResources) {
1385                ids.add(child.getStructureId());
1386            }
1387            m_fileTable.update(ids, false);
1388        } catch (CmsException e) {
1389            CmsErrorDialog.showErrorDialog(e);
1390            LOG.error(e.getLocalizedMessage(), e);
1391        } finally {
1392            m_fileTable.restoreFilters();
1393        }
1394    }
1395
1396    /**
1397     * Expands the currently viewed folder in the tree.<p>
1398     */
1399    void expandCurrentFolder() {
1400
1401        if (m_currentFolder != null) {
1402            m_treeContainer.setChildrenAllowed(m_currentFolder, true);
1403            m_fileTree.expandItem(m_currentFolder);
1404        }
1405    }
1406
1407    /**
1408     * Filters the file table.<p>
1409     *
1410     * @param search the search term
1411     */
1412    void filterTable(String search) {
1413
1414        m_fileTable.filterTable(search);
1415    }
1416
1417    /**
1418     * Handles the file tree click events.<p>
1419     *
1420     * @param event the event
1421     */
1422    void handleFileTreeClick(ItemClickEvent event) {
1423
1424        Item resourceItem = m_treeContainer.getItem(event.getItemId());
1425        if ((resourceItem.getItemProperty(CmsResourceTableProperty.PROPERTY_DISABLED).getValue() == null)
1426            || !((Boolean)resourceItem.getItemProperty(
1427                CmsResourceTableProperty.PROPERTY_DISABLED).getValue()).booleanValue()) {
1428            // don't handle disabled item clicks
1429            try {
1430                readFolder((CmsUUID)event.getItemId());
1431            } catch (CmsException e) {
1432                CmsErrorDialog.showErrorDialog(
1433                    CmsVaadinUtils.getMessageText(Messages.ERR_EXPLORER_CAN_NOT_READ_RESOURCE_1, event.getItemId()),
1434                    e);
1435                LOG.error(e.getLocalizedMessage(), e);
1436            }
1437        }
1438    }
1439
1440    /**
1441     * Handles the file tree value changes.<p>
1442     */
1443    void handleFileTreeValueChange() {
1444
1445        Item resourceItem = m_treeContainer.getItem(m_fileTree.getValue());
1446        if (resourceItem != null) {
1447            if ((resourceItem.getItemProperty(CmsResourceTableProperty.PROPERTY_DISABLED).getValue() != null)
1448                && ((Boolean)resourceItem.getItemProperty(
1449                    CmsResourceTableProperty.PROPERTY_DISABLED).getValue()).booleanValue()) {
1450                // in case the folder is disabled due to missing visibility, reset the value to the previous one
1451                m_fileTree.setValue(m_selectTreeFolder);
1452
1453            } else {
1454                m_selectTreeFolder = (CmsUUID)m_fileTree.getValue();
1455            }
1456        }
1457    }
1458
1459    /**
1460     * Opens the new resource dialog.<p>
1461     */
1462    void onClickNew() {
1463
1464        try {
1465            CmsObject cms = A_CmsUI.getCmsObject();
1466
1467            CmsResource folderRes = cms.readResource(m_currentFolder, CmsResourceFilter.IGNORE_EXPIRATION);
1468            I_CmsDialogContext newDialogContext = getDialogContext();
1469
1470            CmsNewDialog newDialog = new CmsNewDialog(folderRes, newDialogContext);
1471            newDialogContext.start(
1472                CmsVaadinUtils.getMessageText(org.opencms.ui.Messages.GUI_NEWRESOURCEDIALOG_TITLE_0),
1473                newDialog);
1474
1475        } catch (Exception e) {
1476            LOG.error(e.getLocalizedMessage(), e);
1477        }
1478    }
1479
1480    /**
1481     * Opens the given site path.<p>
1482     *
1483     * @param path the path
1484     */
1485    void openPath(String path) {
1486
1487        openPath(path, false);
1488    }
1489
1490    /**
1491     * Opens the given site path.<p>
1492     *
1493     * @param path the path
1494     * @param initTree <code>true</code> in case the tree needs to be initialized
1495     */
1496    void openPath(String path, boolean initTree) {
1497
1498        CmsObject cms = A_CmsUI.getCmsObject();
1499        if (path == null) {
1500            String siteRoot = cms.getRequestContext().getSiteRoot();
1501            path = m_locationCache.getFileExplorerLocation(siteRoot);
1502            if (path == null) {
1503                path = "";
1504            } else if (OpenCms.getSiteManager().startsWithShared(path)) {
1505                path = path.substring(siteRoot.length());
1506            }
1507        }
1508
1509        CmsSiteManagerImpl siteManager = OpenCms.getSiteManager();
1510        if (siteManager.isSharedFolder(cms.getRequestContext().getSiteRoot()) && siteManager.startsWithShared(path)) {
1511            // siteManager.getSharedFolder() has a trailing slash - we want to cut off the shared folder, but keep a leading slash in path
1512            path = path.substring(siteManager.getSharedFolder().length() - 1);
1513            if ("".equals(path)) {
1514                path = "/";
1515            }
1516        }
1517
1518        boolean existsPath = CmsStringUtil.isNotEmptyOrWhitespaceOnly(path)
1519            && cms.existsResource(path, FILES_N_FOLDERS);
1520
1521        if (path.startsWith("/")) {
1522            // remove a leading slash to avoid an empty first path fragment
1523            path = path.substring(1);
1524        }
1525
1526        String[] pathItems = path.split("/");
1527
1528        if (initTree) {
1529            try {
1530                initFolderTree(existsPath);
1531            } catch (CmsException e) {
1532                CmsErrorDialog.showErrorDialog(
1533                    CmsVaadinUtils.getMessageText(Messages.ERR_EXPLORER_CAN_NOT_OPEN_PATH_1, path),
1534                    e);
1535                LOG.error(e.getLocalizedMessage(), e);
1536                return;
1537            }
1538        }
1539
1540        Collection<?> rootItems = m_treeContainer.rootItemIds();
1541        if (rootItems.size() != 1) {
1542            throw new RuntimeException("Illeagal state, folder tree has " + rootItems.size() + " children");
1543        }
1544
1545        CmsUUID folderId = (CmsUUID)rootItems.iterator().next();
1546        if (initTree) {
1547            if (existsPath) {
1548                m_expandListener.setOpenPathFragment(pathItems[0]);
1549            }
1550            m_fileTree.expandItem(folderId);
1551            m_expandListener.setOpenPathFragment(null);
1552        }
1553        Collection<?> children = m_treeContainer.getChildren(folderId);
1554        for (int i = 0; i < pathItems.length; i++) {
1555            if (CmsStringUtil.isEmptyOrWhitespaceOnly(pathItems[i])) {
1556                continue;
1557            }
1558            CmsUUID level = null;
1559            if (children != null) {
1560                for (Object id : children) {
1561                    if (m_treeContainer.getItem(id).getItemProperty(
1562                        CmsResourceTableProperty.PROPERTY_RESOURCE_NAME).getValue().equals(pathItems[i])) {
1563                        level = (CmsUUID)id;
1564                        m_expandListener.setOpenPathFragment(
1565                            existsPath && (i < (pathItems.length - 1)) ? pathItems[i + 1] : null);
1566                        m_fileTree.expandItem(level);
1567                        m_expandListener.setOpenPathFragment(null);
1568                        break;
1569                    }
1570                }
1571            }
1572            if ((level == null) || level.equals(folderId)) {
1573                break;
1574            }
1575            folderId = level;
1576            children = m_treeContainer.getChildren(folderId);
1577        }
1578        try {
1579            readFolder(folderId);
1580        } catch (CmsException e) {
1581            CmsErrorDialog.showErrorDialog(
1582                CmsVaadinUtils.getMessageText(Messages.ERR_EXPLORER_CAN_NOT_OPEN_PATH_1, path),
1583                e);
1584            LOG.error(e.getLocalizedMessage(), e);
1585            return;
1586        }
1587    }
1588
1589    /**
1590     * Reads the given tree level.<p>
1591     *
1592     * @param parentId the parent id
1593     * @param openPathFragment the open path fragment
1594     */
1595    void readTreeLevel(CmsUUID parentId, String openPathFragment) {
1596
1597        CmsObject cms = A_CmsUI.getCmsObject();
1598        boolean openedFragment = false;
1599        try {
1600            CmsResource parent = cms.readResource(parentId, CmsResourceFilter.IGNORE_EXPIRATION);
1601            String folderPath = cms.getSitePath(parent);
1602            List<CmsResource> folderResources = cms.readResources(folderPath, FOLDERS, false);
1603
1604            // sets the parent to leaf mode, in case no child folders are present
1605            m_treeContainer.setChildrenAllowed(parentId, !folderResources.isEmpty());
1606
1607            for (CmsResource resource : folderResources) {
1608                addTreeItem(cms, resource, parentId, false, m_treeContainer);
1609                openedFragment = openedFragment || resource.getName().equals(openPathFragment);
1610            }
1611            if (!openedFragment && (openPathFragment != null)) {
1612                CmsResource resource = cms.readResource(
1613                    CmsStringUtil.joinPaths(folderPath, openPathFragment),
1614                    CmsResourceFilter.IGNORE_EXPIRATION);
1615                addTreeItem(cms, resource, parentId, true, m_treeContainer);
1616            }
1617
1618            m_fileTree.markAsDirtyRecursive();
1619        } catch (CmsException e) {
1620            CmsErrorDialog.showErrorDialog(e);
1621            LOG.error(e.getLocalizedMessage(), e);
1622        }
1623    }
1624
1625    /**
1626     * Selects the item with the given id.<p>
1627     *
1628     * @param itemId the item id
1629     */
1630    void selectTreeItem(CmsUUID itemId) {
1631
1632        m_fileTree.select(itemId);
1633    }
1634
1635    /**
1636     * Shows and hides the path bread crumb.<p>
1637     *
1638     * @param show <code>true</code> to show the bread crumb
1639     */
1640    void showCrumbs(boolean show) {
1641
1642        if (show) {
1643            m_crumbs.removeStyleName(OpenCmsTheme.HIDDEN);
1644            CmsAppWorkplaceUi.get().enableGlobalShortcuts();
1645        } else {
1646            m_crumbs.addStyleName(OpenCmsTheme.HIDDEN);
1647            CmsAppWorkplaceUi.get().disableGlobalShortcuts();
1648        }
1649    }
1650
1651    /**
1652     * Shows the parent folder, if available.<p>
1653     */
1654    void showParentFolder() {
1655
1656        CmsUUID parentId = (CmsUUID)m_treeContainer.getParent(m_currentFolder);
1657        if (parentId != null) {
1658            Item resourceItem = m_treeContainer.getItem(parentId);
1659            if ((resourceItem.getItemProperty(CmsResourceTableProperty.PROPERTY_DISABLED).getValue() == null)
1660                || !((Boolean)resourceItem.getItemProperty(
1661                    CmsResourceTableProperty.PROPERTY_DISABLED).getValue()).booleanValue()) {
1662                // don't open disabled parent folders
1663                try {
1664                    readFolder(parentId);
1665                    m_fileTree.select(parentId);
1666                } catch (CmsException e) {
1667                    CmsErrorDialog.showErrorDialog(
1668                        CmsVaadinUtils.getMessageText(Messages.ERR_EXPLORER_CAN_NOT_READ_RESOURCE_1, parentId),
1669                        e);
1670                    LOG.error(e.getLocalizedMessage(), e);
1671                }
1672            }
1673        }
1674    }
1675
1676    /**
1677     * Toggles between the online and the offline project.<p>
1678     */
1679    void toggleOnlineOffline() {
1680
1681        CmsObject cms = A_CmsUI.getCmsObject();
1682        CmsProject targetProject = null;
1683        if (cms.getRequestContext().getCurrentProject().isOnlineProject()) {
1684            targetProject = A_CmsUI.get().getLastOfflineProject();
1685            if (targetProject == null) {
1686                CmsUserSettings userSettings = new CmsUserSettings(cms);
1687                try {
1688                    CmsProject project = cms.readProject(userSettings.getStartProject());
1689                    if (!project.isOnlineProject()
1690                        && OpenCms.getOrgUnitManager().getAllAccessibleProjects(
1691                            cms,
1692                            project.getOuFqn(),
1693                            false).contains(project)) {
1694                        targetProject = project;
1695                    }
1696                } catch (CmsException e) {
1697                    LOG.debug("Error reading user start project.", e);
1698                }
1699                if (targetProject == null) {
1700                    List<CmsProject> availableProjects = CmsVaadinUtils.getAvailableProjects(cms);
1701                    for (CmsProject project : availableProjects) {
1702                        if (!project.isOnlineProject()) {
1703                            targetProject = project;
1704                            break;
1705                        }
1706                    }
1707                }
1708            }
1709        } else {
1710            try {
1711                targetProject = cms.readProject(CmsProject.ONLINE_PROJECT_ID);
1712            } catch (CmsException e) {
1713                LOG.debug("Error reading online project.", e);
1714            }
1715        }
1716        if (targetProject != null) {
1717            A_CmsUI.get().changeProject(targetProject);
1718            onSiteOrProjectChange(targetProject, null);
1719            Notification.show(
1720                CmsVaadinUtils.getMessageText(Messages.GUI_SWITCHED_TO_PROJECT_1, targetProject.getName()));
1721        }
1722    }
1723
1724    /**
1725     * Adds the given properties to the tree container.<p>
1726     *
1727     * @param properties the properties tom add
1728     */
1729    private void addTreeContainerProperties(CmsResourceTableProperty... properties) {
1730
1731        for (CmsResourceTableProperty property : properties) {
1732            m_treeContainer.addContainerProperty(property, property.getColumnType(), property.getDefaultValue());
1733        }
1734    }
1735
1736    /**
1737     * Adds an item to the folder tree.<p>
1738     *
1739     * @param cms the cms context
1740     * @param resource the folder resource
1741     * @param parentId the parent folder id
1742     * @param disabled in case the item is disabled, used for non visible parent folders
1743     * @param container the data container
1744     */
1745    private void addTreeItem(
1746        CmsObject cms,
1747        CmsResource resource,
1748        CmsUUID parentId,
1749        boolean disabled,
1750        HierarchicalContainer container) {
1751
1752        CmsResourceUtil resUtil = new CmsResourceUtil(cms, resource);
1753        Item resourceItem = container.getItem(resource.getStructureId());
1754        if (resourceItem == null) {
1755            resourceItem = container.addItem(resource.getStructureId());
1756        }
1757
1758        // use the root path as name in case of the root item
1759        String resName = parentId == null ? resource.getRootPath() : resource.getName();
1760        resourceItem.getItemProperty(CmsResourceTableProperty.PROPERTY_RESOURCE_NAME).setValue(resName);
1761        resourceItem.getItemProperty(CmsResourceTableProperty.PROPERTY_STATE).setValue(resource.getState());
1762
1763        resourceItem.getItemProperty(CmsResourceTableProperty.PROPERTY_INSIDE_PROJECT).setValue(
1764            Boolean.valueOf(resUtil.isInsideProject()));
1765        resourceItem.getItemProperty(CmsResourceTableProperty.PROPERTY_RELEASED_NOT_EXPIRED).setValue(
1766            Boolean.valueOf(resUtil.isReleasedAndNotExpired()));
1767        String iconHTML = CmsResourceIcon.getTreeCaptionHTML(resName, resUtil, null, false);
1768
1769        resourceItem.getItemProperty(CmsResourceTableProperty.PROPERTY_TREE_CAPTION).setValue(iconHTML);
1770        if (disabled) {
1771            resourceItem.getItemProperty(CmsResourceTableProperty.PROPERTY_DISABLED).setValue(Boolean.TRUE);
1772        }
1773        if (parentId != null) {
1774            container.setChildrenAllowed(parentId, true);
1775            container.setParent(resource.getStructureId(), parentId);
1776        }
1777    }
1778
1779    /**
1780     * Creates the site selector combo box.<p>
1781     *
1782     * @param cms the current cms context
1783     *
1784     * @return the combo box
1785     */
1786    private ComboBox createSiteSelect(CmsObject cms) {
1787
1788        final List<CmsExtendedSiteSelector.SiteSelectorOption> sites = CmsExtendedSiteSelector.getExplorerSiteSelectorOptions(
1789            cms,
1790            true);
1791        final BeanItemContainer<CmsExtendedSiteSelector.SiteSelectorOption> container = new BeanItemContainer<>(
1792            CmsExtendedSiteSelector.SiteSelectorOption.class);
1793        for (CmsExtendedSiteSelector.SiteSelectorOption option : sites) {
1794            container.addItem(option);
1795        }
1796        ComboBox combo = new ComboBox(null, container);
1797        combo.setTextInputAllowed(true);
1798        combo.setNullSelectionAllowed(false);
1799        combo.setWidth("200px");
1800        combo.setInputPrompt(
1801            Messages.get().getBundle(UI.getCurrent().getLocale()).key(Messages.GUI_EXPLORER_CLICK_TO_EDIT_0));
1802        combo.setItemCaptionPropertyId("label");
1803        combo.select(new CmsExtendedSiteSelector.SiteSelectorOption(cms.getRequestContext().getSiteRoot(), null, null));
1804        combo.setFilteringMode(FilteringMode.CONTAINS);
1805        combo.addValueChangeListener(new ValueChangeListener() {
1806
1807            private static final long serialVersionUID = 1L;
1808
1809            /** Flag used to temporarily disable the standard listener behavior, used when we change the combo box's value programatically. */
1810            private boolean m_disabled;
1811
1812            public void valueChange(ValueChangeEvent event) {
1813
1814                if (m_disabled) {
1815                    return;
1816                }
1817                CmsExtendedSiteSelector.SiteSelectorOption option = (CmsExtendedSiteSelector.SiteSelectorOption)(event.getProperty().getValue());
1818                if (container.containsId(option)) {
1819                    changeSite(option.getSite(), option.getPath());
1820                    m_appContext.updateOnChange();
1821                    container.removeAllContainerFilters();
1822                    if (option.getPath() != null) {
1823                        try {
1824                            m_disabled = true;
1825                            combo.select(new CmsExtendedSiteSelector.SiteSelectorOption(option.getSite(), null, null));
1826                        } finally {
1827                            m_disabled = false;
1828                        }
1829                    }
1830                }
1831            }
1832        });
1833        if (Page.getCurrent().getBrowserWindowHeight() > 650) {
1834            combo.setPageLength(20);
1835        }
1836        return combo;
1837    }
1838
1839    /**
1840     * Returns the site path from the current state.<p>
1841     *
1842     * @return the site path
1843     */
1844    private String getPathFromState() {
1845
1846        return StateBean.parse(m_currentState).getFolder();
1847    }
1848
1849    /**
1850     * Returns the project id from the current state.<p>
1851     *
1852     * @return the project id
1853     */
1854    private CmsUUID getProjectIdFromState() {
1855
1856        String projectIdStr = StateBean.parse(m_currentState).getProjectId();
1857        if (CmsUUID.isValidUUID(projectIdStr)) {
1858            return new CmsUUID(projectIdStr);
1859
1860        }
1861        return null;
1862
1863    }
1864
1865    /**
1866     * Returns selected resource UUID from state (empty if not set).<p>
1867     *
1868     * @param state current state
1869     * @return uuid as string
1870     */
1871    private String getSelectionFromState(String state) {
1872
1873        return StateBean.parse(state).getSelectedResource();
1874    }
1875
1876    /**
1877     * Returns the site root from the current state.<p>
1878     *
1879     * @return the site root
1880     */
1881    private String getSiteRootFromState() {
1882
1883        String siteRoot = StateBean.parse(m_currentState).getSiteRoot();
1884        return siteRoot;
1885    }
1886
1887    /**
1888     * Populates the folder tree.<p>
1889     *
1890     * @param showInvisible to show non visible root folder as disabled
1891     *
1892     * @throws CmsException in case reading the root folder fails
1893     */
1894    private void initFolderTree(boolean showInvisible) throws CmsException {
1895
1896        CmsObject cms = A_CmsUI.getCmsObject();
1897        m_treeContainer.removeAllItems();
1898        try {
1899            CmsResource siteRoot = cms.readResource("/", FOLDERS);
1900            addTreeItem(cms, siteRoot, null, false, m_treeContainer);
1901        } catch (CmsException e) {
1902            if (showInvisible) {
1903                CmsResource siteRoot = cms.readResource("/", CmsResourceFilter.IGNORE_EXPIRATION);
1904                addTreeItem(cms, siteRoot, null, true, m_treeContainer);
1905            } else {
1906                throw e;
1907            }
1908        }
1909
1910    }
1911
1912    /**
1913     * Initializes the app specific toolbar buttons.<p>
1914     *
1915     * @param context the UI context
1916     */
1917    private void initToolbarButtons(I_CmsAppUIContext context) {
1918
1919        m_publishButton = context.addPublishButton(new I_CmsUpdateListener<String>() {
1920
1921            public void onUpdate(List<String> updatedItems) {
1922
1923                updateAll(false);
1924            }
1925
1926        });
1927
1928        m_newButton = CmsToolBar.createButton(
1929            FontOpenCms.WAND,
1930            CmsVaadinUtils.getMessageText(Messages.GUI_NEW_RESOURCE_TITLE_0));
1931        if (CmsAppWorkplaceUi.isOnlineProject()) {
1932            m_newButton.setEnabled(false);
1933            m_newButton.setDescription(CmsVaadinUtils.getMessageText(Messages.GUI_TOOLBAR_NOT_AVAILABLE_ONLINE_0));
1934        }
1935        m_newButton.addClickListener(new ClickListener() {
1936
1937            private static final long serialVersionUID = 1L;
1938
1939            public void buttonClick(ClickEvent event) {
1940
1941                onClickNew();
1942            }
1943
1944        });
1945
1946        context.addToolbarButton(m_newButton);
1947
1948        m_uploadButton = new CmsUploadButton(FontOpenCms.UPLOAD, "/");
1949        m_specialUploadButton = new Button(FontOpenCms.UPLOAD);
1950        m_uploadButton.addStyleName(ValoTheme.BUTTON_BORDERLESS);
1951        m_uploadButton.addStyleName(OpenCmsTheme.TOOLBAR_BUTTON);
1952        if (CmsAppWorkplaceUi.isOnlineProject()) {
1953            m_uploadButton.setEnabled(false);
1954            m_specialUploadButton.setEnabled(false);
1955            m_uploadButton.setDescription(CmsVaadinUtils.getMessageText(Messages.GUI_TOOLBAR_NOT_AVAILABLE_ONLINE_0));
1956            m_specialUploadButton.setDescription(
1957                CmsVaadinUtils.getMessageText(Messages.GUI_TOOLBAR_NOT_AVAILABLE_ONLINE_0));
1958        } else {
1959            m_uploadButton.setDescription(CmsVaadinUtils.getMessageText(Messages.GUI_UPLOAD_BUTTON_TITLE_0));
1960            m_specialUploadButton.setDescription(CmsVaadinUtils.getMessageText(Messages.GUI_UPLOAD_BUTTON_TITLE_0));
1961        }
1962        m_uploadButton.addUploadListener(new I_UploadListener() {
1963
1964            public void onUploadFinished(List<String> uploadedFiles) {
1965
1966                updateAll(true);
1967            }
1968        });
1969        m_specialUploadButton.addStyleName(ValoTheme.BUTTON_BORDERLESS);
1970        m_specialUploadButton.addStyleName(OpenCmsTheme.TOOLBAR_BUTTON);
1971        m_specialUploadButton.addClickListener(event -> {
1972            try {
1973                @SuppressWarnings("unchecked")
1974                Class<I_CmsWorkplaceAction> actionClass = (Class<I_CmsWorkplaceAction>)getClass().getClassLoader().loadClass(
1975                    m_uploadAction);
1976                I_CmsWorkplaceAction action = actionClass.newInstance();
1977                List<CmsResource> resources = new ArrayList<>();
1978                resources.add(m_uploadFolder);
1979                CmsExplorerDialogContext uploadContext = new CmsExplorerDialogContext(
1980                    ContextType.fileTable,
1981                    m_fileTable,
1982                    this,
1983                    resources);
1984                action.executeAction(uploadContext);
1985            } catch (Exception e) {
1986                LOG.error(e.getLocalizedMessage(), e);
1987                CmsErrorDialog.showErrorDialog(e);
1988            }
1989
1990        });
1991
1992        context.addToolbarButton(m_uploadButton);
1993        context.addToolbarButton(m_specialUploadButton);
1994    }
1995
1996    /**
1997     * Normalizes the state string. Ensuring it starts with the right number of slashes resolving an issue with the vaadin state handling.<p>
1998     *
1999     * @param state the state string
2000     *
2001     * @return the normalized state string
2002     */
2003    private String normalizeState(String state) {
2004
2005        String result = "";
2006        if (state.contains(A_CmsWorkplaceApp.PARAM_SEPARATOR)) {
2007            result = state;
2008            while (result.startsWith(CmsAppWorkplaceUi.WORKPLACE_STATE_SEPARATOR)) {
2009                result = result.substring(1);
2010            }
2011        }
2012        return result;
2013    }
2014
2015    /**
2016     * Sets the current path info.<p>
2017     *
2018     * @param path the path
2019     */
2020    private void setPathInfo(String path) {
2021
2022        m_infoPath.setValue(path);
2023
2024        // generate the path bread crumb
2025        m_crumbs.removeAllComponents();
2026        int i = path.indexOf("/");
2027        String openPath = "";
2028        while (i >= 0) {
2029            String fragment = path.substring(0, i + 1);
2030            openPath += fragment;
2031            path = path.substring(i + 1);
2032            i = path.indexOf("/");
2033            Button crumb = new Button(fragment, m_crumbListener);
2034            crumb.setData(openPath);
2035            crumb.addStyleName(ValoTheme.BUTTON_LINK);
2036            m_crumbs.addComponent(crumb);
2037        }
2038    }
2039
2040    /**
2041     * Sets the toolbar buttons enabled.<p>
2042     *
2043     * @param enabled the enabled flag
2044     */
2045    private void setToolbarButtonsEnabled(boolean enabled) {
2046
2047        m_publishButton.setEnabled(enabled);
2048        m_newButton.setEnabled(enabled);
2049        m_uploadButton.setEnabled(enabled);
2050        m_specialUploadButton.setEnabled(enabled);
2051        if (enabled) {
2052            m_publishButton.setDescription(CmsVaadinUtils.getMessageText(Messages.GUI_PUBLISH_BUTTON_TITLE_0));
2053            m_newButton.setDescription(CmsVaadinUtils.getMessageText(Messages.GUI_NEW_RESOURCE_TITLE_0));
2054            m_uploadButton.setDescription(CmsVaadinUtils.getMessageText(Messages.GUI_UPLOAD_BUTTON_TITLE_0));
2055            m_specialUploadButton.setDescription(CmsVaadinUtils.getMessageText(Messages.GUI_UPLOAD_BUTTON_TITLE_0));
2056        } else {
2057            m_publishButton.setDescription(CmsVaadinUtils.getMessageText(Messages.GUI_TOOLBAR_NOT_AVAILABLE_ONLINE_0));
2058            m_newButton.setDescription(CmsVaadinUtils.getMessageText(Messages.GUI_TOOLBAR_NOT_AVAILABLE_ONLINE_0));
2059            m_uploadButton.setDescription(CmsVaadinUtils.getMessageText(Messages.GUI_TOOLBAR_NOT_AVAILABLE_ONLINE_0));
2060            m_specialUploadButton.setDescription(
2061                CmsVaadinUtils.getMessageText(Messages.GUI_TOOLBAR_NOT_AVAILABLE_ONLINE_0));
2062        }
2063    }
2064
2065    /**
2066     * Updates the upload button state based on the current folder.
2067     *
2068     * @param folder the current folder
2069     */
2070    private void updateUploadButton(CmsResource folder) {
2071
2072        String uploadAction = null;
2073        m_uploadFolder = folder;
2074        try {
2075            uploadAction = OpenCms.getResourceManager().getResourceType(folder).getConfiguration().get(
2076                "gallery.upload.action");
2077        } catch (Exception e) {
2078            // Resource type not found?
2079            LOG.error(e.getLocalizedMessage(), e);
2080        }
2081        m_uploadAction = uploadAction;
2082        if (uploadAction != null) {
2083            m_uploadButton.setVisible(false);
2084            m_specialUploadButton.setVisible(true);
2085            m_uploadArea.setTargetFolder(null);
2086        } else {
2087            boolean enabled = OpenCms.getWorkplaceManager().getUploadRestriction().getUploadRestrictionInfo(
2088                A_CmsUI.getCmsObject()).isUploadEnabled(folder.getRootPath());
2089            m_uploadButton.setVisible(enabled);
2090            m_specialUploadButton.setVisible(false);
2091            m_uploadButton.setTargetFolder(folder.getRootPath());
2092            m_uploadArea.setTargetFolder(folder.getRootPath());
2093        }
2094    }
2095}