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