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