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.components.fileselect;
029
030import org.opencms.file.CmsObject;
031import org.opencms.file.CmsResource;
032import org.opencms.file.CmsResourceFilter;
033import org.opencms.main.CmsLog;
034import org.opencms.ui.CmsVaadinUtils;
035import org.opencms.ui.components.CmsFileTable;
036import org.opencms.ui.components.CmsResourceTableProperty;
037import org.opencms.ui.components.OpenCmsTheme;
038import org.opencms.ui.util.I_CmsItemSorter;
039import org.opencms.util.CmsFileUtil;
040import org.opencms.util.CmsUUID;
041
042import java.util.Collection;
043import java.util.HashSet;
044import java.util.List;
045import java.util.Set;
046
047import org.apache.commons.logging.Log;
048
049import com.google.common.base.Predicate;
050import com.google.common.base.Predicates;
051import com.google.common.collect.ComparisonChain;
052import com.google.common.collect.Lists;
053import com.vaadin.ui.themes.ValoTheme;
054import com.vaadin.v7.data.Container;
055import com.vaadin.v7.data.Item;
056import com.vaadin.v7.data.util.DefaultItemSorter;
057import com.vaadin.v7.event.ItemClickEvent;
058import com.vaadin.v7.event.ItemClickEvent.ItemClickListener;
059import com.vaadin.v7.shared.ui.label.ContentMode;
060import com.vaadin.v7.ui.Label;
061import com.vaadin.v7.ui.Table;
062import com.vaadin.v7.ui.Tree.CollapseEvent;
063import com.vaadin.v7.ui.Tree.CollapseListener;
064import com.vaadin.v7.ui.Tree.ExpandEvent;
065import com.vaadin.v7.ui.Tree.ExpandListener;
066import com.vaadin.v7.ui.TreeTable;
067
068/**
069 * Tree subclass used to display VFS resource trees.<p>
070 */
071public class CmsResourceTreeTable extends TreeTable {
072
073    /**
074     * Extends the default sorting to differentiate between files and folder when sorting by name.<p>
075     * Also allows sorting by navPos property for the Resource icon column.<p>
076     */
077    public static class FileSorter extends DefaultItemSorter implements I_CmsItemSorter {
078
079        /** The serial version id. */
080        private static final long serialVersionUID = 1L;
081
082        /**
083         * @see org.opencms.ui.util.I_CmsItemSorter#getSortableContainerPropertyIds(com.vaadin.v7.data.Container)
084         */
085        public Collection<?> getSortableContainerPropertyIds(Container container) {
086
087            Set<Object> result = new HashSet<Object>();
088            result.add(CAPTION_FOLDERS);
089            result.add(CmsResourceTableProperty.PROPERTY_NAVIGATION_TEXT);
090            return result;
091        }
092
093        /**
094         * @see com.vaadin.v7.data.util.DefaultItemSorter#compareProperty(java.lang.Object, boolean, com.vaadin.v7.data.Item, com.vaadin.v7.data.Item)
095         */
096        @Override
097        protected int compareProperty(Object propertyId, boolean sortDirection, Item item1, Item item2) {
098
099            if (CAPTION_FOLDERS.equals(propertyId)) {
100                Boolean isFolder1 = (Boolean)item1.getItemProperty(
101                    CmsResourceTableProperty.PROPERTY_IS_FOLDER).getValue();
102                Boolean isFolder2 = (Boolean)item2.getItemProperty(
103                    CmsResourceTableProperty.PROPERTY_IS_FOLDER).getValue();
104                String name1 = (String)(item1.getItemProperty(
105                    CmsResourceTableProperty.PROPERTY_RESOURCE_NAME).getValue());
106                name1 = CmsFileUtil.removeTrailingSeparator(name1);
107                String name2 = (String)(item2.getItemProperty(
108                    CmsResourceTableProperty.PROPERTY_RESOURCE_NAME).getValue());
109                name2 = CmsFileUtil.removeTrailingSeparator(name2);
110                return (sortDirection ? 1 : -1)
111                    * ComparisonChain.start().compareTrueFirst(
112                        isFolder1.booleanValue(),
113                        isFolder2.booleanValue()).compare(name1, name2).result();
114            } else if (CmsResourceTableProperty.PROPERTY_NAVIGATION_TEXT.equals(propertyId)
115                && (item1.getItemProperty(CmsResourceTableProperty.PROPERTY_NAVIGATION_POSITION) != null)) {
116                int result;
117                Float pos1 = (Float)item1.getItemProperty(
118                    CmsResourceTableProperty.PROPERTY_NAVIGATION_POSITION).getValue();
119                Float pos2 = (Float)item2.getItemProperty(
120                    CmsResourceTableProperty.PROPERTY_NAVIGATION_POSITION).getValue();
121                if (pos1 == null) {
122                    result = pos2 == null
123                    ? compareProperty(CmsResourceTableProperty.PROPERTY_RESOURCE_NAME, true, item1, item2)
124                    : 1;
125                } else {
126                    result = pos2 == null ? -1 : Float.compare(pos1.floatValue(), pos2.floatValue());
127                }
128                if (!sortDirection) {
129                    result = result * (-1);
130                }
131                return result;
132            }
133            return super.compareProperty(propertyId, sortDirection, item1, item2);
134        }
135    }
136
137    /**
138     * The style generator.<p>
139     */
140    public class StyleGenerator implements CellStyleGenerator {
141
142        /** The serial version id. */
143        private static final long serialVersionUID = 1L;
144
145        /**
146         * @see com.vaadin.ui.Table.CellStyleGenerator#getStyle(com.vaadin.ui.Table, java.lang.Object, java.lang.Object)
147         */
148        public String getStyle(Table source, Object itemId, Object propertyId) {
149
150            Item item = source.getContainerDataSource().getItem(itemId);
151            String style = CmsFileTable.getStateStyle(item);
152            if (!isSelectable(item)) {
153                style += " " + OpenCmsTheme.DISABLED;
154            }
155            if (CAPTION_FOLDERS.equals(propertyId)) {
156                style += " o-name-column";
157            } else if (CmsResourceTableProperty.PROPERTY_NAVIGATION_TEXT == propertyId) {
158                if ((item.getItemProperty(CmsResourceTableProperty.PROPERTY_IN_NAVIGATION) != null)
159                    && ((Boolean)item.getItemProperty(
160                        CmsResourceTableProperty.PROPERTY_IN_NAVIGATION).getValue()).booleanValue()) {
161                    style += " " + OpenCmsTheme.IN_NAVIGATION;
162                }
163            }
164            return style;
165        }
166    }
167
168    /** The logger instance for this class. */
169    private static final Log LOG = CmsLog.getLog(CmsResourceTreeTable.class);
170
171    /** Serial version id. */
172    private static final long serialVersionUID = 1L;
173
174    /** The folder caption column id. */
175    public static final String CAPTION_FOLDERS = "CAPTION_FOLDERS";
176
177    /** The CMS context. */
178    CmsObject m_cms;
179
180    /** The list of selection handlers. */
181    private List<I_CmsSelectionHandler<CmsResource>> m_resourceSelectionHandlers = Lists.newArrayList();
182
183    /** The root resource. */
184    private CmsResource m_root;
185
186    /**
187     * Predicate which can be used to prevent selection of a node (this means the select handler will not be called
188     * if this returns false, but the Vaadin selection mechanism will still mark it as selected.
189     */
190    private Predicate<Item> m_selectionFilter = Predicates.alwaysTrue();
191
192    /**
193     * Creates a new instance.<p>
194     *
195     * @param cms the CMS context
196     * @param root the root resource
197     * @param filter the resource filter
198     */
199    public CmsResourceTreeTable(CmsObject cms, CmsResource root, CmsResourceFilter filter) {
200
201        this(cms, root, new CmsResourceTreeContainer(filter));
202    }
203
204    /**
205     * Creates a new instance.<p>
206     *
207     * @param cms the CMS context
208     * @param root the root resource
209     * @param container the data container for the tree
210     */
211    public CmsResourceTreeTable(CmsObject cms, CmsResource root, CmsResourceTreeContainer container) {
212
213        m_cms = cms;
214        m_root = root;
215        FileSorter sorter = new FileSorter();
216        sorter.setSortProperties(
217            container,
218            new Object[] {CAPTION_FOLDERS, CmsResourceTableProperty.PROPERTY_NAVIGATION_TEXT},
219            new boolean[] {true, true});
220        container.setItemSorter(sorter);
221        setContainerDataSource(container);
222        ColumnGenerator captionGenerator = new ColumnGenerator() {
223
224            private static final long serialVersionUID = 1L;
225
226            public Object generateCell(Table source, Object itemId, Object columnId) {
227
228                if (CAPTION_FOLDERS.equals(columnId)) {
229                    String html = (String)source.getContainerDataSource().getItem(itemId).getItemProperty(
230                        CmsResourceTableProperty.PROPERTY_TREE_CAPTION).getValue();
231                    Label label = new Label(html, ContentMode.HTML);
232                    label.setStyleName("o-tree-table-caption");
233                    return label;
234                } else {
235                    return null;
236                }
237            }
238        };
239        addGeneratedColumn(CAPTION_FOLDERS, captionGenerator);
240        setVisibleColumns(CAPTION_FOLDERS, CmsResourceTableProperty.PROPERTY_NAVIGATION_TEXT);
241        setItemCaptionPropertyId(CAPTION_FOLDERS);
242        setColumnHeader(
243            CmsResourceTableProperty.PROPERTY_NAVIGATION_TEXT,
244            CmsVaadinUtils.getMessageText(CmsResourceTableProperty.PROPERTY_NAVIGATION_TEXT.getHeaderKey()));
245        setColumnHeader(
246            CAPTION_FOLDERS,
247            CmsVaadinUtils.getMessageText(CmsResourceTableProperty.PROPERTY_RESOURCE_NAME.getHeaderKey()));
248
249        // hide vertical and horizontal lines and disable alternating row background
250        addStyleName(ValoTheme.TABLE_NO_STRIPES);
251        addStyleName(ValoTheme.TABLE_NO_HORIZONTAL_LINES);
252        addStyleName(OpenCmsTheme.FILE_TREE);
253        addExpandListener(new ExpandListener() {
254
255            private static final long serialVersionUID = 1L;
256
257            public void nodeExpand(ExpandEvent event) {
258
259                getTreeContainer().readTreeLevel(m_cms, (CmsUUID)event.getItemId());
260                getTreeContainer().updateSort();
261                markAsDirtyRecursive(); // required so open / close arrows on folders without contents are rendered correctly
262            }
263
264        });
265
266        addCollapseListener(new CollapseListener() {
267
268            private static final long serialVersionUID = 1L;
269
270            public void nodeCollapse(CollapseEvent event) {
271
272                getTreeContainer().removeChildren((CmsUUID)event.getItemId());
273            }
274        });
275
276        addItemClickListener(new ItemClickListener() {
277
278            private static final long serialVersionUID = 1L;
279
280            public void itemClick(ItemClickEvent event) {
281
282                if (isSelectable(event.getItem())) {
283                    CmsResource resource = (CmsResource)(event.getItem().getItemProperty(
284                        CmsResourceTreeContainer.PROPERTY_RESOURCE).getValue());
285                    handleSelection(resource);
286                }
287            }
288        });
289        setCellStyleGenerator(new StyleGenerator());
290
291        getTreeContainer().addTreeItem(cms, m_root, null);
292        try {
293            setCollapsed(m_root.getStructureId(), false);
294            markAsDirtyRecursive();
295
296        } catch (Exception e) {
297            LOG.error(e.getLocalizedMessage(), e);
298        }
299    }
300
301    /**
302     * Adds a resource selection handler.<p>
303     *
304     * @param handler the resource selection handler
305     */
306    public void addResourceSelectionHandler(I_CmsSelectionHandler<CmsResource> handler) {
307
308        m_resourceSelectionHandlers.add(handler);
309    }
310
311    /**
312     * Expands the item with the given id.<p>
313     *
314     * @param itemId the item id
315     */
316    public void expandItem(CmsUUID itemId) {
317
318        setCollapsed(itemId, false);
319    }
320
321    /**
322     * Gets the tree container.<p>
323     *
324     * @return the tree container
325     */
326    public CmsResourceTreeContainer getTreeContainer() {
327
328        return (CmsResourceTreeContainer)getContainerDataSource();
329    }
330
331    /**
332     * Removes the given resource selection handler.<p>
333     *
334     * @param handler the resource selection handler
335     */
336    public void removeResourceSelectionHandler(I_CmsSelectionHandler<CmsResource> handler) {
337
338        m_resourceSelectionHandlers.remove(handler);
339    }
340
341    /**
342     * Sets the selection filter, a predicate that, by returning false for an item, can veto a tree entry selection.<p>
343     *
344     * @param selectionFilter the selection filter
345     */
346    public void setSelectionFilter(Predicate<Item> selectionFilter) {
347
348        m_selectionFilter = selectionFilter;
349    }
350
351    /**
352     * Shows the sitemap view.<p>
353     *
354     * @param showSitemap <code>true</code> to show the sitemap view
355     */
356    public void showSitemapView(boolean showSitemap) {
357
358        if (showSitemap) {
359            setSortContainerPropertyId(CmsResourceTableProperty.PROPERTY_NAVIGATION_TEXT);
360            setSortAscending(true);
361        } else {
362            setSortContainerPropertyId(CAPTION_FOLDERS);
363            setSortAscending(true);
364        }
365    }
366
367    /**
368     * Handles resource selection.<p>
369     *
370     * @param resource the selected resource
371     */
372    void handleSelection(CmsResource resource) {
373
374        for (I_CmsSelectionHandler<CmsResource> handler : m_resourceSelectionHandlers) {
375            handler.onSelection(resource);
376        }
377    }
378
379    /**
380     * Returns whether the given item is selectable.<p>
381     *
382     * @param item the item to check
383     *
384     * @return <code>true</code> if the given item is selectable
385     */
386    boolean isSelectable(Item item) {
387
388        return m_selectionFilter.apply(item);
389    }
390}