001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (C) Alkacon Software (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.components;
029
030import static org.opencms.ui.components.CmsResourceTableProperty.PROPERTY_CACHE;
031import static org.opencms.ui.components.CmsResourceTableProperty.PROPERTY_COPYRIGHT;
032import static org.opencms.ui.components.CmsResourceTableProperty.PROPERTY_DATE_CREATED;
033import static org.opencms.ui.components.CmsResourceTableProperty.PROPERTY_DATE_EXPIRED;
034import static org.opencms.ui.components.CmsResourceTableProperty.PROPERTY_DATE_MODIFIED;
035import static org.opencms.ui.components.CmsResourceTableProperty.PROPERTY_DATE_RELEASED;
036import static org.opencms.ui.components.CmsResourceTableProperty.PROPERTY_INSIDE_PROJECT;
037import static org.opencms.ui.components.CmsResourceTableProperty.PROPERTY_INTERNAL_RESOURCE_TYPE;
038import static org.opencms.ui.components.CmsResourceTableProperty.PROPERTY_IN_NAVIGATION;
039import static org.opencms.ui.components.CmsResourceTableProperty.PROPERTY_IS_FOLDER;
040import static org.opencms.ui.components.CmsResourceTableProperty.PROPERTY_NAVIGATION_POSITION;
041import static org.opencms.ui.components.CmsResourceTableProperty.PROPERTY_NAVIGATION_TEXT;
042import static org.opencms.ui.components.CmsResourceTableProperty.PROPERTY_PERMISSIONS;
043import static org.opencms.ui.components.CmsResourceTableProperty.PROPERTY_PROJECT;
044import static org.opencms.ui.components.CmsResourceTableProperty.PROPERTY_RELEASED_NOT_EXPIRED;
045import static org.opencms.ui.components.CmsResourceTableProperty.PROPERTY_RESOURCE_NAME;
046import static org.opencms.ui.components.CmsResourceTableProperty.PROPERTY_RESOURCE_TYPE;
047import static org.opencms.ui.components.CmsResourceTableProperty.PROPERTY_SIZE;
048import static org.opencms.ui.components.CmsResourceTableProperty.PROPERTY_STATE;
049import static org.opencms.ui.components.CmsResourceTableProperty.PROPERTY_STATE_NAME;
050import static org.opencms.ui.components.CmsResourceTableProperty.PROPERTY_TITLE;
051import static org.opencms.ui.components.CmsResourceTableProperty.PROPERTY_TYPE_ICON;
052import static org.opencms.ui.components.CmsResourceTableProperty.PROPERTY_USER_CREATED;
053import static org.opencms.ui.components.CmsResourceTableProperty.PROPERTY_USER_LOCKED;
054import static org.opencms.ui.components.CmsResourceTableProperty.PROPERTY_USER_MODIFIED;
055
056import org.opencms.db.CmsResourceState;
057import org.opencms.file.CmsObject;
058import org.opencms.file.CmsResource;
059import org.opencms.file.CmsResourceFilter;
060import org.opencms.file.CmsVfsResourceNotFoundException;
061import org.opencms.main.CmsException;
062import org.opencms.main.CmsLog;
063import org.opencms.main.OpenCms;
064import org.opencms.ui.A_CmsUI;
065import org.opencms.ui.CmsVaadinUtils;
066import org.opencms.ui.I_CmsDialogContext;
067import org.opencms.ui.I_CmsEditPropertyContext;
068import org.opencms.ui.actions.I_CmsDefaultAction;
069import org.opencms.ui.apps.CmsFileExplorerSettings;
070import org.opencms.ui.apps.I_CmsContextProvider;
071import org.opencms.ui.contextmenu.CmsContextMenu;
072import org.opencms.ui.contextmenu.CmsResourceContextMenuBuilder;
073import org.opencms.ui.contextmenu.I_CmsContextMenuBuilder;
074import org.opencms.ui.util.I_CmsItemSorter;
075import org.opencms.util.CmsStringUtil;
076import org.opencms.util.CmsUUID;
077
078import java.util.ArrayList;
079import java.util.Collection;
080import java.util.Collections;
081import java.util.HashSet;
082import java.util.LinkedHashMap;
083import java.util.List;
084import java.util.Map;
085import java.util.Map.Entry;
086import java.util.Set;
087
088import org.apache.commons.logging.Log;
089
090import com.google.common.collect.Lists;
091import com.vaadin.event.FieldEvents.BlurEvent;
092import com.vaadin.event.FieldEvents.BlurListener;
093import com.vaadin.event.ShortcutAction.KeyCode;
094import com.vaadin.event.ShortcutListener;
095import com.vaadin.shared.MouseEventDetails.MouseButton;
096import com.vaadin.ui.Component;
097import com.vaadin.ui.themes.ValoTheme;
098import com.vaadin.v7.data.Container;
099import com.vaadin.v7.data.Container.Filter;
100import com.vaadin.v7.data.Item;
101import com.vaadin.v7.data.Property.ValueChangeEvent;
102import com.vaadin.v7.data.Property.ValueChangeListener;
103import com.vaadin.v7.data.util.DefaultItemSorter;
104import com.vaadin.v7.data.util.IndexedContainer;
105import com.vaadin.v7.data.util.filter.Or;
106import com.vaadin.v7.data.util.filter.SimpleStringFilter;
107import com.vaadin.v7.event.ItemClickEvent;
108import com.vaadin.v7.event.ItemClickEvent.ItemClickListener;
109import com.vaadin.v7.ui.AbstractTextField.TextChangeEventMode;
110import com.vaadin.v7.ui.DefaultFieldFactory;
111import com.vaadin.v7.ui.Field;
112import com.vaadin.v7.ui.Table;
113import com.vaadin.v7.ui.Table.TableDragMode;
114import com.vaadin.v7.ui.TextField;
115
116/**
117 * Table for displaying resources.<p>
118 */
119public class CmsFileTable extends CmsResourceTable {
120
121    /**
122     * File edit handler.<p>
123     */
124    public class FileEditHandler implements BlurListener {
125
126        /** The serial version id. */
127        private static final long serialVersionUID = -2286815522247807054L;
128
129        /**
130         * @see com.vaadin.event.FieldEvents.BlurListener#blur(com.vaadin.event.FieldEvents.BlurEvent)
131         */
132        public void blur(BlurEvent event) {
133
134            stopEdit();
135        }
136    }
137
138    /**
139     * Field factory to enable inline editing of individual file properties.<p>
140     */
141    public class FileFieldFactory extends DefaultFieldFactory {
142
143        /** The serial version id. */
144        private static final long serialVersionUID = 3079590603587933576L;
145
146        /**
147         * @see com.vaadin.ui.DefaultFieldFactory#createField(com.vaadin.v7.data.Container, java.lang.Object, java.lang.Object, com.vaadin.ui.Component)
148         */
149        @Override
150        public Field<?> createField(Container container, Object itemId, Object propertyId, Component uiContext) {
151
152            Field<?> result = null;
153            if (itemId.equals(getEditItemId().toString()) && isEditProperty((CmsResourceTableProperty)propertyId)) {
154                result = super.createField(container, itemId, propertyId, uiContext);
155                result.addStyleName(OpenCmsTheme.INLINE_TEXTFIELD);
156                result.addValidator(m_editHandler);
157                if (result instanceof TextField) {
158                    ((TextField)result).setComponentError(null);
159                    ((TextField)result).addShortcutListener(new ShortcutListener("Cancel edit", KeyCode.ESCAPE, null) {
160
161                        private static final long serialVersionUID = 1L;
162
163                        @Override
164                        public void handleAction(Object sender, Object target) {
165
166                            cancelEdit();
167                        }
168                    });
169                    ((TextField)result).addShortcutListener(new ShortcutListener("Save", KeyCode.ENTER, null) {
170
171                        private static final long serialVersionUID = 1L;
172
173                        @Override
174                        public void handleAction(Object sender, Object target) {
175
176                            stopEdit();
177                        }
178                    });
179                    ((TextField)result).addBlurListener(m_fileEditHandler);
180                    ((TextField)result).setTextChangeEventMode(TextChangeEventMode.LAZY);
181                    ((TextField)result).addTextChangeListener(m_editHandler);
182                }
183                result.focus();
184            }
185            return result;
186        }
187    }
188
189    /**
190     * Extends the default sorting to differentiate between files and folder when sorting by name.<p>
191     * Also allows sorting by navPos property for the Resource icon column.<p>
192     */
193    public static class FileSorter extends DefaultItemSorter implements I_CmsItemSorter {
194
195        /** The serial version id. */
196        private static final long serialVersionUID = 1L;
197
198        /**
199         * @see org.opencms.ui.util.I_CmsItemSorter#getSortableContainerPropertyIds(com.vaadin.v7.data.Container)
200         */
201        public Collection<?> getSortableContainerPropertyIds(Container container) {
202
203            Set<Object> result = new HashSet<Object>();
204            for (Object propId : container.getContainerPropertyIds()) {
205                Class<?> propertyType = container.getType(propId);
206                if (Comparable.class.isAssignableFrom(propertyType)
207                    || propertyType.isPrimitive()
208                    || (propId.equals(CmsResourceTableProperty.PROPERTY_TYPE_ICON)
209                        && container.getContainerPropertyIds().contains(
210                            CmsResourceTableProperty.PROPERTY_NAVIGATION_POSITION))) {
211                    result.add(propId);
212                }
213            }
214            return result;
215        }
216
217        /**
218         * @see com.vaadin.v7.data.util.DefaultItemSorter#compareProperty(java.lang.Object, boolean, com.vaadin.v7.data.Item, com.vaadin.v7.data.Item)
219         */
220        @Override
221        protected int compareProperty(Object propertyId, boolean sortDirection, Item item1, Item item2) {
222
223            //@formatter:off
224            if (CmsResourceTableProperty.PROPERTY_RESOURCE_NAME.equals(propertyId)) {
225                Boolean isFolder1 = (Boolean)item1.getItemProperty(
226                    CmsResourceTableProperty.PROPERTY_IS_FOLDER).getValue();
227                Boolean isFolder2 = (Boolean)item2.getItemProperty(
228                    CmsResourceTableProperty.PROPERTY_IS_FOLDER).getValue();
229                if (!isFolder1.equals(isFolder2)) {
230                    int result = isFolder1.booleanValue() ? -1 : 1;
231                    if (!sortDirection) {
232                        result = result * (-1);
233                    }
234                    return result;
235                }
236            } else if ((CmsResourceTableProperty.PROPERTY_TYPE_ICON.equals(propertyId)
237                || CmsResourceTableProperty.PROPERTY_NAVIGATION_TEXT.equals(propertyId))
238                && (item1.getItemProperty(CmsResourceTableProperty.PROPERTY_NAVIGATION_POSITION) != null)) {
239                    int result;
240                    Float pos1 = (Float)item1.getItemProperty(
241                        CmsResourceTableProperty.PROPERTY_NAVIGATION_POSITION).getValue();
242                    Float pos2 = (Float)item2.getItemProperty(
243                        CmsResourceTableProperty.PROPERTY_NAVIGATION_POSITION).getValue();
244                    if (pos1 == null) {
245                        result = pos2 == null
246                        ? compareProperty(CmsResourceTableProperty.PROPERTY_RESOURCE_NAME, true, item1, item2)
247                        : 1;
248                    } else {
249                        result = pos2 == null ? -1 : Float.compare(pos1.floatValue(), pos2.floatValue());
250                    }
251                    if (!sortDirection) {
252                        result = result * (-1);
253                    }
254                    return result;
255                } else if (((CmsResourceTableProperty)propertyId).getColumnType().equals(String.class)) {
256                    String value1 = (String)item1.getItemProperty(propertyId).getValue();
257                    String value2 = (String)item2.getItemProperty(propertyId).getValue();
258                    // Java collators obtained by java.text.Collator.getInstance(...) ignore spaces, and we don't want to ignore them, so we use
259                    // ICU collators instead
260                    com.ibm.icu.text.Collator collator = com.ibm.icu.text.Collator.getInstance(
261                        com.ibm.icu.util.ULocale.ROOT);
262                    int result = collator.compare(value1, value2);
263                    if (!sortDirection) {
264                        result = -result;
265                    }
266                    return result;
267                }
268            return super.compareProperty(propertyId, sortDirection, item1, item2);
269            //@formatter:on
270        }
271    }
272
273    /**
274     * Handles folder selects in the file table.<p>
275     */
276    public interface I_FolderSelectHandler {
277
278        /**
279         * Called when the folder name is left clicked.<p>
280         *
281         * @param folderId the selected folder id
282         */
283        void onFolderSelect(CmsUUID folderId);
284    }
285
286    /** The default file table columns. */
287    public static final Map<CmsResourceTableProperty, Integer> DEFAULT_TABLE_PROPERTIES;
288
289    /** The logger instance for this class. */
290    static final Log LOG = CmsLog.getLog(CmsFileTable.class);
291
292    /** The serial version id. */
293    private static final long serialVersionUID = 5460048685141699277L;
294
295    static {
296        Map<CmsResourceTableProperty, Integer> defaultProps = new LinkedHashMap<CmsResourceTableProperty, Integer>();
297        defaultProps.put(PROPERTY_TYPE_ICON, Integer.valueOf(0));
298        defaultProps.put(PROPERTY_PROJECT, Integer.valueOf(COLLAPSED));
299        defaultProps.put(PROPERTY_RESOURCE_NAME, Integer.valueOf(0));
300        defaultProps.put(PROPERTY_TITLE, Integer.valueOf(0));
301        defaultProps.put(PROPERTY_NAVIGATION_TEXT, Integer.valueOf(COLLAPSED));
302        defaultProps.put(PROPERTY_NAVIGATION_POSITION, Integer.valueOf(INVISIBLE));
303        defaultProps.put(PROPERTY_IN_NAVIGATION, Integer.valueOf(INVISIBLE));
304        defaultProps.put(PROPERTY_COPYRIGHT, Integer.valueOf(COLLAPSED));
305        defaultProps.put(PROPERTY_CACHE, Integer.valueOf(COLLAPSED));
306        defaultProps.put(PROPERTY_RESOURCE_TYPE, Integer.valueOf(0));
307        defaultProps.put(PROPERTY_INTERNAL_RESOURCE_TYPE, Integer.valueOf(COLLAPSED));
308        defaultProps.put(PROPERTY_SIZE, Integer.valueOf(0));
309        defaultProps.put(PROPERTY_PERMISSIONS, Integer.valueOf(COLLAPSED));
310        defaultProps.put(PROPERTY_DATE_MODIFIED, Integer.valueOf(0));
311        defaultProps.put(PROPERTY_USER_MODIFIED, Integer.valueOf(COLLAPSED));
312        defaultProps.put(PROPERTY_DATE_CREATED, Integer.valueOf(COLLAPSED));
313        defaultProps.put(PROPERTY_USER_CREATED, Integer.valueOf(COLLAPSED));
314        defaultProps.put(PROPERTY_DATE_RELEASED, Integer.valueOf(0));
315        defaultProps.put(PROPERTY_DATE_EXPIRED, Integer.valueOf(0));
316        defaultProps.put(PROPERTY_STATE_NAME, Integer.valueOf(0));
317        defaultProps.put(PROPERTY_USER_LOCKED, Integer.valueOf(0));
318        defaultProps.put(PROPERTY_IS_FOLDER, Integer.valueOf(INVISIBLE));
319        defaultProps.put(PROPERTY_STATE, Integer.valueOf(INVISIBLE));
320        defaultProps.put(PROPERTY_INSIDE_PROJECT, Integer.valueOf(INVISIBLE));
321        defaultProps.put(PROPERTY_RELEASED_NOT_EXPIRED, Integer.valueOf(INVISIBLE));
322        DEFAULT_TABLE_PROPERTIES = Collections.unmodifiableMap(defaultProps);
323    }
324
325    /** The selected resources. */
326    protected List<CmsResource> m_currentResources = new ArrayList<CmsResource>();
327
328    /** The default action column property. */
329    CmsResourceTableProperty m_actionColumnProperty;
330
331    /** The additional cell style generators. */
332    List<Table.CellStyleGenerator> m_additionalStyleGenerators;
333
334    /** The current file property edit handler. */
335    I_CmsFilePropertyEditHandler m_editHandler;
336
337    /** File edit event handler. */
338    FileEditHandler m_fileEditHandler = new FileEditHandler();
339
340    /** The context menu. */
341    CmsContextMenu m_menu;
342
343    /** The context menu builder. */
344    I_CmsContextMenuBuilder m_menuBuilder;
345
346    /** The table drag mode, stored during item editing. */
347    private TableDragMode m_beforEditDragMode;
348
349    /** The dialog context provider. */
350    private I_CmsContextProvider m_contextProvider;
351
352    /** The edited item id. */
353    private CmsUUID m_editItemId;
354
355    /** The edited property id. */
356    private CmsResourceTableProperty m_editProperty;
357
358    /** Saved container filters. */
359    private Collection<Filter> m_filters = Collections.emptyList();
360
361    /** The folder select handler. */
362    private I_FolderSelectHandler m_folderSelectHandler;
363
364    /** The original edit value. */
365    private String m_originalEditValue;
366
367    /**
368     * Default constructor.<p>
369     *
370     * @param contextProvider the dialog context provider
371     */
372    public CmsFileTable(I_CmsContextProvider contextProvider) {
373
374        this(contextProvider, DEFAULT_TABLE_PROPERTIES);
375    }
376
377    /**
378     * Default constructor.<p>
379     *
380     * @param contextProvider the dialog context provider
381     * @param tableColumns the table columns to show
382     */
383    public CmsFileTable(I_CmsContextProvider contextProvider, Map<CmsResourceTableProperty, Integer> tableColumns) {
384
385        super();
386        m_additionalStyleGenerators = new ArrayList<Table.CellStyleGenerator>();
387        m_actionColumnProperty = PROPERTY_RESOURCE_NAME;
388        m_contextProvider = contextProvider;
389        m_container.setItemSorter(new FileSorter());
390        m_fileTable.addStyleName(ValoTheme.TABLE_BORDERLESS);
391        m_fileTable.addStyleName(OpenCmsTheme.SIMPLE_DRAG);
392        m_fileTable.setSizeFull();
393        m_fileTable.setColumnCollapsingAllowed(true);
394        m_fileTable.setSelectable(true);
395        m_fileTable.setMultiSelect(true);
396
397        m_fileTable.setTableFieldFactory(new FileFieldFactory());
398        ColumnBuilder builder = new ColumnBuilder();
399        for (Entry<CmsResourceTableProperty, Integer> entry : tableColumns.entrySet()) {
400            builder.column(entry.getKey(), entry.getValue().intValue());
401        }
402        builder.buildColumns();
403
404        m_fileTable.setSortContainerPropertyId(CmsResourceTableProperty.PROPERTY_RESOURCE_NAME);
405        m_menu = new CmsContextMenu();
406        m_fileTable.addValueChangeListener(new ValueChangeListener() {
407
408            private static final long serialVersionUID = 1L;
409
410            public void valueChange(ValueChangeEvent event) {
411
412                @SuppressWarnings("unchecked")
413                Set<String> selectedIds = (Set<String>)event.getProperty().getValue();
414                List<CmsResource> selectedResources = new ArrayList<CmsResource>();
415                for (String id : selectedIds) {
416                    try {
417                        CmsResource resource = A_CmsUI.getCmsObject().readResource(
418                            getUUIDFromItemID(id),
419                            CmsResourceFilter.ALL);
420                        selectedResources.add(resource);
421                    } catch (CmsException e) {
422                        LOG.error(e.getLocalizedMessage(), e);
423                    }
424
425                }
426                m_currentResources = selectedResources;
427
428                rebuildMenu();
429            }
430        });
431
432        m_fileTable.addItemClickListener(new ItemClickListener() {
433
434            private static final long serialVersionUID = 1L;
435
436            public void itemClick(ItemClickEvent event) {
437
438                handleFileItemClick(event);
439            }
440        });
441
442        m_fileTable.setCellStyleGenerator(new Table.CellStyleGenerator() {
443
444            private static final long serialVersionUID = 1L;
445
446            public String getStyle(Table source, Object itemId, Object propertyId) {
447
448                Item item = m_container.getItem(itemId);
449                String style = getStateStyle(item);
450                if (m_actionColumnProperty == propertyId) {
451                    style += " " + OpenCmsTheme.HOVER_COLUMN;
452                } else if ((CmsResourceTableProperty.PROPERTY_NAVIGATION_TEXT == propertyId)
453                    || (CmsResourceTableProperty.PROPERTY_TITLE == propertyId)) {
454                        if ((item.getItemProperty(CmsResourceTableProperty.PROPERTY_IN_NAVIGATION) != null)
455                            && ((Boolean)item.getItemProperty(
456                                CmsResourceTableProperty.PROPERTY_IN_NAVIGATION).getValue()).booleanValue()) {
457                            style += " " + OpenCmsTheme.IN_NAVIGATION;
458                        }
459                    }
460                for (Table.CellStyleGenerator generator : m_additionalStyleGenerators) {
461                    String additional = generator.getStyle(source, itemId, propertyId);
462                    if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(additional)) {
463                        style += " " + additional;
464                    }
465                }
466                return style;
467            }
468        });
469
470        m_menu.setAsTableContextMenu(m_fileTable);
471    }
472
473    /**
474     * Returns the resource state specific style name.<p>
475     *
476     * @param resourceItem the resource item
477     *
478     * @return the style name
479     */
480    public static String getStateStyle(Item resourceItem) {
481
482        String result = "";
483        if (resourceItem != null) {
484            if ((resourceItem.getItemProperty(PROPERTY_INSIDE_PROJECT) == null)
485                || ((Boolean)resourceItem.getItemProperty(PROPERTY_INSIDE_PROJECT).getValue()).booleanValue()) {
486
487                CmsResourceState state = (CmsResourceState)resourceItem.getItemProperty(
488                    CmsResourceTableProperty.PROPERTY_STATE).getValue();
489                result = getStateStyle(state);
490            } else {
491                result = OpenCmsTheme.PROJECT_OTHER;
492            }
493            if ((resourceItem.getItemProperty(PROPERTY_RELEASED_NOT_EXPIRED) != null)
494                && !((Boolean)resourceItem.getItemProperty(PROPERTY_RELEASED_NOT_EXPIRED).getValue()).booleanValue()) {
495                result += " " + OpenCmsTheme.EXPIRED;
496            }
497            if ((resourceItem.getItemProperty(CmsResourceTableProperty.PROPERTY_DISABLED) != null)
498                && ((Boolean)resourceItem.getItemProperty(
499                    CmsResourceTableProperty.PROPERTY_DISABLED).getValue()).booleanValue()) {
500                result += " " + OpenCmsTheme.DISABLED;
501            }
502        }
503        return result;
504    }
505
506    /**
507     * Adds an additional cell style generator.<p>
508     *
509     * @param styleGenerator the cell style generator
510     */
511    public void addAdditionalStyleGenerator(Table.CellStyleGenerator styleGenerator) {
512
513        m_additionalStyleGenerators.add(styleGenerator);
514    }
515
516    /**
517     * Applies settings generally used within workplace app file lists.<p>
518     */
519    public void applyWorkplaceAppSettings() {
520
521        // add site path property to container
522        m_container.addContainerProperty(
523            CmsResourceTableProperty.PROPERTY_SITE_PATH,
524            CmsResourceTableProperty.PROPERTY_SITE_PATH.getColumnType(),
525            CmsResourceTableProperty.PROPERTY_SITE_PATH.getDefaultValue());
526
527        // replace the resource name column with the path column
528        Object[] visibleCols = m_fileTable.getVisibleColumns();
529        for (int i = 0; i < visibleCols.length; i++) {
530            if (CmsResourceTableProperty.PROPERTY_RESOURCE_NAME.equals(visibleCols[i])) {
531                visibleCols[i] = CmsResourceTableProperty.PROPERTY_SITE_PATH;
532            }
533        }
534        m_fileTable.setVisibleColumns(visibleCols);
535        m_fileTable.setColumnCollapsible(CmsResourceTableProperty.PROPERTY_SITE_PATH, false);
536        m_fileTable.setColumnHeader(
537            CmsResourceTableProperty.PROPERTY_SITE_PATH,
538            CmsVaadinUtils.getMessageText(CmsResourceTableProperty.PROPERTY_SITE_PATH.getHeaderKey()));
539
540        // update column visibility according to the latest file explorer settings
541        CmsFileExplorerSettings settings;
542        try {
543            settings = OpenCms.getWorkplaceAppManager().getAppSettings(
544                A_CmsUI.getCmsObject(),
545                CmsFileExplorerSettings.class);
546
547            setTableState(settings);
548        } catch (Exception e) {
549            LOG.error("Error while reading file explorer settings from user.", e);
550        }
551        m_fileTable.setSortContainerPropertyId(CmsResourceTableProperty.PROPERTY_SITE_PATH);
552        setActionColumnProperty(CmsResourceTableProperty.PROPERTY_SITE_PATH);
553        setMenuBuilder(new CmsResourceContextMenuBuilder());
554    }
555
556    /**
557     * Clears all container filters.
558     */
559    public void clearFilters() {
560
561        IndexedContainer container = (IndexedContainer)m_fileTable.getContainerDataSource();
562        container.removeAllContainerFilters();
563    }
564
565    /**
566    * Filters the displayed resources.<p>
567    * Only resources where either the resource name, the title or the nav-text contains the given substring are shown.<p>
568    *
569    * @param search the search term
570    */
571    public void filterTable(String search) {
572
573        m_container.removeAllContainerFilters();
574        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(search)) {
575            m_container.addContainerFilter(
576                new Or(
577                    new SimpleStringFilter(CmsResourceTableProperty.PROPERTY_RESOURCE_NAME, search, true, false),
578                    new SimpleStringFilter(CmsResourceTableProperty.PROPERTY_NAVIGATION_TEXT, search, true, false),
579                    new SimpleStringFilter(CmsResourceTableProperty.PROPERTY_TITLE, search, true, false)));
580        }
581        if ((m_fileTable.getValue() != null) & !((Set<?>)m_fileTable.getValue()).isEmpty()) {
582            m_fileTable.setCurrentPageFirstItemId(((Set<?>)m_fileTable.getValue()).iterator().next());
583        }
584    }
585
586    /**
587     * Returns the index of the first visible item.<p>
588     *
589     * @return the first visible item
590     */
591    public int getFirstVisibleItemIndex() {
592
593        return m_fileTable.getCurrentPageFirstItemIndex();
594    }
595
596    /**
597     * Gets the selected structure ids.<p>
598     *
599     * @return the set of selected structure ids
600     */
601    @SuppressWarnings("unchecked")
602    public Collection<CmsUUID> getSelectedIds() {
603
604        return itemIdsToUUIDs((Collection<String>)m_fileTable.getValue());
605    }
606
607    /**
608     * Gets the list of selected resources.<p>
609     *
610     * @return the list of selected resources
611     */
612    public List<CmsResource> getSelectedResources() {
613
614        return m_currentResources;
615    }
616
617    /**
618     * Returns the current table state.<p>
619     *
620     * @return the table state
621     */
622    public CmsFileExplorerSettings getTableSettings() {
623
624        CmsFileExplorerSettings fileTableState = new CmsFileExplorerSettings();
625
626        fileTableState.setSortAscending(m_fileTable.isSortAscending());
627        fileTableState.setSortColumnId((CmsResourceTableProperty)m_fileTable.getSortContainerPropertyId());
628        List<CmsResourceTableProperty> collapsedCollumns = new ArrayList<CmsResourceTableProperty>();
629        Object[] visibleCols = m_fileTable.getVisibleColumns();
630        for (int i = 0; i < visibleCols.length; i++) {
631            if (m_fileTable.isColumnCollapsed(visibleCols[i])) {
632                collapsedCollumns.add((CmsResourceTableProperty)visibleCols[i]);
633            }
634        }
635        fileTableState.setCollapsedColumns(collapsedCollumns);
636        return fileTableState;
637    }
638
639    /**
640     * Handles the item selection.<p>
641     *
642     * @param itemId the selected item id
643     */
644    public void handleSelection(String itemId) {
645
646        Collection<?> selection = (Collection<?>)m_fileTable.getValue();
647        if (selection == null) {
648            m_fileTable.select(itemId);
649        } else if (!selection.contains(itemId)) {
650            m_fileTable.setValue(null);
651            m_fileTable.select(itemId);
652        }
653    }
654
655    /**
656     * Returns if a file property is being edited.<p>
657     * @return <code>true</code> if a file property is being edited
658     */
659    public boolean isEditing() {
660
661        return m_editItemId != null;
662    }
663
664    /**
665     * Returns if the given property is being edited.<p>
666     *
667     * @param propertyId the property id
668     *
669     * @return <code>true</code> if the given property is being edited
670     */
671    public boolean isEditProperty(CmsResourceTableProperty propertyId) {
672
673        return (m_editProperty != null) && m_editProperty.equals(propertyId);
674    }
675
676    /**
677     * Opens the context menu.<p>
678     *
679     * @param event the click event
680     */
681    public void openContextMenu(ItemClickEvent event) {
682
683        m_menu.openForTable(event, m_fileTable);
684    }
685
686    /**
687     * Removes the given cell style generator.<p>
688     *
689     * @param styleGenerator the cell style generator to remove
690     */
691    public void removeAdditionalStyleGenerator(Table.CellStyleGenerator styleGenerator) {
692
693        m_additionalStyleGenerators.remove(styleGenerator);
694    }
695
696    /**
697     * Restores container filters to the ones previously saved via saveFilters().
698     */
699    public void restoreFilters() {
700
701        IndexedContainer container = (IndexedContainer)m_fileTable.getContainerDataSource();
702        container.removeAllContainerFilters();
703        for (Filter filter : m_filters) {
704            container.addContainerFilter(filter);
705        }
706    }
707
708    /**
709     * Saves currently active filters.<p>
710     */
711    public void saveFilters() {
712
713        IndexedContainer container = (IndexedContainer)m_fileTable.getContainerDataSource();
714        m_filters = container.getContainerFilters();
715    }
716
717    /**
718     * Sets the default action column property.<p>
719     *
720     * @param actionColumnProperty the default action column property
721     */
722    public void setActionColumnProperty(CmsResourceTableProperty actionColumnProperty) {
723
724        m_actionColumnProperty = actionColumnProperty;
725    }
726
727    /**
728     * Sets the dialog context provider.<p>
729     *
730     * @param provider the dialog context provider
731     */
732    public void setContextProvider(I_CmsContextProvider provider) {
733
734        m_contextProvider = provider;
735    }
736
737    /**
738     * Sets the first visible item index.<p>
739     *
740     * @param i the item index
741     */
742    public void setFirstVisibleItemIndex(int i) {
743
744        m_fileTable.setCurrentPageFirstItemIndex(i);
745    }
746
747    /**
748     * Sets the folder select handler.<p>
749     *
750     * @param folderSelectHandler the folder select handler
751     */
752    public void setFolderSelectHandler(I_FolderSelectHandler folderSelectHandler) {
753
754        m_folderSelectHandler = folderSelectHandler;
755    }
756
757    /**
758     * Sets the menu builder.<p>
759     *
760     * @param builder the menu builder
761     */
762    public void setMenuBuilder(I_CmsContextMenuBuilder builder) {
763
764        m_menuBuilder = builder;
765    }
766
767    /**
768     * Sets the table state.<p>
769     *
770     * @param state the table state
771     */
772    public void setTableState(CmsFileExplorerSettings state) {
773
774        if (state != null) {
775            m_fileTable.setSortContainerPropertyId(state.getSortColumnId());
776            m_fileTable.setSortAscending(state.isSortAscending());
777            Object[] visibleCols = m_fileTable.getVisibleColumns();
778            for (int i = 0; i < visibleCols.length; i++) {
779                m_fileTable.setColumnCollapsed(visibleCols[i], state.getCollapsedColumns().contains(visibleCols[i]));
780            }
781        }
782    }
783
784    /**
785     * Starts inline editing of the given file property.<p>
786     *
787     * @param itemId the item resource structure id
788     * @param propertyId the property to edit
789     * @param editHandler the edit handler
790     */
791    public void startEdit(
792        CmsUUID itemId,
793        CmsResourceTableProperty propertyId,
794        I_CmsFilePropertyEditHandler editHandler) {
795
796        m_editItemId = itemId;
797        m_editProperty = propertyId;
798        m_originalEditValue = (String)m_container.getItem(m_editItemId.toString()).getItemProperty(
799            m_editProperty).getValue();
800        m_editHandler = editHandler;
801
802        // storing current drag mode and setting it to none to avoid text selection issues in IE11
803        m_beforEditDragMode = m_fileTable.getDragMode();
804        m_fileTable.setDragMode(TableDragMode.NONE);
805
806        m_fileTable.setEditable(true);
807    }
808
809    /**
810     * Stops the current edit process to save the changed property value.<p>
811     */
812    public void stopEdit() {
813
814        if (m_editHandler != null) {
815            String value = (String)m_container.getItem(m_editItemId.toString()).getItemProperty(
816                m_editProperty).getValue();
817            if (!value.equals(m_originalEditValue)) {
818                m_editHandler.validate(value);
819                m_editHandler.save(value);
820            } else {
821                // call cancel to ensure unlock
822                m_editHandler.cancel();
823            }
824        }
825        clearEdit();
826
827        // restoring drag mode
828        m_fileTable.setDragMode(m_beforEditDragMode);
829
830        m_beforEditDragMode = null;
831    }
832
833    /**
834     * Updates all items with ids from the given list.<p>
835     *
836     * @param ids the resource structure ids to update
837     * @param remove true if the item should be removed only
838     */
839    public void update(Collection<CmsUUID> ids, boolean remove) {
840
841        for (CmsUUID id : ids) {
842            updateItem(id, remove);
843        }
844        rebuildMenu();
845    }
846
847    /**
848     * Updates the column widths.<p>
849     *
850     * The reason this is needed is that the Vaadin table does not support minimum widths for columns,
851     * so expanding columns get squished when most of the horizontal space is used by other columns.
852     * So we try to determine whether the expanded columns would have enough space, and if not, give them a
853     * fixed width.
854     *
855     * @param estimatedSpace the estimated horizontal space available for the table.
856     */
857    public void updateColumnWidths(int estimatedSpace) {
858
859        Object[] cols = m_fileTable.getVisibleColumns();
860        List<CmsResourceTableProperty> expandCols = Lists.newArrayList();
861        int nonExpandWidth = 0;
862        int totalExpandMinWidth = 0;
863        for (Object colObj : cols) {
864            if (m_fileTable.isColumnCollapsed(colObj)) {
865                continue;
866            }
867            CmsResourceTableProperty prop = (CmsResourceTableProperty)colObj;
868            if (0 < m_fileTable.getColumnExpandRatio(prop)) {
869                expandCols.add(prop);
870                totalExpandMinWidth += getAlternativeWidthForExpandingColumns(prop);
871            } else {
872                nonExpandWidth += prop.getColumnWidth();
873            }
874        }
875        if (estimatedSpace < (totalExpandMinWidth + nonExpandWidth)) {
876            for (CmsResourceTableProperty expandCol : expandCols) {
877                m_fileTable.setColumnWidth(expandCol, getAlternativeWidthForExpandingColumns(expandCol));
878            }
879        }
880    }
881
882    /**
883     * Updates the file table sorting.<p>
884     */
885    public void updateSorting() {
886
887        m_fileTable.sort();
888    }
889
890    /**
891     * Cancels the current edit process.<p>
892     */
893    void cancelEdit() {
894
895        if (m_editHandler != null) {
896            m_editHandler.cancel();
897        }
898        clearEdit();
899    }
900
901    /**
902     * Returns the dialog context provider.<p>
903     *
904     * @return the dialog context provider
905     */
906    I_CmsContextProvider getContextProvider() {
907
908        return m_contextProvider;
909    }
910
911    /**
912     * Returns the edit item id.<p>
913     *
914     * @return the edit item id
915     */
916    CmsUUID getEditItemId() {
917
918        return m_editItemId;
919    }
920
921    /**
922     * Returns the edit property id.<p>
923     *
924     * @return the edit property id
925     */
926    CmsResourceTableProperty getEditProperty() {
927
928        return m_editProperty;
929    }
930
931    /**
932     * Handles the file table item click.<p>
933     *
934     * @param event the click event
935     */
936    void handleFileItemClick(ItemClickEvent event) {
937
938        if (isEditing()) {
939            stopEdit();
940
941        } else if (!event.isCtrlKey() && !event.isShiftKey()) {
942            // don't interfere with multi-selection using control key
943            String itemId = (String)event.getItemId();
944            CmsUUID structureId = getUUIDFromItemID(itemId);
945            boolean openedFolder = false;
946            if (event.getButton().equals(MouseButton.RIGHT)) {
947                handleSelection(itemId);
948                openContextMenu(event);
949            } else {
950                if ((event.getPropertyId() == null)
951                    || CmsResourceTableProperty.PROPERTY_TYPE_ICON.equals(event.getPropertyId())) {
952                    handleSelection(itemId);
953                    openContextMenu(event);
954                } else {
955                    if (m_actionColumnProperty.equals(event.getPropertyId())) {
956                        Boolean isFolder = (Boolean)event.getItem().getItemProperty(
957                            CmsResourceTableProperty.PROPERTY_IS_FOLDER).getValue();
958                        if ((isFolder != null) && isFolder.booleanValue()) {
959                            if (m_folderSelectHandler != null) {
960                                m_folderSelectHandler.onFolderSelect(structureId);
961                            }
962                            openedFolder = true;
963                        } else {
964                            try {
965                                CmsObject cms = A_CmsUI.getCmsObject();
966                                CmsResource res = cms.readResource(structureId, CmsResourceFilter.IGNORE_EXPIRATION);
967                                m_currentResources = Collections.singletonList(res);
968                                I_CmsDialogContext context = m_contextProvider.getDialogContext();
969                                I_CmsDefaultAction action = OpenCms.getWorkplaceAppManager().getDefaultAction(
970                                    context,
971                                    m_menuBuilder);
972                                if (action != null) {
973                                    action.executeAction(context);
974                                    return;
975                                }
976                            } catch (CmsVfsResourceNotFoundException e) {
977                                LOG.info(e.getLocalizedMessage(), e);
978                            } catch (CmsException e) {
979                                LOG.error(e.getLocalizedMessage(), e);
980                            }
981                        }
982                    } else {
983                        I_CmsDialogContext context = m_contextProvider.getDialogContext();
984                        if ((m_currentResources.size() == 1)
985                            && m_currentResources.get(0).getStructureId().equals(structureId)
986                            && (context instanceof I_CmsEditPropertyContext)
987                            && ((I_CmsEditPropertyContext)context).isPropertyEditable(event.getPropertyId())) {
988
989                            ((I_CmsEditPropertyContext)context).editProperty(event.getPropertyId());
990                        }
991                    }
992                }
993            }
994            // update the item on click to show any available changes
995            if (!openedFolder) {
996                update(Collections.singletonList(structureId), false);
997            }
998        }
999    }
1000
1001    /**
1002     * Rebuilds the context menu.<p>
1003     */
1004    void rebuildMenu() {
1005
1006        if (!getSelectedIds().isEmpty() && (m_menuBuilder != null)) {
1007            m_menu.removeAllItems();
1008            m_menuBuilder.buildContextMenu(getContextProvider().getDialogContext(), m_menu);
1009        }
1010    }
1011
1012    /**
1013     * Clears the current edit process.<p>
1014     */
1015    private void clearEdit() {
1016
1017        m_fileTable.setEditable(false);
1018        if (m_editItemId != null) {
1019            updateItem(m_editItemId, false);
1020        }
1021        m_editItemId = null;
1022        m_editProperty = null;
1023        m_editHandler = null;
1024        updateSorting();
1025    }
1026
1027    /**
1028     * Gets alternative width for expanding table columns which is used when there is not enough space for
1029     * all visible columns.<p>
1030     *
1031     * @param prop the table property
1032     * @return the alternative column width
1033     */
1034    private int getAlternativeWidthForExpandingColumns(CmsResourceTableProperty prop) {
1035
1036        if (prop.getId().equals(CmsResourceTableProperty.PROPERTY_RESOURCE_NAME.getId())) {
1037            return 200;
1038        }
1039        if (prop.getId().equals(CmsResourceTableProperty.PROPERTY_TITLE.getId())) {
1040            return 300;
1041        }
1042        if (prop.getId().equals(CmsResourceTableProperty.PROPERTY_NAVIGATION_TEXT.getId())) {
1043            return 200;
1044        }
1045        return 200;
1046    }
1047
1048    /**
1049     * Updates the given item in the file table.<p>
1050     *
1051     * @param itemId the item id
1052     * @param remove true if the item should be removed only
1053     */
1054    private void updateItem(CmsUUID itemId, boolean remove) {
1055
1056        if (remove) {
1057            String idStr = itemId != null ? itemId.toString() : null;
1058            m_container.removeItem(idStr);
1059            return;
1060        }
1061
1062        CmsObject cms = A_CmsUI.getCmsObject();
1063        try {
1064            CmsResource resource = cms.readResource(itemId, CmsResourceFilter.ALL);
1065            fillItem(cms, resource, OpenCms.getWorkplaceManager().getWorkplaceLocale(cms));
1066
1067        } catch (CmsVfsResourceNotFoundException e) {
1068            if (null != itemId) {
1069                m_container.removeItem(itemId.toString());
1070            }
1071            LOG.debug("Failed to update file table item, removing it from view.", e);
1072        } catch (CmsException e) {
1073            LOG.error(e.getLocalizedMessage(), e);
1074        }
1075    }
1076
1077}