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