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