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.CmsResultContextMenuHandler;
031import org.opencms.ade.galleries.client.CmsResultsTabHandler;
032import org.opencms.ade.galleries.client.I_CmsGalleryHandler;
033import org.opencms.ade.galleries.client.Messages;
034import org.opencms.ade.galleries.client.ui.css.I_CmsLayoutBundle;
035import org.opencms.ade.galleries.shared.CmsGalleryFolderBean;
036import org.opencms.ade.galleries.shared.CmsGallerySearchBean;
037import org.opencms.ade.galleries.shared.CmsGallerySearchScope;
038import org.opencms.ade.galleries.shared.CmsResultItemBean;
039import org.opencms.ade.galleries.shared.I_CmsGalleryProviderConstants.GalleryTabId;
040import org.opencms.ade.galleries.shared.I_CmsGalleryProviderConstants.SortParams;
041import org.opencms.ade.upload.client.ui.CmsDialogUploadButtonHandler;
042import org.opencms.gwt.client.CmsCoreProvider;
043import org.opencms.gwt.client.dnd.CmsDNDHandler;
044import org.opencms.gwt.client.rpc.CmsRpcAction;
045import org.opencms.gwt.client.ui.CmsList;
046import org.opencms.gwt.client.ui.CmsListItemWidget;
047import org.opencms.gwt.client.ui.CmsPushButton;
048import org.opencms.gwt.client.ui.I_CmsButton;
049import org.opencms.gwt.client.ui.I_CmsButton.ButtonStyle;
050import org.opencms.gwt.client.ui.I_CmsListItem;
051import org.opencms.gwt.client.ui.contextmenu.CmsContextMenuButton;
052import org.opencms.gwt.client.ui.contextmenu.CmsContextMenuHandler;
053import org.opencms.gwt.client.ui.externallink.CmsEditExternalLinkDialog;
054import org.opencms.gwt.client.ui.input.CmsSelectBox;
055import org.opencms.gwt.client.ui.input.CmsTextBox;
056import org.opencms.gwt.client.ui.input.upload.CmsUploadButton;
057import org.opencms.gwt.client.ui.input.upload.I_CmsUploadButtonHandler;
058import org.opencms.gwt.client.util.CmsDebugLog;
059import org.opencms.gwt.client.util.CmsDomUtil;
060import org.opencms.gwt.client.util.CmsEmbeddedDialogHandler;
061import org.opencms.gwt.shared.CmsCoreData.AdeContext;
062import org.opencms.gwt.shared.CmsGwtConstants;
063import org.opencms.util.CmsStringUtil;
064import org.opencms.util.CmsUUID;
065
066import java.util.ArrayList;
067import java.util.HashSet;
068import java.util.LinkedHashMap;
069import java.util.List;
070import java.util.Map;
071import java.util.Set;
072
073import com.google.common.collect.Lists;
074import com.google.gwt.core.client.GWT;
075import com.google.gwt.core.client.Scheduler;
076import com.google.gwt.core.client.Scheduler.ScheduledCommand;
077import com.google.gwt.dom.client.Style.Display;
078import com.google.gwt.dom.client.Style.Unit;
079import com.google.gwt.event.dom.client.ClickEvent;
080import com.google.gwt.event.dom.client.ClickHandler;
081import com.google.gwt.event.dom.client.DoubleClickEvent;
082import com.google.gwt.event.dom.client.DoubleClickHandler;
083import com.google.gwt.event.dom.client.KeyCodes;
084import com.google.gwt.event.dom.client.ScrollEvent;
085import com.google.gwt.event.dom.client.ScrollHandler;
086import com.google.gwt.event.logical.shared.OpenEvent;
087import com.google.gwt.event.logical.shared.OpenHandler;
088import com.google.gwt.event.logical.shared.ValueChangeEvent;
089import com.google.gwt.event.logical.shared.ValueChangeHandler;
090import com.google.gwt.uibinder.client.UiBinder;
091import com.google.gwt.uibinder.client.UiField;
092import com.google.gwt.uibinder.client.UiHandler;
093import com.google.gwt.uibinder.client.UiTemplate;
094import com.google.gwt.user.client.Timer;
095import com.google.gwt.user.client.ui.Composite;
096import com.google.gwt.user.client.ui.FlowPanel;
097import com.google.gwt.user.client.ui.HTML;
098import com.google.gwt.user.client.ui.ScrollPanel;
099import com.google.gwt.user.client.ui.Widget;
100
101/**
102 * Provides the widget for the results tab.<p>
103 *
104 * It displays the selected search parameter, the sort order and
105 * the search results for the current search.
106 *
107 * @since 8.0.
108 */
109public class CmsResultsTab extends A_CmsListTab {
110
111    /**
112     * Click-handler for the delete button.<p>
113     */
114    public class DeleteHandler implements ClickHandler {
115
116        /** The resource path of the selected item. */
117        protected String m_resourcePath;
118
119        /**
120         * Constructor.<p>
121         *
122         * @param resourcePath the item resource path
123         */
124        protected DeleteHandler(String resourcePath) {
125
126            m_resourcePath = resourcePath;
127        }
128
129        /**
130         * @see com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt.event.dom.client.ClickEvent)
131         */
132        public void onClick(ClickEvent event) {
133
134            getTabHandler().deleteResource(m_resourcePath);
135        }
136
137    }
138
139    /**
140     * Enum representing different options for the results tab.
141     */
142    public enum ParamType {
143        /** Search scope. */
144        scope,
145        /** Query text. */
146        text;
147    }
148
149    /**
150     * Bar containing the search scope selection and a text search field.
151     */
152    public class SearchBar extends Composite {
153
154        /** The field for the text search. */
155        @UiField
156        protected CmsTextBox m_searchInput;
157
158        /** The search button. */
159        @UiField
160        protected CmsPushButton m_textSearchButton;
161
162        /** The select box for the search scope selection. */
163        @UiField
164        protected CmsSelectBox m_scopeSelection;
165
166        /**
167         * Creates a new instance.
168         */
169        public SearchBar() {
170
171            I_CmsSearchBarUiBinder uiBinder = GWT.create(I_CmsSearchBarUiBinder.class);
172            FlowPanel content = uiBinder.createAndBindUi(this);
173            initWidget(content);
174            m_searchInput.setGhostValue(Messages.get().key(Messages.GUI_QUICK_FINDER_SEARCH_0), true);
175            m_searchInput.setGhostModeClear(true);
176            m_textSearchButton.setButtonStyle(ButtonStyle.FONT_ICON, null);
177            m_textSearchButton.setImageClass(I_CmsButton.SEARCH_SMALL);
178            m_textSearchButton.setTitle(Messages.get().key(Messages.GUI_TAB_SEARCH_SEARCH_EXISTING_0));
179        }
180
181        /**
182         * Gets the scope selection widget.
183         *
184         * @return the scope selection widget
185         */
186        public CmsSelectBox getScopeSelection() {
187
188            return m_scopeSelection;
189        }
190
191        /**
192         * Gets the search button.
193         *
194         * @return the search button
195         */
196        public CmsPushButton getSearchButton() {
197
198            return m_textSearchButton;
199        }
200
201        /**
202         * Gets the search input field.
203         *
204         * @return the search input field
205         */
206        public CmsTextBox getSearchInput() {
207
208            return m_searchInput;
209        }
210
211        /**
212         * Handles the change event on the search scope select box.<p>
213         *
214         * @param event the change event
215         */
216        @UiHandler("m_scopeSelection")
217        protected void onScopeChange(ValueChangeEvent<String> event) {
218
219            String value = event.getValue();
220            m_tabHandler.setScope(CmsGallerySearchScope.valueOf(value));
221
222        }
223    }
224
225    /**
226     * Scroll handler which executes an action when the user has scrolled to the bottom.<p>
227     *
228     * @since 8.0.0
229     */
230    protected class CmsAsynchronousScrollToBottomHandler implements ScrollHandler {
231
232        /**
233         * If the lower edge of the content being scrolled is at most this many pixels below the lower
234         * edge of the scrolling viewport, the action is triggered.
235         */
236        public static final int DEFAULT_SCROLL_THRESHOLD = 200;
237
238        /**
239         * Constructs a new scroll handler with a custom scroll threshold.
240         *
241         * The scroll threshold is the distance from the bottom edge of the scrolled content
242         * such that when the distance from the bottom edge of the scroll viewport to the bottom
243         * edge of the scrolled content becomes lower than the distance, the scroll action is triggered.
244         *
245         */
246        public CmsAsynchronousScrollToBottomHandler() {
247
248            // noop
249        }
250
251        /**
252         * @see com.google.gwt.event.dom.client.ScrollHandler#onScroll(com.google.gwt.event.dom.client.ScrollEvent)
253         */
254        public void onScroll(ScrollEvent event) {
255
256            if (!m_hasMoreResults || getTabHandler().isLoading()) {
257                return;
258            }
259            final ScrollPanel scrollPanel = (ScrollPanel)event.getSource();
260            final int scrollPos = scrollPanel.getVerticalScrollPosition();
261            Widget child = scrollPanel.getWidget();
262            int childHeight = child.getOffsetHeight();
263            int ownHeight = scrollPanel.getOffsetHeight();
264            boolean isBottom = (scrollPos + ownHeight) >= (childHeight - DEFAULT_SCROLL_THRESHOLD);
265            if (isBottom) {
266                getTabHandler().onScrollToBottom();
267                setScrollPosition(scrollPos);
268            }
269        }
270    }
271
272    /**
273     * Special click handler to use with preview button.<p>
274     */
275    protected class PreviewHandler implements ClickHandler {
276
277        /** The resource path of the selected item. */
278        private String m_resourcePath;
279
280        /** The resource type of the selected item. */
281        private String m_resourceType;
282
283        /**
284         * Constructor.<p>
285         *
286         * @param resourcePath the item resource path
287         * @param resourceType the item resource type
288         */
289        public PreviewHandler(String resourcePath, String resourceType) {
290
291            m_resourcePath = resourcePath;
292            m_resourceType = resourceType;
293        }
294
295        /**
296         * @see com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt.event.dom.client.ClickEvent)
297         */
298        public void onClick(ClickEvent event) {
299
300            getTabHandler().openPreview(m_resourcePath, m_resourceType);
301
302        }
303    }
304
305    /**
306     * Special click handler to use with select button.<p>
307     */
308    protected class SelectHandler implements ClickHandler, DoubleClickHandler {
309
310        /** The id of the selected item. */
311        private String m_resourcePath;
312
313        /** The resource type of the selected item. */
314        private String m_resourceType;
315
316        /** The structure id. */
317        private CmsUUID m_structureId;
318
319        /** The resource title. */
320        private String m_title;
321
322        /**
323         * Constructor.<p>
324         *
325         * @param resourcePath the item resource path
326         * @param structureId the structure id
327         * @param title the resource title
328         * @param resourceType the item resource type
329         */
330        public SelectHandler(String resourcePath, CmsUUID structureId, String title, String resourceType) {
331
332            m_resourcePath = resourcePath;
333            m_structureId = structureId;
334            m_resourceType = resourceType;
335            m_title = title;
336        }
337
338        /**
339         * @see com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt.event.dom.client.ClickEvent)
340         */
341        public void onClick(ClickEvent event) {
342
343            getTabHandler().selectResource(m_resourcePath, m_structureId, m_title, m_resourceType);
344        }
345
346        /**
347         * @see com.google.gwt.event.dom.client.DoubleClickHandler#onDoubleClick(com.google.gwt.event.dom.client.DoubleClickEvent)
348         */
349        public void onDoubleClick(DoubleClickEvent event) {
350
351            getTabHandler().selectResource(m_resourcePath, m_structureId, m_title, m_resourceType);
352        }
353    }
354
355    /**
356     * UiBinder interface for the search bar.
357     */
358    @UiTemplate("CmsResultsTabSearchBar.ui.xml")
359    interface I_CmsSearchBarUiBinder extends UiBinder<FlowPanel, SearchBar> {
360        // UiBinder
361    }
362
363    /** The big thumbnails view name. */
364    static final String BIG = "big";
365
366    /** The details view name. */
367    static final String DETAILS = "details";
368
369    /** The small thumbnails view name. */
370    static final String SMALL = "small";
371
372    /** The handler for scrolling to the top of the scroll panel. */
373    protected CmsResultsBackwardsScrollHandler m_backwardScrollHandler = new CmsResultsBackwardsScrollHandler(this);
374
375    /** Stores the information if more results in the search object are available. */
376    protected boolean m_hasMoreResults;
377
378    /** The result list item which corresponds to a preset value in the editor. */
379    protected CmsResultListItem m_preset;
380
381    /** The gallery handler. */
382    I_CmsGalleryHandler m_galleryHandler;
383
384    /** The context menu handler. */
385    private CmsContextMenuHandler m_contextMenuHandler;
386
387    /** The optional dnd manager. */
388    private CmsDNDHandler m_dndHandler;
389
390    /** A HTML widget for the message if nor search params were selected. */
391    private HTML m_noParamsMessage;
392
393    /** The panel showing the search parameters. */
394    private FlowPanel m_params;
395
396    /** The view select box. */
397    private CmsSelectBox m_selectView;
398
399    /** The button to create new external link resources. */
400    private CmsPushButton m_specialUploadButton;
401
402    /** The reference to the handler of this tab. */
403    CmsResultsTabHandler m_tabHandler;
404
405    /** Set of resource types currently displayed in the result list. */
406    private Set<String> m_types;
407
408    /** The upload button. */
409    private CmsUploadButton m_uploadButton;
410
411    /** The search bar. */
412    private SearchBar m_searchBar = new SearchBar();
413
414    /** The default scope. */
415    private CmsGallerySearchScope m_defaultScope;
416
417    /**
418     * The constructor.<p>
419     *
420     * @param tabHandler the tab handler
421     * @param dndHandler the dnd manager
422     * @param galleryHandler the gallery handler
423     * @param scope the initial scope
424     * @param defaultScope the default scope
425     **/
426    public CmsResultsTab(
427        CmsResultsTabHandler tabHandler,
428        CmsDNDHandler dndHandler,
429        I_CmsGalleryHandler galleryHandler,
430        CmsGallerySearchScope scope,
431        CmsGallerySearchScope defaultScope) {
432
433        super(GalleryTabId.cms_tab_results);
434        m_defaultScope = defaultScope;
435        m_galleryHandler = galleryHandler;
436        m_additionalWidgets.add(m_searchBar);
437        for (CmsGallerySearchScope choice : CmsGallerySearchScope.values()) {
438            String name = Messages.get().key(choice.getKey());
439            m_searchBar.getScopeSelection().addOption(choice.name(), name);
440        }
441        m_searchBar.getScopeSelection().selectValue(scope.name());
442        m_searchBar.getSearchButton().addClickHandler(event -> {
443            m_tabHandler.updateResult();
444        });
445        m_searchBar.getSearchInput().addValueChangeHandler(event -> {
446            if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(event.getValue()) && (event.getValue().length() >= 3)) {
447                m_tabHandler.setSearchQuery(event.getValue());
448            } else {
449                m_tabHandler.setSearchQuery(null);
450            }
451        });
452        m_searchBar.getSearchInput().addKeyPressHandler(event -> {
453            if (event.getNativeEvent().getKeyCode() == KeyCodes.KEY_ENTER) {
454                m_tabHandler.updateResult();
455            }
456        });
457        tabHandler.addSearchChangeHandler(new ValueChangeHandler<CmsGallerySearchBean>() {
458
459            @SuppressWarnings("synthetic-access")
460            public void onValueChange(ValueChangeEvent<CmsGallerySearchBean> event) {
461
462                // only set the query if the tab is not currently selected
463                if (!isSelected()) {
464                    m_searchBar.getSearchInput().setFormValueAsString(event.getValue().getQuery());
465                }
466            }
467        });
468
469        m_contextMenuHandler = new CmsResultContextMenuHandler(tabHandler);
470        m_types = new HashSet<String>();
471        m_hasMoreResults = false;
472        m_dndHandler = dndHandler;
473        m_tabHandler = tabHandler;
474        m_params = new FlowPanel();
475        m_params.setStyleName(I_CmsLayoutBundle.INSTANCE.galleryDialogCss().tabParamsPanel());
476        m_tab.insert(m_params, 0);
477        getList().addScrollHandler(new CmsAsynchronousScrollToBottomHandler());
478        getList().addScrollHandler(m_backwardScrollHandler);
479        init();
480        Map<String, String> views = new LinkedHashMap<String, String>();
481        views.put(DETAILS, Messages.get().key(Messages.GUI_VIEW_LABEL_DETAILS_0));
482        views.put(SMALL, Messages.get().key(Messages.GUI_VIEW_LABEL_SMALL_ICONS_0));
483        views.put(BIG, Messages.get().key(Messages.GUI_VIEW_LABEL_BIG_ICONS_0));
484        String resultViewType = m_tabHandler.getResultViewType();
485        if (!views.containsKey(resultViewType)) {
486            resultViewType = SMALL;
487        }
488        m_selectView = new CmsSelectBox(views);
489        m_selectView.addStyleName(DIALOG_CSS.selectboxWidth());
490        m_selectView.selectValue(resultViewType);
491        selectView(resultViewType);
492        addWidgetToOptions(m_selectView);
493        m_selectView.addValueChangeHandler(new ValueChangeHandler<String>() {
494
495            public void onValueChange(ValueChangeEvent<String> event) {
496
497                selectView(event.getValue());
498                setScrollPosition(0);
499                onContentChange();
500                getTabHandler().setResultViewType(event.getValue());
501            }
502        });
503    }
504
505    /**
506     * Checks if the type is viewable as an image in the gallery result tab.<p>
507     *
508     * @param typeName the type to check
509     * @return true if the type can be viewed as an image in the result tab
510     */
511    public static boolean isImagelikeType(String typeName) {
512
513        return CmsGwtConstants.TYPE_IMAGE.equals(typeName);
514    }
515
516    /**
517     * Clears all search parameters.<p>
518     */
519    @Override
520    public void clearParams() {
521
522        CmsDebugLog.getInstance().printLine("Unallowed call to clear params in result tab.");
523    }
524
525    /**
526     * Fill the content of the results tab.<p>
527     *
528     * @param searchObj the current search object containing search results
529     * @param paramPanels list of search parameter panels to show
530     */
531    public void fillContent(final CmsGallerySearchBean searchObj, List<CmsSearchParamPanel> paramPanels) {
532
533        removeNoParamMessage();
534
535        // in case there is a single type selected and the current sort order is not by type,
536        // hide the type ascending and type descending sort order options
537        SortParams currentSorting = SortParams.valueOf(searchObj.getSortOrder());
538
539        // this should always be true in the results tab, but put it in an 'if' in case of future changes
540        if (m_sortSelectBox instanceof CmsSelectBox) {
541            if ((searchObj.getTypes().size() == 1)
542                && !((currentSorting == SortParams.type_asc) || (currentSorting == SortParams.type_desc))) {
543                ((CmsSelectBox)m_sortSelectBox).setItems(getSortList(false));
544            } else {
545                ((CmsSelectBox)m_sortSelectBox).setItems(getSortList(true));
546            }
547        } else {
548            CmsDebugLog.consoleLog("gallery result tab sort box is not a CmsSelectBox!");
549        }
550        m_sortSelectBox.selectValue(searchObj.getSortOrder());
551        displayResultCount(getResultsDisplayed(searchObj), searchObj.getResultCount());
552        m_searchBar.getSearchInput().setFormValueAsString(searchObj.getQuery());
553        if (searchObj.getScope() != null) {
554            m_searchBar.getScopeSelection().setFormValue(searchObj.getScope().name());
555        }
556        paramPanels.addAll(getParamPanels(searchObj));
557
558        m_hasMoreResults = searchObj.hasMore();
559        if (searchObj.hasReplacedResults()) {
560            m_preset = null;
561            getList().scrollToTop();
562            clearList();
563            showParams(paramPanels);
564            addContent(searchObj);
565            getList().getElement().getStyle().clearDisplay();
566        } else if (searchObj.getPage() == 1) {
567            m_preset = null;
568            getList().scrollToTop();
569            clearList();
570            showParams(paramPanels);
571            m_backwardScrollHandler.updateSearchBean(searchObj);
572            getList().getElement().getStyle().clearDisplay();
573            scrollToPreset();
574        } else {
575            showParams(paramPanels);
576            addContent(searchObj);
577        }
578        showUpload(searchObj);
579    }
580
581    /**
582     * Returns the drag and drop handler.<p>
583     *
584     * @return the drag and drop handler
585     */
586    public CmsDNDHandler getDNDHandler() {
587
588        return m_dndHandler;
589    }
590
591    /**
592     * @see org.opencms.ade.galleries.client.ui.A_CmsTab#getParamPanels(org.opencms.ade.galleries.shared.CmsGallerySearchBean)
593     */
594    @Override
595    public List<CmsSearchParamPanel> getParamPanels(CmsGallerySearchBean searchObj) {
596
597        List<CmsSearchParamPanel> result = new ArrayList<CmsSearchParamPanel>();
598        CmsGallerySearchScope scope = CmsGallerySearchScope.valueOf(
599            m_searchBar.getScopeSelection().getFormValueAsString());
600        if ((scope != m_defaultScope)) {
601            CmsSearchParamPanel panel = new CmsSearchParamPanel(
602                Messages.get().key(Messages.GUI_PARAMS_LABEL_SCOPE_0),
603                this);
604            panel.setContent(Messages.get().key(scope.getKey()), ParamType.scope.name());
605            result.add(panel);
606        }
607
608        String query = m_searchBar.getSearchInput().getFormValueAsString();
609        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(query)) {
610            CmsSearchParamPanel panel = new CmsSearchParamPanel(
611                Messages.get().key(Messages.GUI_TAB_SEARCH_LABEL_TEXT_0),
612                this);
613            panel.setContent(query, ParamType.text.name());
614            result.add(panel);
615        }
616
617        return result;
618
619    }
620
621    /**
622     * @see org.opencms.ade.galleries.client.ui.A_CmsListTab#getRequiredHeight()
623     */
624    @Override
625    public int getRequiredHeight() {
626
627        return super.getRequiredHeight()
628            + (m_searchBar.getOffsetHeight())
629            + (m_params.isVisible() ? m_params.getOffsetHeight() + 5 : 21);
630    }
631
632    /**
633     * Returns the delete handler.<p>
634     *
635     * @param resourcePath the resource path of the resource
636     *
637     * @return the delete handler
638     */
639    public DeleteHandler makeDeleteHandler(String resourcePath) {
640
641        return new DeleteHandler(resourcePath);
642    }
643
644    /**
645     * @see org.opencms.ade.galleries.client.ui.A_CmsListTab#onResize()
646     */
647    @Override
648    public void onResize() {
649
650        super.onResize();
651        // check if more result items should be loaded to fill the available height
652        if (m_hasMoreResults
653            && !getTabHandler().isLoading()
654            && (m_list.getOffsetHeight() > (m_scrollList.getOffsetHeight() - 100))) {
655            getTabHandler().onScrollToBottom();
656        }
657    }
658
659    /**
660     * Removes the no params message.<p>
661     */
662    public void removeNoParamMessage() {
663
664        if (m_noParamsMessage != null) {
665            m_tab.remove(m_noParamsMessage);
666        }
667    }
668
669    /**
670     * Removes the query.
671     */
672    public void removeQuery() {
673
674        m_searchBar.getSearchInput().setFormValueAsString("");
675    }
676
677    /**
678     * Removes the scope.
679     */
680    public void removeScope() {
681
682        m_searchBar.getScopeSelection().setFormValueAsString(m_defaultScope.name());
683    }
684
685    /**
686     * Updates the height (with border) of the result list panel according to the search parameter panels shown.<p>
687     */
688    public void updateListSize() {
689
690        int paramsHeight = m_params.isVisible()
691        ? m_params.getOffsetHeight()
692            + CmsDomUtil.getCurrentStyleInt(m_params.getElement(), CmsDomUtil.Style.marginBottom)
693        : 21;
694        int optionsHeight = m_options.getOffsetHeight()
695            + CmsDomUtil.getCurrentStyleInt(m_options.getElement(), CmsDomUtil.Style.marginBottom);
696        int addHeight = m_additionalWidgets.getOffsetHeight()
697            + CmsDomUtil.getCurrentStyleInt(m_additionalWidgets.getElement(), CmsDomUtil.Style.marginBottom);
698
699        int listTop = paramsHeight + optionsHeight + addHeight + 5;
700        // another sanity check, don't set any top value below 35
701        if (listTop > 35) {
702            m_list.getElement().getStyle().setTop(listTop, Unit.PX);
703        }
704    }
705
706    /**
707     * Appends the list items for the search results from a search bean.<p>
708     *
709     * @param searchBean a search bean containing results
710     */
711    protected void addContent(CmsGallerySearchBean searchBean) {
712
713        if (searchBean.getResults() != null) {
714            boolean showPath = SortParams.path_asc.name().equals(searchBean.getSortOrder())
715                || SortParams.path_desc.name().equals(searchBean.getSortOrder());
716            addContentItems(searchBean.getResults(), false, showPath);
717        }
718    }
719
720    /**
721     * Adds list items for a list of search results.<p>
722     *
723     * @param list the list of search results
724     * @param front if true, list items will be added to the front of the list, else at the back
725     * @param showPath <code>true</code> to show the resource path in sub title
726     */
727    protected void addContentItems(List<CmsResultItemBean> list, boolean front, boolean showPath) {
728
729        if (front) {
730            list = Lists.reverse(list);
731        }
732        for (CmsResultItemBean resultItem : list) {
733            addSingleResult(resultItem, front, showPath);
734        }
735        if (isTilingViewAllowed()) {
736            m_selectView.getElement().getStyle().clearDisplay();
737            selectView(m_selectView.getFormValueAsString());
738        } else {
739            m_selectView.getElement().getStyle().setDisplay(Display.NONE);
740            selectView(DETAILS);
741        }
742        onContentChange();
743    }
744
745    /**
746     * Adds a list item for a single search result.<p>
747     *
748     * @param resultItem the search result
749     * @param front if true, adds the list item to the front of the list, else at the back
750     * @param showPath <code>true</code> to show the resource path in sub title
751     */
752    protected void addSingleResult(CmsResultItemBean resultItem, boolean front, boolean showPath) {
753
754        m_types.add(resultItem.getType());
755        boolean hasPreview = m_tabHandler.hasPreview(resultItem.getType());
756        CmsDNDHandler dndHandler = m_dndHandler;
757        if (!m_galleryHandler.filterDnd(resultItem)) {
758            dndHandler = null;
759        }
760        CmsResultListItem listItem = new CmsResultListItem(resultItem, hasPreview, showPath, dndHandler);
761        if (resultItem.isPreset()) {
762            m_preset = listItem;
763        }
764        if (hasPreview) {
765            listItem.addPreviewClickHandler(new PreviewHandler(resultItem.getPath(), resultItem.getType()));
766        }
767        CmsUUID structureId = new CmsUUID(resultItem.getClientId());
768        listItem.getListItemWidget().addButton(
769            new CmsContextMenuButton(structureId, m_contextMenuHandler, AdeContext.gallery));
770        listItem.getListItemWidget().addOpenHandler(new OpenHandler<CmsListItemWidget>() {
771
772            public void onOpen(OpenEvent<CmsListItemWidget> event) {
773
774                onContentChange();
775            }
776        });
777        if (m_tabHandler.hasSelectResource()) {
778            SelectHandler selectHandler = new SelectHandler(
779                resultItem.getPath(),
780                structureId,
781                resultItem.getRawTitle(),
782                resultItem.getType());
783            listItem.addSelectClickHandler(selectHandler);
784
785            // this affects both tiled and non-tiled result lists.
786            listItem.addDoubleClickHandler(selectHandler);
787        }
788        m_galleryHandler.processResultItem(listItem);
789        if (front) {
790            addWidgetToFrontOfList(listItem);
791        } else {
792            addWidgetToList(listItem);
793        }
794    }
795
796    /**
797     * @see org.opencms.ade.galleries.client.ui.A_CmsListTab#clearList()
798     */
799    @Override
800    protected void clearList() {
801
802        super.clearList();
803        m_types.clear();
804    }
805
806    /**
807     * @see org.opencms.ade.galleries.client.ui.A_CmsListTab#getSortList()
808     */
809    @Override
810    protected LinkedHashMap<String, String> getSortList() {
811
812        return getSortList(true);
813    }
814
815    /**
816     * @see org.opencms.ade.galleries.client.ui.A_CmsListTab#getTabHandler()
817     */
818    @Override
819    protected CmsResultsTabHandler getTabHandler() {
820
821        return m_tabHandler;
822    }
823
824    /**
825     * @see org.opencms.ade.galleries.client.ui.A_CmsListTab#hasQuickFilter()
826     */
827    @Override
828    protected boolean hasQuickFilter() {
829
830        // quick filter not available for this tab
831        return false;
832    }
833
834    /**
835     * Scrolls to the result which corresponds to a preset value in the editor.<p>
836     */
837    protected void scrollToPreset() {
838
839        final ScrollPanel scrollPanel = getList();
840        if (m_preset != null) {
841            Widget child = scrollPanel.getWidget();
842            if (child instanceof CmsList<?>) {
843                @SuppressWarnings("unchecked")
844                CmsList<I_CmsListItem> list = (CmsList<I_CmsListItem>)child;
845                if (list.getWidgetCount() > 0) {
846                    final Widget first = (Widget)list.getItem(0);
847                    Timer timer = new Timer() {
848
849                        @Override
850                        public void run() {
851
852                            int firstTop = first.getElement().getAbsoluteTop();
853                            int presetTop = m_preset.getElement().getAbsoluteTop();
854                            final int offset = presetTop - firstTop;
855                            if (offset >= 0) {
856                                scrollPanel.setVerticalScrollPosition(offset);
857                            } else {
858                                // something is seriously wrong with the positioning if this case occurs
859                                scrollPanel.scrollToBottom();
860                            }
861                        }
862                    };
863                    timer.schedule(10);
864                }
865            }
866        }
867    }
868
869    /**
870     * Helper for setting the scroll position of the scroll panel.<p>
871     *
872     * @param pos the scroll position
873     */
874    protected void setScrollPosition(final int pos) {
875
876        getList().setVerticalScrollPosition(pos);
877        Scheduler.get().scheduleDeferred(new ScheduledCommand() {
878
879            /**
880             * @see com.google.gwt.core.client.Scheduler.ScheduledCommand#execute()
881             */
882            public void execute() {
883
884                if (getList().getVerticalScrollPosition() != pos) {
885                    getList().setVerticalScrollPosition(pos);
886                }
887
888            }
889        });
890
891    }
892
893    /**
894     * Selects the view with the given name.<p>
895     *
896     * @param viewName the view name
897     */
898    void selectView(String viewName) {
899
900        if (DETAILS.equals(viewName)) {
901            getList().removeStyleName(I_CmsLayoutBundle.INSTANCE.galleryResultItemCss().tilingList());
902        } else if (SMALL.equals(viewName)) {
903            getList().addStyleName(I_CmsLayoutBundle.INSTANCE.galleryResultItemCss().tilingList());
904            getList().addStyleName(I_CmsLayoutBundle.INSTANCE.galleryResultItemCss().smallThumbnails());
905        } else if (BIG.equals(viewName)) {
906            getList().addStyleName(I_CmsLayoutBundle.INSTANCE.galleryResultItemCss().tilingList());
907            getList().removeStyleName(I_CmsLayoutBundle.INSTANCE.galleryResultItemCss().smallThumbnails());
908        }
909    }
910
911    /**
912     * Creates special upload button (for external link galleries or galleries with custom upload actions).
913     *
914     * @param gallery the gallery bean
915     *
916     * @return the new button
917     */
918    private CmsPushButton createSpecialUploadButton(CmsGalleryFolderBean gallery) {
919
920        if (CmsEditExternalLinkDialog.LINK_GALLERY_RESOURCE_TYPE_NAME.equals(gallery.getType())) {
921            return createNewExternalLinkButton(gallery.getPath());
922        } else if (gallery.getUploadAction() != null) {
923            CmsPushButton uploadButton = new CmsPushButton(I_CmsButton.UPLOAD_SMALL);
924            uploadButton.setText(null);
925            uploadButton.setTitle(Messages.get().key(Messages.GUI_GALLERY_UPLOAD_TITLE_1, gallery.getPath()));
926            uploadButton.setButtonStyle(ButtonStyle.FONT_ICON, null);
927            uploadButton.addClickHandler(new ClickHandler() {
928
929                public void onClick(ClickEvent event) {
930
931                    CmsRpcAction<CmsUUID> action = new CmsRpcAction<CmsUUID>() {
932
933                        @Override
934                        public void execute() {
935
936                            start(0, true);
937                            CmsCoreProvider.getVfsService().getStructureId(gallery.getPath(), this);
938                        }
939
940                        @Override
941                        protected void onResponse(CmsUUID result) {
942
943                            stop(false);
944                            List<CmsUUID> resultIds = new ArrayList<>();
945                            resultIds.add(result);
946                            CmsEmbeddedDialogHandler.openDialog(
947                                gallery.getUploadAction(),
948                                resultIds,
949                                id -> getTabHandler().updateIndex());
950                        }
951
952                    };
953                    action.execute();
954                }
955            });
956            return uploadButton;
957        } else {
958            return null;
959        }
960    }
961
962    /**
963     * Displays the result count.<p>
964     *
965     * @param displayed the displayed result items
966     * @param total the total of result items
967     */
968    private void displayResultCount(int displayed, int total) {
969
970        String message = Messages.get().key(
971            Messages.GUI_LABEL_NUM_RESULTS_2,
972            new Integer(displayed),
973            new Integer(total));
974        m_infoLabel.setText(message);
975    }
976
977    /**
978     * Returns the count of the currently displayed results.<p>
979     *
980     * @param searchObj the search bean
981     *
982     * @return the count of the currently displayed results
983     */
984    private int getResultsDisplayed(CmsGallerySearchBean searchObj) {
985
986        if (searchObj.hasReplacedResults()) {
987            return searchObj.getResults().size();
988        }
989        int resultsDisplayed = searchObj.getMatchesPerPage() * searchObj.getLastPage();
990        return (resultsDisplayed > searchObj.getResultCount()) ? searchObj.getResultCount() : resultsDisplayed;
991    }
992
993    /**
994     * Returns the list of properties to sort the results according to.<p>
995     *
996     * @param includeType <code>true</code> to include sort according to type
997     *
998     * @return the sort list
999     */
1000    private LinkedHashMap<String, String> getSortList(boolean includeType) {
1001
1002        LinkedHashMap<String, String> list = new LinkedHashMap<String, String>();
1003        list.put(SortParams.title_asc.name(), Messages.get().key(Messages.GUI_SORT_LABEL_TITLE_ASC_0));
1004        list.put(SortParams.title_desc.name(), Messages.get().key(Messages.GUI_SORT_LABEL_TITLE_DECS_0));
1005        list.put(
1006            SortParams.dateLastModified_asc.name(),
1007            Messages.get().key(Messages.GUI_SORT_LABEL_DATELASTMODIFIED_ASC_0));
1008        list.put(
1009            SortParams.dateLastModified_desc.name(),
1010            Messages.get().key(Messages.GUI_SORT_LABEL_DATELASTMODIFIED_DESC_0));
1011        list.put(SortParams.path_asc.name(), Messages.get().key(Messages.GUI_SORT_LABEL_PATH_ASC_0));
1012        list.put(SortParams.path_desc.name(), Messages.get().key(Messages.GUI_SORT_LABEL_PATH_DESC_0));
1013        list.put(SortParams.score.name(), Messages.get().key(Messages.GUI_SORT_LABEL_SCORE_0));
1014        if (includeType) {
1015            list.put(SortParams.type_asc.name(), Messages.get().key(Messages.GUI_SORT_LABEL_TYPE_ASC_0));
1016            list.put(SortParams.type_desc.name(), Messages.get().key(Messages.GUI_SORT_LABEL_TYPE_DESC_0));
1017        }
1018        return list;
1019    }
1020
1021    /**
1022     * Checks if the thumbnail tiling view is allowed for the given result items.<p>
1023     *
1024     * @return <code>true</code> if the thumbnail tiling view is allowed for the given result items
1025     */
1026    private boolean isTilingViewAllowed() {
1027
1028        if (m_types.size() == 0) {
1029            return false;
1030        }
1031        for (String typeName : m_types) {
1032            if (!isImagelikeType(typeName)) {
1033                return false;
1034            }
1035        }
1036        return true;
1037
1038    }
1039
1040    /**
1041     * Check if we need a special upload button for the gallery.
1042     *
1043     * @param gallery a gallery bean
1044     * @return true if we need a special upload button
1045     */
1046    private boolean needsSpecialButton(CmsGalleryFolderBean gallery) {
1047
1048        return (gallery.getUploadAction() != null)
1049            || CmsEditExternalLinkDialog.LINK_GALLERY_RESOURCE_TYPE_NAME.equals(gallery.getType());
1050
1051    }
1052
1053    /**
1054     * Displays the selected search parameters in the result tab.<p>
1055     *
1056     * @param paramPanels the list of search parameter panels to show
1057     *
1058     */
1059    private void showParams(List<CmsSearchParamPanel> paramPanels) {
1060
1061        m_params.clear();
1062        if ((paramPanels == null) || (paramPanels.size() == 0)) {
1063            m_params.setVisible(false);
1064            updateListSize();
1065            return;
1066        }
1067        m_params.setVisible(true);
1068        for (CmsSearchParamPanel panel : paramPanels) {
1069            m_params.add(panel);
1070        }
1071        updateListSize();
1072    }
1073
1074    /**
1075     * Shows the upload button if appropriate.<p>
1076     *
1077     * @param searchObj the current search object
1078     */
1079    private void showUpload(CmsGallerySearchBean searchObj) {
1080
1081        boolean uploadDisabled = CmsCoreProvider.get().isUploadDisabled();
1082        Set<String> targets = new HashSet<String>();
1083
1084        if (searchObj.getGalleries() != null) {
1085            targets.addAll(searchObj.getGalleries());
1086        }
1087        if (searchObj.getFolders() != null) {
1088            targets.addAll(searchObj.getFolders());
1089        }
1090        if (m_specialUploadButton != null) {
1091            m_specialUploadButton.removeFromParent();
1092            m_specialUploadButton = null;
1093        }
1094        if (m_uploadButton == null) {
1095            m_uploadButton = createUploadButtonForTarget("", false);
1096            m_uploadButton.addStyleName(I_CmsLayoutBundle.INSTANCE.galleryDialogCss().resultTabUpload());
1097            m_tab.insert(m_uploadButton, 0);
1098        } else {
1099            m_uploadButton.getElement().getStyle().clearDisplay();
1100        }
1101        if (targets.size() == 1) {
1102            CmsGalleryFolderBean galleryFolder = getTabHandler().getGalleryInfo(targets.iterator().next());
1103
1104            if ((galleryFolder != null) && needsSpecialButton(galleryFolder)) {
1105
1106                m_specialUploadButton = createSpecialUploadButton(galleryFolder);
1107                if (m_specialUploadButton != null) {
1108                    m_specialUploadButton.addStyleName(I_CmsLayoutBundle.INSTANCE.galleryDialogCss().resultTabUpload());
1109                    m_tab.insert(m_specialUploadButton, 0);
1110                    if (CmsStringUtil.isEmptyOrWhitespaceOnly(searchObj.getNoUploadReason())) {
1111                        m_specialUploadButton.enable();
1112                    } else {
1113                        m_specialUploadButton.disable(searchObj.getNoUploadReason());
1114                    }
1115                }
1116                m_uploadButton.getElement().getStyle().setDisplay(Display.NONE);
1117            } else {
1118                String uploadTarget = targets.iterator().next();
1119                I_CmsUploadButtonHandler handler = m_uploadButton.getButtonHandler();
1120                if (handler instanceof CmsDialogUploadButtonHandler) {
1121                    ((CmsDialogUploadButtonHandler)handler).setTargetFolder(uploadTarget);
1122                    // in case the upload target is a folder the root path is used
1123                    ((CmsDialogUploadButtonHandler)handler).setIsTargetRootPath(searchObj.getFolders().size() == 1);
1124                    m_uploadButton.updateFileInput();
1125                }
1126                if (CmsStringUtil.isEmptyOrWhitespaceOnly(searchObj.getNoUploadReason())) {
1127                    m_uploadButton.enable();
1128                    m_uploadButton.setTitle(Messages.get().key(Messages.GUI_GALLERY_UPLOAD_TITLE_1, uploadTarget));
1129                } else {
1130                    m_uploadButton.disable(searchObj.getNoUploadReason());
1131                }
1132            }
1133        } else {
1134            m_uploadButton.disable(Messages.get().key(Messages.GUI_GALLERY_UPLOAD_TARGET_UNSPECIFIC_0));
1135        }
1136        if (uploadDisabled) {
1137
1138            for (Widget button : new Widget[] {m_uploadButton, m_specialUploadButton}) {
1139                if (button != null) {
1140                    button.removeFromParent();
1141                }
1142            }
1143            m_uploadButton = null;
1144            m_specialUploadButton = null;
1145        }
1146
1147    }
1148}