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.ade.galleries.client.ui;
029
030import org.opencms.ade.galleries.client.CmsGalleriesTabHandler;
031import org.opencms.ade.galleries.client.Messages;
032import org.opencms.ade.galleries.client.ui.css.I_CmsLayoutBundle;
033import org.opencms.ade.galleries.shared.CmsGalleryFolderBean;
034import org.opencms.ade.galleries.shared.CmsGalleryGroup;
035import org.opencms.ade.galleries.shared.CmsGallerySearchBean;
036import org.opencms.ade.galleries.shared.CmsGalleryTreeEntry;
037import org.opencms.ade.galleries.shared.I_CmsGalleryProviderConstants.GalleryTabId;
038import org.opencms.ade.galleries.shared.I_CmsGalleryProviderConstants.SortParams;
039import org.opencms.gwt.client.CmsCoreProvider;
040import org.opencms.gwt.client.rpc.CmsRpcAction;
041import org.opencms.gwt.client.ui.CmsListItem;
042import org.opencms.gwt.client.ui.CmsListItemWidget;
043import org.opencms.gwt.client.ui.CmsPushButton;
044import org.opencms.gwt.client.ui.CmsSimpleListItem;
045import org.opencms.gwt.client.ui.I_CmsButton;
046import org.opencms.gwt.client.ui.I_CmsButton.ButtonStyle;
047import org.opencms.gwt.client.ui.externallink.CmsEditExternalLinkDialog;
048import org.opencms.gwt.client.ui.input.CmsCheckBox;
049import org.opencms.gwt.client.ui.tree.CmsTreeItem;
050import org.opencms.gwt.client.util.CmsEmbeddedDialogHandler;
051import org.opencms.gwt.client.util.CmsScrollToBottomHandler;
052import org.opencms.util.CmsStringUtil;
053import org.opencms.util.CmsUUID;
054
055import java.util.ArrayList;
056import java.util.HashMap;
057import java.util.Iterator;
058import java.util.LinkedHashMap;
059import java.util.List;
060import java.util.Map;
061
062import com.google.common.collect.Lists;
063import com.google.gwt.core.client.Scheduler;
064import com.google.gwt.core.client.Scheduler.RepeatingCommand;
065import com.google.gwt.event.dom.client.ClickEvent;
066import com.google.gwt.event.dom.client.ClickHandler;
067import com.google.gwt.user.client.Timer;
068import com.google.gwt.user.client.ui.Label;
069
070/**
071 * Provides the widget for the galleries(folder) tab.<p>
072 *
073 * It displays the available gallery folders in the given order.
074 *
075 * @since 8.0.
076 */
077public class CmsGalleriesTab extends A_CmsListTab {
078
079    /**
080     * A class which generates list items incrementally to fill the galleries tab.<p>
081     */
082    protected class ListItemGenerator implements Iterator<CmsTreeItem> {
083
084        /** The internal iterator over the gallery beans. */
085        protected Iterator<CmsGalleryFolderBean> m_beanIterator;
086
087        /** True if output should be grouped. */
088        protected boolean m_useGroups;
089
090        /**
091         * Creates a new instance.<p>
092         * @param folders the list of folders for which to generate list items
093         * @param grouped true if the list items should be displayed in groups (this assumes the items have already been sorted correctly)
094         */
095        public ListItemGenerator(List<CmsGalleryFolderBean> folders, boolean grouped) {
096
097            if (folders == null) {
098                folders = new ArrayList<CmsGalleryFolderBean>();
099            }
100
101            m_beanIterator = folders.iterator();
102            m_useGroups = grouped;
103        }
104
105        /**
106         * @see java.util.Iterator#hasNext()
107         */
108        public boolean hasNext() {
109
110            return m_beanIterator.hasNext();
111        }
112
113        /**
114         * @see java.util.Iterator#next()
115         */
116        public CmsTreeItem next() {
117
118            CmsGalleryFolderBean gallery = m_beanIterator.next();
119            CmsTreeItem treeItem = createTreeItem(gallery, m_selectedGalleries, false, m_useGroups);
120            return treeItem;
121        }
122
123        /**
124         * @see java.util.Iterator#remove()
125         */
126        public void remove() {
127
128            throw new UnsupportedOperationException();
129        }
130    }
131
132    /**
133     * Command for adding more list items to the list of publish items.<p>
134     */
135    protected class MoreItemsCommand implements RepeatingCommand {
136
137        /** The number of items left to add. */
138        private int m_numItems;
139
140        /**
141         * Creates a new instance.<p>
142         *
143         * @param numItems the maximal number of items to add
144         */
145        public MoreItemsCommand(int numItems) {
146
147            m_numItems = numItems;
148        }
149
150        /**
151         * @see com.google.gwt.core.client.Scheduler.RepeatingCommand#execute()
152         */
153        public boolean execute() {
154
155            if (m_numItems == 0) {
156                setLoading(false);
157                onContentChange();
158                return false;
159            }
160            boolean hasMore = m_itemIterator.hasNext();
161            if (!hasMore) {
162                setLoading(false);
163                onContentChange();
164                return false;
165            } else {
166                CmsTreeItem treeItem = m_itemIterator.next();
167                CmsGalleryGroup group = (CmsGalleryGroup)treeItem.getData();
168                if ((group != null) && (group != m_lastGroup)) {
169                    m_lastGroup = group;
170                    CmsSimpleListItem header = new CmsSimpleListItem();
171                    header.getElement().setInnerText(getGroupName(group));
172                    String groupHeaderClass = I_CmsLayoutBundle.INSTANCE.galleryDialogCss().groupHeader();
173                    header.addStyleName(groupHeaderClass);
174                    addWidgetToList(header);
175                }
176
177                addWidgetToList(treeItem);
178            }
179            m_numItems -= 1;
180            return true;
181        }
182    }
183
184    /**
185     * A class which generates tree items incrementally to fill the galleries tab.<p>
186     */
187    protected class TreeItemGenerator implements Iterator<CmsTreeItem> {
188
189        /** The internal iterator over the gallery beans. <p> */
190        protected Iterator<CmsGalleryTreeEntry> m_beanIterator;
191
192        /**
193         * Creates a new instance.<p>
194         *
195         * @param folders the folders from which to generate list items
196         */
197        public TreeItemGenerator(List<CmsGalleryTreeEntry> folders) {
198
199            if (folders == null) {
200                folders = new ArrayList<CmsGalleryTreeEntry>();
201            }
202
203            m_beanIterator = folders.iterator();
204        }
205
206        /**
207         * @see java.util.Iterator#hasNext()
208         */
209        public boolean hasNext() {
210
211            return m_beanIterator.hasNext();
212        }
213
214        /**
215         * @see java.util.Iterator#next()
216         */
217        public CmsTreeItem next() {
218
219            CmsGalleryTreeEntry gallery = m_beanIterator.next();
220            CmsTreeItem treeItem = createTreeItem(gallery, m_selectedGalleries, true, false);
221            addChildren(treeItem, gallery.getChildren(), m_selectedGalleries);
222            treeItem.setOpen(true);
223            return treeItem;
224        }
225
226        /**
227         * @see java.util.Iterator#remove()
228         */
229        public void remove() {
230
231            throw new UnsupportedOperationException();
232        }
233    }
234
235    /**
236     * Handles the change of the item selection.<p>
237     */
238    private class SelectionHandler extends A_SelectionHandler {
239
240        /** The gallery path as id for the selected gallery. */
241        private String m_galleryPath;
242
243        /**
244         * Constructor.<p>
245         *
246         * @param gallerPath as id for the selected category
247         * @param checkBox the reference to the checkbox
248         */
249        public SelectionHandler(String gallerPath, CmsCheckBox checkBox) {
250
251            super(checkBox);
252            m_galleryPath = gallerPath;
253            m_selectionHandlers.add(this);
254        }
255
256        /**
257         * @see org.opencms.ade.galleries.client.ui.A_CmsListTab.A_SelectionHandler#onSelectionChange()
258         */
259        @Override
260        protected void onSelectionChange() {
261
262            if (getCheckBox().isChecked()) {
263                getTabHandler().onSelectGallery(m_galleryPath);
264            } else {
265                getTabHandler().onDeselectGallery(m_galleryPath);
266            }
267
268        }
269
270        /**
271         * @see org.opencms.ade.galleries.client.ui.A_CmsListTab.A_SelectionHandler#selectBeforeGoingToResultTab()
272         */
273        @Override
274        protected void selectBeforeGoingToResultTab() {
275
276            for (SelectionHandler otherHandler : m_selectionHandlers) {
277                if ((otherHandler != this)
278                    && (otherHandler.getCheckBox() != null)
279                    && otherHandler.getCheckBox().isChecked()) {
280                    otherHandler.getCheckBox().setChecked(false);
281                    otherHandler.onSelectionChange();
282                }
283            }
284            getCheckBox().setChecked(true);
285            onSelectionChange();
286        }
287    }
288
289    /** The batch size for adding new elements to the tab.<p> */
290    protected static final int LOAD_BATCH_SIZE = 50;
291
292    /** The labels to display for groups. */
293    protected Map<CmsGalleryGroup, String> m_groupLabels = new HashMap<>();
294
295    /** An iterator which produces new list items which should be added to the tab.<p> */
296    protected Iterator<CmsTreeItem> m_itemIterator;
297
298    /** The group of the gallery folder list item that was last rendered. */
299    protected CmsGalleryGroup m_lastGroup;
300
301    /** List of selected galleries. */
302    protected List<String> m_selectedGalleries;
303
304    /** The selection handlers. */
305    List<SelectionHandler> m_selectionHandlers = Lists.newArrayList();
306
307    /** Map of gallery folders by path. */
308    private Map<String, CmsGalleryFolderBean> m_galleries;
309
310    /** Flag which indicates whether new elements are currently being inserted into the galleries tab.<p> */
311    private boolean m_loading;
312
313    /** The tab handler. */
314    private CmsGalleriesTabHandler m_tabHandler;
315
316    /**
317     * Constructor.<p>
318     *
319     * @param tabHandler the tab handler
320     */
321    public CmsGalleriesTab(CmsGalleriesTabHandler tabHandler) {
322
323        super(GalleryTabId.cms_tab_galleries);
324        getList().addScrollHandler(new CmsScrollToBottomHandler(new Runnable() {
325
326            public void run() {
327
328                if (!isLoading()) {
329                    loadMoreItems();
330                }
331            }
332        }));
333        m_tabHandler = tabHandler;
334        m_galleries = new HashMap<String, CmsGalleryFolderBean>();
335        init();
336    }
337
338    /**
339     * Fill the content of the galleries tab panel.<p>
340     *
341     * @param galleryInfos the gallery info beans
342     * @param selectedGalleries the list of galleries to select
343     * @param grouped true if the items should be broken into groups
344     */
345    public void fillContent(List<CmsGalleryFolderBean> galleryInfos, List<String> selectedGalleries, boolean grouped) {
346
347        clearList();
348        m_lastGroup = null;
349        m_selectedGalleries = selectedGalleries;
350        if (!galleryInfos.isEmpty()) {
351            for (CmsGalleryFolderBean galleryInfo : galleryInfos) {
352                m_galleries.put(galleryInfo.getPath(), galleryInfo);
353            }
354            m_itemIterator = new ListItemGenerator(galleryInfos, grouped);
355            loadMoreItems();
356        } else {
357            showIsEmptyLabel();
358        }
359        onContentChange();
360    }
361
362    /**
363     * @see org.opencms.ade.galleries.client.ui.A_CmsTab#getParamPanels(org.opencms.ade.galleries.shared.CmsGallerySearchBean)
364     */
365    @Override
366    public List<CmsSearchParamPanel> getParamPanels(CmsGallerySearchBean searchObj) {
367
368        List<CmsSearchParamPanel> result = new ArrayList<CmsSearchParamPanel>();
369        for (String galleryPath : searchObj.getGalleries()) {
370            CmsGalleryFolderBean galleryBean = m_galleries.get(galleryPath);
371            if (galleryBean != null) {
372                String title = galleryBean.getTitle();
373                if (CmsStringUtil.isEmptyOrWhitespaceOnly(title)) {
374                    title = galleryBean.getPath();
375                }
376                CmsSearchParamPanel panel = new CmsSearchParamPanel(
377                    Messages.get().key(Messages.GUI_PARAMS_LABEL_GALLERIES_0),
378                    this);
379                panel.setContent(title, galleryPath);
380                result.add(panel);
381            }
382        }
383        return result;
384    }
385
386    /**
387     * Returns the value of the "loading" flag, which indicates whether new elements are currently being added into the galleries tab.<p>
388     *
389     * @return the "loading" flag
390     */
391    public boolean isLoading() {
392
393        return m_loading;
394
395    }
396
397    /**
398     * @see org.opencms.ade.galleries.client.ui.A_CmsTab#onSelection()
399     */
400    @Override
401    public void onSelection() {
402
403        super.onSelection();
404        Timer timer = new Timer() {
405
406            @Override
407            public void run() {
408
409                m_quickSearch.setFocus(true);
410            }
411        };
412        timer.schedule(20);
413    }
414
415    /**
416     * Sets the "loading" flag.<p>
417     *
418     * @param loading the new value of the loading flag
419     */
420    public void setLoading(boolean loading) {
421
422        m_loading = loading;
423    }
424
425    /**
426    * De-selects the galleries in the galleries list.<p>
427    *
428    * @param galleries the galleries to deselect
429    */
430    public void uncheckGalleries(List<String> galleries) {
431
432        for (String gallery : galleries) {
433            CmsListItem item = searchTreeItem(m_scrollList, gallery);
434            if (item != null) {
435                item.getCheckBox().setChecked(false);
436            }
437        }
438    }
439
440    /**
441     * Update the galleries list.<p>
442     *
443     * @param galleries the new gallery list
444     * @param selectedGalleries the list of galleries to select
445     * @param useGroups true if the galleries should be broken into groups (this assumes the galleries have already been sorted correctly)
446     */
447    public void updateListContent(
448        List<CmsGalleryFolderBean> galleries,
449        List<String> selectedGalleries,
450        boolean useGroups) {
451
452        clearList();
453        fillContent(galleries, selectedGalleries, useGroups);
454    }
455
456    /**
457     * Update the galleries tree.<p>
458     *
459     * @param galleryTreeEntries the new gallery tree list
460     * @param selectedGalleries the list of galleries to select
461     */
462    public void updateTreeContent(List<CmsGalleryTreeEntry> galleryTreeEntries, List<String> selectedGalleries) {
463
464        clearList();
465        m_selectedGalleries = selectedGalleries;
466        if (!galleryTreeEntries.isEmpty()) {
467            m_itemIterator = new TreeItemGenerator(galleryTreeEntries);
468            loadMoreItems();
469        } else {
470            showIsEmptyLabel();
471        }
472        onContentChange();
473    }
474
475    /**
476     * Adds children to the gallery tree and select the galleries.<p>
477     *
478     * @param parent the parent item
479     * @param children the list of children
480     * @param selectedGalleries the list of galleries to select
481     */
482    protected void addChildren(CmsTreeItem parent, List<CmsGalleryTreeEntry> children, List<String> selectedGalleries) {
483
484        if (children != null) {
485            for (CmsGalleryTreeEntry child : children) {
486                // set the category tree item and add to parent tree item
487                CmsTreeItem treeItem = createTreeItem(child, selectedGalleries, true, false);
488                if ((selectedGalleries != null) && selectedGalleries.contains(child.getPath())) {
489                    parent.setOpen(true);
490                    openParents(parent);
491                }
492                parent.addChild(treeItem);
493                addChildren(treeItem, child.getChildren(), selectedGalleries);
494            }
495        }
496    }
497
498    /**
499     * Creates a tree item widget used in list and tree view of this tab.<p>
500     *
501     * @param galleryInfo the gallery folder bean
502     * @param selectedGalleries the selected galleries
503     * @param forTree <code>true</code> if the item is used within tree view
504     * @param useGroups true if the gallery tree items should be broken into groups
505     *
506     * @return the tree item
507     */
508    protected CmsTreeItem createTreeItem(
509        CmsGalleryFolderBean galleryInfo,
510        List<String> selectedGalleries,
511        boolean forTree,
512        boolean useGroups) {
513
514        CmsListItemWidget listItemWidget = new CmsListItemWidget(galleryInfo);
515        listItemWidget.setUnselectable();
516        CmsCheckBox checkBox = new CmsCheckBox();
517        SelectionHandler selectionHandler = new SelectionHandler(galleryInfo.getPath(), checkBox);
518        checkBox.addClickHandler(selectionHandler);
519        listItemWidget.addClickHandler(selectionHandler);
520        if ((selectedGalleries != null) && selectedGalleries.contains(galleryInfo.getPath())) {
521            checkBox.setChecked(true);
522        }
523
524        if (galleryInfo.isEditable()) {
525            String uploadAction = galleryInfo.getUploadAction();
526
527            if (null != uploadAction) {
528                CmsPushButton uploadButton = new CmsPushButton(I_CmsButton.UPLOAD_SMALL);
529                uploadButton.setText(null);
530                uploadButton.setTitle(Messages.get().key(Messages.GUI_GALLERY_UPLOAD_TITLE_1, galleryInfo.getPath()));
531                uploadButton.setButtonStyle(ButtonStyle.FONT_ICON, null);
532                uploadButton.addClickHandler(new ClickHandler() {
533
534                    public void onClick(ClickEvent event) {
535
536                        // prevent event from bubbling up to surrounding widget
537                        event.stopPropagation();
538
539                        CmsRpcAction<CmsUUID> action = new CmsRpcAction<CmsUUID>() {
540
541                            @Override
542                            public void execute() {
543
544                                start(0, true);
545                                CmsCoreProvider.getVfsService().getStructureId(galleryInfo.getPath(), this);
546                            }
547
548                            @Override
549                            protected void onResponse(CmsUUID result) {
550
551                                stop(false);
552                                List<CmsUUID> resultIds = new ArrayList<>();
553                                resultIds.add(result);
554                                CmsEmbeddedDialogHandler.openDialog(
555                                    uploadAction,
556                                    resultIds,
557                                    id -> getTabHandler().updateIndex());
558                            }
559
560                        };
561                        action.execute();
562
563                    }
564                });
565                listItemWidget.addButton(uploadButton);
566
567            } else {
568                if (CmsEditExternalLinkDialog.LINK_GALLERY_RESOURCE_TYPE_NAME.equals(galleryInfo.getType())) {
569                    CmsPushButton createExternalLink = createNewExternalLinkButton(galleryInfo.getPath());
570                    if (createExternalLink != null) {
571                        listItemWidget.addButton(createExternalLink);
572                    }
573                } else {
574                    if (!CmsCoreProvider.get().isUploadDisabled()) {
575                        String uploadPath = CmsStringUtil.joinPaths(
576                            CmsCoreProvider.get().getSiteRoot(),
577                            galleryInfo.getPath());
578                        if (CmsCoreProvider.get().getUploadRestriction().isUploadEnabled(uploadPath)) {
579                            listItemWidget.addButton(createUploadButtonForTarget(galleryInfo.getPath(), false));
580                        }
581                    }
582                }
583            }
584        }
585        listItemWidget.addButton(createSelectButton(selectionHandler));
586        if (m_tabHandler.hasGalleriesSelectable()) {
587            CmsPushButton selectButton = createSelectResourceButton(
588                galleryInfo.getPath(),
589                CmsUUID.getNullUUID(),
590                "",
591                "");
592            listItemWidget.addButton(selectButton);
593        }
594
595        CmsTreeItem treeItem = new CmsTreeItem(forTree, checkBox, listItemWidget);
596        if (useGroups) {
597            treeItem.setData(galleryInfo.getGroup());
598        }
599        if (galleryInfo.getGroup() != null) {
600            m_groupLabels.putIfAbsent(galleryInfo.getGroup(), galleryInfo.getGroupLabel());
601        }
602        treeItem.setId(galleryInfo.getPath());
603        return treeItem;
604    }
605
606    /**
607     * Gets the label to display for a group.
608     *
609     * @param group the gallery group
610     * @return the label to display for the gallery group
611     */
612    protected String getGroupName(CmsGalleryGroup group) {
613
614        if (m_groupLabels.containsKey(group)) {
615            return m_groupLabels.get(group);
616        } else {
617            return "" + group;
618        }
619    }
620
621    /**
622     * @see org.opencms.ade.galleries.client.ui.A_CmsListTab#getSortList()
623     */
624    @Override
625    protected LinkedHashMap<String, String> getSortList() {
626
627        LinkedHashMap<String, String> list = new LinkedHashMap<String, String>();
628        list.put(SortParams.grouped.name(), Messages.get().key(Messages.GUI_SORT_LABEL_GROUPED_0));
629        list.put(SortParams.grouped_title.name(), Messages.get().key(Messages.GUI_SORT_LABEL_GROUPED_TITLE_0));
630        list.put(SortParams.title_asc.name(), Messages.get().key(Messages.GUI_SORT_LABEL_TITLE_ASC_0));
631        list.put(SortParams.title_desc.name(), Messages.get().key(Messages.GUI_SORT_LABEL_TITLE_DECS_0));
632        list.put(SortParams.type_asc.name(), Messages.get().key(Messages.GUI_SORT_LABEL_TYPE_ASC_0));
633        list.put(SortParams.type_desc.name(), Messages.get().key(Messages.GUI_SORT_LABEL_TYPE_DESC_0));
634        list.put(SortParams.tree.name(), Messages.get().key(Messages.GUI_SORT_LABEL_HIERARCHIC_0));
635
636        return list;
637    }
638
639    /**
640     * @see org.opencms.ade.galleries.client.ui.A_CmsListTab#getTabHandler()
641     */
642    @Override
643    protected CmsGalleriesTabHandler getTabHandler() {
644
645        return m_tabHandler;
646    }
647
648    /**
649     * @see org.opencms.ade.galleries.client.ui.A_CmsListTab#hasQuickFilter()
650     */
651    @Override
652    protected boolean hasQuickFilter() {
653
654        // allow filter if not in tree mode
655        return SortParams.tree != SortParams.valueOf(m_sortSelectBox.getFormValueAsString());
656    }
657
658    /**
659     * Adds more gallery list items to display in the tab, if available.<p>
660     */
661    protected void loadMoreItems() {
662
663        setLoading(true);
664        MoreItemsCommand cmd = new MoreItemsCommand(30);
665        Scheduler.get().scheduleFixedDelay(cmd, 1);
666    }
667
668    /**
669     * Goes up the tree and opens the parents of the item.<p>
670     *
671     * @param item the child item to start from
672     */
673    private void openParents(CmsTreeItem item) {
674
675        if (item != null) {
676            item.setOpen(true);
677            openParents(item.getParentItem());
678        }
679    }
680
681    /**
682     * Shows the tab list is empty label.<p>
683     */
684    private void showIsEmptyLabel() {
685
686        CmsSimpleListItem item = new CmsSimpleListItem();
687        Label isEmptyLabel = new Label(Messages.get().key(Messages.GUI_TAB_GALLERIES_IS_EMPTY_0));
688        item.add(isEmptyLabel);
689        m_scrollList.add(item);
690    }
691}