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        if ((searchObj.getTypes().size() == 1)
539            && !((currentSorting == SortParams.type_asc) || (currentSorting == SortParams.type_desc))) {
540            m_sortSelectBox.setItems(getSortList(false));
541        } else {
542            m_sortSelectBox.setItems(getSortList(true));
543        }
544        m_sortSelectBox.selectValue(searchObj.getSortOrder());
545        displayResultCount(getResultsDisplayed(searchObj), searchObj.getResultCount());
546        m_searchBar.getSearchInput().setFormValueAsString(searchObj.getQuery());
547        if (searchObj.getScope() != null) {
548            m_searchBar.getScopeSelection().setFormValue(searchObj.getScope().name());
549        }
550        paramPanels.addAll(getParamPanels(searchObj));
551
552        m_hasMoreResults = searchObj.hasMore();
553        if (searchObj.hasReplacedResults()) {
554            m_preset = null;
555            getList().scrollToTop();
556            clearList();
557            showParams(paramPanels);
558            addContent(searchObj);
559            getList().getElement().getStyle().clearDisplay();
560        } else if (searchObj.getPage() == 1) {
561            m_preset = null;
562            getList().scrollToTop();
563            clearList();
564            showParams(paramPanels);
565            m_backwardScrollHandler.updateSearchBean(searchObj);
566            getList().getElement().getStyle().clearDisplay();
567            scrollToPreset();
568        } else {
569            showParams(paramPanels);
570            addContent(searchObj);
571        }
572        showUpload(searchObj);
573    }
574
575    /**
576     * Returns the drag and drop handler.<p>
577     *
578     * @return the drag and drop handler
579     */
580    public CmsDNDHandler getDNDHandler() {
581
582        return m_dndHandler;
583    }
584
585    /**
586     * @see org.opencms.ade.galleries.client.ui.A_CmsTab#getParamPanels(org.opencms.ade.galleries.shared.CmsGallerySearchBean)
587     */
588    @Override
589    public List<CmsSearchParamPanel> getParamPanels(CmsGallerySearchBean searchObj) {
590
591        List<CmsSearchParamPanel> result = new ArrayList<CmsSearchParamPanel>();
592        CmsGallerySearchScope scope = CmsGallerySearchScope.valueOf(
593            m_searchBar.getScopeSelection().getFormValueAsString());
594        if ((scope != m_defaultScope)) {
595            CmsSearchParamPanel panel = new CmsSearchParamPanel(
596                Messages.get().key(Messages.GUI_PARAMS_LABEL_SCOPE_0),
597                this);
598            panel.setContent(Messages.get().key(scope.getKey()), ParamType.scope.name());
599            result.add(panel);
600        }
601
602        String query = m_searchBar.getSearchInput().getFormValueAsString();
603        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(query)) {
604            CmsSearchParamPanel panel = new CmsSearchParamPanel(
605                Messages.get().key(Messages.GUI_TAB_SEARCH_LABEL_TEXT_0),
606                this);
607            panel.setContent(query, ParamType.text.name());
608            result.add(panel);
609        }
610
611        return result;
612
613    }
614
615    /**
616     * @see org.opencms.ade.galleries.client.ui.A_CmsListTab#getRequiredHeight()
617     */
618    @Override
619    public int getRequiredHeight() {
620
621        return super.getRequiredHeight()
622            + (m_searchBar.getOffsetHeight())
623            + (m_params.isVisible() ? m_params.getOffsetHeight() + 5 : 21);
624    }
625
626    /**
627     * Returns the delete handler.<p>
628     *
629     * @param resourcePath the resource path of the resource
630     *
631     * @return the delete handler
632     */
633    public DeleteHandler makeDeleteHandler(String resourcePath) {
634
635        return new DeleteHandler(resourcePath);
636    }
637
638    /**
639     * @see org.opencms.ade.galleries.client.ui.A_CmsListTab#onResize()
640     */
641    @Override
642    public void onResize() {
643
644        super.onResize();
645        // check if more result items should be loaded to fill the available height
646        if (m_hasMoreResults
647            && !getTabHandler().isLoading()
648            && (m_list.getOffsetHeight() > (m_scrollList.getOffsetHeight() - 100))) {
649            getTabHandler().onScrollToBottom();
650        }
651    }
652
653    /**
654     * Removes the no params message.<p>
655     */
656    public void removeNoParamMessage() {
657
658        if (m_noParamsMessage != null) {
659            m_tab.remove(m_noParamsMessage);
660        }
661    }
662
663    /**
664     * Removes the query.
665     */
666    public void removeQuery() {
667
668        m_searchBar.getSearchInput().setFormValueAsString("");
669    }
670
671    /**
672     * Removes the scope.
673     */
674    public void removeScope() {
675
676        m_searchBar.getScopeSelection().setFormValueAsString(m_defaultScope.name());
677    }
678
679    /**
680     * Updates the height (with border) of the result list panel according to the search parameter panels shown.<p>
681     */
682    public void updateListSize() {
683
684        int paramsHeight = m_params.isVisible()
685        ? m_params.getOffsetHeight()
686            + CmsDomUtil.getCurrentStyleInt(m_params.getElement(), CmsDomUtil.Style.marginBottom)
687        : 21;
688        int optionsHeight = m_options.getOffsetHeight()
689            + CmsDomUtil.getCurrentStyleInt(m_options.getElement(), CmsDomUtil.Style.marginBottom);
690        int addHeight = m_additionalWidgets.getOffsetHeight()
691            + CmsDomUtil.getCurrentStyleInt(m_additionalWidgets.getElement(), CmsDomUtil.Style.marginBottom);
692
693        int listTop = paramsHeight + optionsHeight + addHeight + 5;
694        // another sanity check, don't set any top value below 35
695        if (listTop > 35) {
696            m_list.getElement().getStyle().setTop(listTop, Unit.PX);
697        }
698    }
699
700    /**
701     * Appends the list items for the search results from a search bean.<p>
702     *
703     * @param searchBean a search bean containing results
704     */
705    protected void addContent(CmsGallerySearchBean searchBean) {
706
707        if (searchBean.getResults() != null) {
708            boolean showPath = SortParams.path_asc.name().equals(searchBean.getSortOrder())
709                || SortParams.path_desc.name().equals(searchBean.getSortOrder());
710            addContentItems(searchBean.getResults(), false, showPath);
711        }
712    }
713
714    /**
715     * Adds list items for a list of search results.<p>
716     *
717     * @param list the list of search results
718     * @param front if true, list items will be added to the front of the list, else at the back
719     * @param showPath <code>true</code> to show the resource path in sub title
720     */
721    protected void addContentItems(List<CmsResultItemBean> list, boolean front, boolean showPath) {
722
723        if (front) {
724            list = Lists.reverse(list);
725        }
726        for (CmsResultItemBean resultItem : list) {
727            addSingleResult(resultItem, front, showPath);
728        }
729        if (isTilingViewAllowed()) {
730            m_selectView.getElement().getStyle().clearDisplay();
731            selectView(m_selectView.getFormValueAsString());
732        } else {
733            m_selectView.getElement().getStyle().setDisplay(Display.NONE);
734            selectView(DETAILS);
735        }
736        onContentChange();
737    }
738
739    /**
740     * Adds a list item for a single search result.<p>
741     *
742     * @param resultItem the search result
743     * @param front if true, adds the list item to the front of the list, else at the back
744     * @param showPath <code>true</code> to show the resource path in sub title
745     */
746    protected void addSingleResult(CmsResultItemBean resultItem, boolean front, boolean showPath) {
747
748        m_types.add(resultItem.getType());
749        boolean hasPreview = m_tabHandler.hasPreview(resultItem.getType());
750        CmsDNDHandler dndHandler = m_dndHandler;
751        if (!m_galleryHandler.filterDnd(resultItem)) {
752            dndHandler = null;
753        }
754        CmsResultListItem listItem = new CmsResultListItem(resultItem, hasPreview, showPath, dndHandler);
755        if (resultItem.isPreset()) {
756            m_preset = listItem;
757        }
758        if (hasPreview) {
759            listItem.addPreviewClickHandler(new PreviewHandler(resultItem.getPath(), resultItem.getType()));
760        }
761        CmsUUID structureId = new CmsUUID(resultItem.getClientId());
762        listItem.getListItemWidget().addButton(
763            new CmsContextMenuButton(structureId, m_contextMenuHandler, AdeContext.gallery));
764        listItem.getListItemWidget().addOpenHandler(new OpenHandler<CmsListItemWidget>() {
765
766            public void onOpen(OpenEvent<CmsListItemWidget> event) {
767
768                onContentChange();
769            }
770        });
771        if (m_tabHandler.hasSelectResource()) {
772            SelectHandler selectHandler = new SelectHandler(
773                resultItem.getPath(),
774                structureId,
775                resultItem.getRawTitle(),
776                resultItem.getType());
777            listItem.addSelectClickHandler(selectHandler);
778
779            // this affects both tiled and non-tiled result lists.
780            listItem.addDoubleClickHandler(selectHandler);
781        }
782        m_galleryHandler.processResultItem(listItem);
783        if (front) {
784            addWidgetToFrontOfList(listItem);
785        } else {
786            addWidgetToList(listItem);
787        }
788    }
789
790    /**
791     * @see org.opencms.ade.galleries.client.ui.A_CmsListTab#clearList()
792     */
793    @Override
794    protected void clearList() {
795
796        super.clearList();
797        m_types.clear();
798    }
799
800    /**
801     * @see org.opencms.ade.galleries.client.ui.A_CmsListTab#getSortList()
802     */
803    @Override
804    protected LinkedHashMap<String, String> getSortList() {
805
806        return getSortList(true);
807    }
808
809    /**
810     * @see org.opencms.ade.galleries.client.ui.A_CmsListTab#getTabHandler()
811     */
812    @Override
813    protected CmsResultsTabHandler getTabHandler() {
814
815        return m_tabHandler;
816    }
817
818    /**
819     * @see org.opencms.ade.galleries.client.ui.A_CmsListTab#hasQuickFilter()
820     */
821    @Override
822    protected boolean hasQuickFilter() {
823
824        // quick filter not available for this tab
825        return false;
826    }
827
828    /**
829     * Scrolls to the result which corresponds to a preset value in the editor.<p>
830     */
831    protected void scrollToPreset() {
832
833        final ScrollPanel scrollPanel = getList();
834        if (m_preset != null) {
835            Widget child = scrollPanel.getWidget();
836            if (child instanceof CmsList<?>) {
837                @SuppressWarnings("unchecked")
838                CmsList<I_CmsListItem> list = (CmsList<I_CmsListItem>)child;
839                if (list.getWidgetCount() > 0) {
840                    final Widget first = (Widget)list.getItem(0);
841                    Timer timer = new Timer() {
842
843                        @Override
844                        public void run() {
845
846                            int firstTop = first.getElement().getAbsoluteTop();
847                            int presetTop = m_preset.getElement().getAbsoluteTop();
848                            final int offset = presetTop - firstTop;
849                            if (offset >= 0) {
850                                scrollPanel.setVerticalScrollPosition(offset);
851                            } else {
852                                // something is seriously wrong with the positioning if this case occurs
853                                scrollPanel.scrollToBottom();
854                            }
855                        }
856                    };
857                    timer.schedule(10);
858                }
859            }
860        }
861    }
862
863    /**
864     * Helper for setting the scroll position of the scroll panel.<p>
865     *
866     * @param pos the scroll position
867     */
868    protected void setScrollPosition(final int pos) {
869
870        getList().setVerticalScrollPosition(pos);
871        Scheduler.get().scheduleDeferred(new ScheduledCommand() {
872
873            /**
874             * @see com.google.gwt.core.client.Scheduler.ScheduledCommand#execute()
875             */
876            public void execute() {
877
878                if (getList().getVerticalScrollPosition() != pos) {
879                    getList().setVerticalScrollPosition(pos);
880                }
881
882            }
883        });
884
885    }
886
887    /**
888     * Selects the view with the given name.<p>
889     *
890     * @param viewName the view name
891     */
892    void selectView(String viewName) {
893
894        if (DETAILS.equals(viewName)) {
895            getList().removeStyleName(I_CmsLayoutBundle.INSTANCE.galleryResultItemCss().tilingList());
896        } else if (SMALL.equals(viewName)) {
897            getList().addStyleName(I_CmsLayoutBundle.INSTANCE.galleryResultItemCss().tilingList());
898            getList().addStyleName(I_CmsLayoutBundle.INSTANCE.galleryResultItemCss().smallThumbnails());
899        } else if (BIG.equals(viewName)) {
900            getList().addStyleName(I_CmsLayoutBundle.INSTANCE.galleryResultItemCss().tilingList());
901            getList().removeStyleName(I_CmsLayoutBundle.INSTANCE.galleryResultItemCss().smallThumbnails());
902        }
903    }
904
905    /**
906     * Creates special upload button (for external link galleries or galleries with custom upload actions).
907     *
908     * @param gallery the gallery bean
909     *
910     * @return the new button
911     */
912    private CmsPushButton createSpecialUploadButton(CmsGalleryFolderBean gallery) {
913
914        if (CmsEditExternalLinkDialog.LINK_GALLERY_RESOURCE_TYPE_NAME.equals(gallery.getType())) {
915            return createNewExternalLinkButton(gallery.getPath());
916        } else if (gallery.getUploadAction() != null) {
917            CmsPushButton uploadButton = new CmsPushButton(I_CmsButton.UPLOAD_SMALL);
918            uploadButton.setText(null);
919            uploadButton.setTitle(Messages.get().key(Messages.GUI_GALLERY_UPLOAD_TITLE_1, gallery.getPath()));
920            uploadButton.setButtonStyle(ButtonStyle.FONT_ICON, null);
921            uploadButton.addClickHandler(new ClickHandler() {
922
923                public void onClick(ClickEvent event) {
924
925                    CmsRpcAction<CmsUUID> action = new CmsRpcAction<CmsUUID>() {
926
927                        @Override
928                        public void execute() {
929
930                            start(0, true);
931                            CmsCoreProvider.getVfsService().getStructureId(gallery.getPath(), this);
932                        }
933
934                        @Override
935                        protected void onResponse(CmsUUID result) {
936
937                            stop(false);
938                            List<CmsUUID> resultIds = new ArrayList<>();
939                            resultIds.add(result);
940                            CmsEmbeddedDialogHandler.openDialog(
941                                gallery.getUploadAction(),
942                                resultIds,
943                                id -> getTabHandler().updateIndex());
944                        }
945
946                    };
947                    action.execute();
948                }
949            });
950            return uploadButton;
951        } else {
952            return null;
953        }
954    }
955
956    /**
957     * Displays the result count.<p>
958     *
959     * @param displayed the displayed result items
960     * @param total the total of result items
961     */
962    private void displayResultCount(int displayed, int total) {
963
964        String message = Messages.get().key(
965            Messages.GUI_LABEL_NUM_RESULTS_2,
966            new Integer(displayed),
967            new Integer(total));
968        m_infoLabel.setText(message);
969    }
970
971    /**
972     * Returns the count of the currently displayed results.<p>
973     *
974     * @param searchObj the search bean
975     *
976     * @return the count of the currently displayed results
977     */
978    private int getResultsDisplayed(CmsGallerySearchBean searchObj) {
979
980        if (searchObj.hasReplacedResults()) {
981            return searchObj.getResults().size();
982        }
983        int resultsDisplayed = searchObj.getMatchesPerPage() * searchObj.getLastPage();
984        return (resultsDisplayed > searchObj.getResultCount()) ? searchObj.getResultCount() : resultsDisplayed;
985    }
986
987    /**
988     * Returns the list of properties to sort the results according to.<p>
989     *
990     * @param includeType <code>true</code> to include sort according to type
991     *
992     * @return the sort list
993     */
994    private LinkedHashMap<String, String> getSortList(boolean includeType) {
995
996        LinkedHashMap<String, String> list = new LinkedHashMap<String, String>();
997        list.put(SortParams.title_asc.name(), Messages.get().key(Messages.GUI_SORT_LABEL_TITLE_ASC_0));
998        list.put(SortParams.title_desc.name(), Messages.get().key(Messages.GUI_SORT_LABEL_TITLE_DECS_0));
999        list.put(
1000            SortParams.dateLastModified_asc.name(),
1001            Messages.get().key(Messages.GUI_SORT_LABEL_DATELASTMODIFIED_ASC_0));
1002        list.put(
1003            SortParams.dateLastModified_desc.name(),
1004            Messages.get().key(Messages.GUI_SORT_LABEL_DATELASTMODIFIED_DESC_0));
1005        list.put(SortParams.path_asc.name(), Messages.get().key(Messages.GUI_SORT_LABEL_PATH_ASC_0));
1006        list.put(SortParams.path_desc.name(), Messages.get().key(Messages.GUI_SORT_LABEL_PATH_DESC_0));
1007        list.put(SortParams.score.name(), Messages.get().key(Messages.GUI_SORT_LABEL_SCORE_0));
1008        if (includeType) {
1009            list.put(SortParams.type_asc.name(), Messages.get().key(Messages.GUI_SORT_LABEL_TYPE_ASC_0));
1010            list.put(SortParams.type_desc.name(), Messages.get().key(Messages.GUI_SORT_LABEL_TYPE_DESC_0));
1011        }
1012        return list;
1013    }
1014
1015    /**
1016     * Checks if the thumbnail tiling view is allowed for the given result items.<p>
1017     *
1018     * @return <code>true</code> if the thumbnail tiling view is allowed for the given result items
1019     */
1020    private boolean isTilingViewAllowed() {
1021
1022        if (m_types.size() == 0) {
1023            return false;
1024        }
1025        for (String typeName : m_types) {
1026            if (!isImagelikeType(typeName)) {
1027                return false;
1028            }
1029        }
1030        return true;
1031
1032    }
1033
1034    /**
1035     * Check if we need a special upload button for the gallery.
1036     *
1037     * @param gallery a gallery bean
1038     * @return true if we need a special upload button
1039     */
1040    private boolean needsSpecialButton(CmsGalleryFolderBean gallery) {
1041
1042        return (gallery.getUploadAction() != null)
1043            || CmsEditExternalLinkDialog.LINK_GALLERY_RESOURCE_TYPE_NAME.equals(gallery.getType());
1044
1045    }
1046
1047    /**
1048     * Displays the selected search parameters in the result tab.<p>
1049     *
1050     * @param paramPanels the list of search parameter panels to show
1051     *
1052     */
1053    private void showParams(List<CmsSearchParamPanel> paramPanels) {
1054
1055        m_params.clear();
1056        if ((paramPanels == null) || (paramPanels.size() == 0)) {
1057            m_params.setVisible(false);
1058            updateListSize();
1059            return;
1060        }
1061        m_params.setVisible(true);
1062        for (CmsSearchParamPanel panel : paramPanels) {
1063            m_params.add(panel);
1064        }
1065        updateListSize();
1066    }
1067
1068    /**
1069     * Shows the upload button if appropriate.<p>
1070     *
1071     * @param searchObj the current search object
1072     */
1073    private void showUpload(CmsGallerySearchBean searchObj) {
1074
1075        boolean uploadDisabled = CmsCoreProvider.get().isUploadDisabled();
1076        Set<String> targets = new HashSet<String>();
1077
1078        if (searchObj.getGalleries() != null) {
1079            targets.addAll(searchObj.getGalleries());
1080        }
1081        if (searchObj.getFolders() != null) {
1082            targets.addAll(searchObj.getFolders());
1083        }
1084        if (m_specialUploadButton != null) {
1085            m_specialUploadButton.removeFromParent();
1086            m_specialUploadButton = null;
1087        }
1088        if (m_uploadButton == null) {
1089            m_uploadButton = createUploadButtonForTarget("", false);
1090            m_uploadButton.addStyleName(I_CmsLayoutBundle.INSTANCE.galleryDialogCss().resultTabUpload());
1091            m_tab.insert(m_uploadButton, 0);
1092        } else {
1093            m_uploadButton.getElement().getStyle().clearDisplay();
1094        }
1095        if (targets.size() == 1) {
1096            CmsGalleryFolderBean galleryFolder = getTabHandler().getGalleryInfo(targets.iterator().next());
1097
1098            if ((galleryFolder != null) && needsSpecialButton(galleryFolder)) {
1099
1100                m_specialUploadButton = createSpecialUploadButton(galleryFolder);
1101                if (m_specialUploadButton != null) {
1102                    m_specialUploadButton.addStyleName(I_CmsLayoutBundle.INSTANCE.galleryDialogCss().resultTabUpload());
1103                    m_tab.insert(m_specialUploadButton, 0);
1104                    if (CmsStringUtil.isEmptyOrWhitespaceOnly(searchObj.getNoUploadReason())) {
1105                        m_specialUploadButton.enable();
1106                    } else {
1107                        m_specialUploadButton.disable(searchObj.getNoUploadReason());
1108                    }
1109                }
1110                m_uploadButton.getElement().getStyle().setDisplay(Display.NONE);
1111            } else {
1112                String uploadTarget = targets.iterator().next();
1113                I_CmsUploadButtonHandler handler = m_uploadButton.getButtonHandler();
1114                if (handler instanceof CmsDialogUploadButtonHandler) {
1115                    ((CmsDialogUploadButtonHandler)handler).setTargetFolder(uploadTarget);
1116                    // in case the upload target is a folder the root path is used
1117                    ((CmsDialogUploadButtonHandler)handler).setIsTargetRootPath(searchObj.getFolders().size() == 1);
1118                    m_uploadButton.updateFileInput();
1119                }
1120                if (CmsStringUtil.isEmptyOrWhitespaceOnly(searchObj.getNoUploadReason())) {
1121                    m_uploadButton.enable();
1122                    m_uploadButton.setTitle(Messages.get().key(Messages.GUI_GALLERY_UPLOAD_TITLE_1, uploadTarget));
1123                } else {
1124                    m_uploadButton.disable(searchObj.getNoUploadReason());
1125                }
1126            }
1127        } else {
1128            m_uploadButton.disable(Messages.get().key(Messages.GUI_GALLERY_UPLOAD_TARGET_UNSPECIFIC_0));
1129        }
1130        if (uploadDisabled) {
1131
1132            for (Widget button : new Widget[] {m_uploadButton, m_specialUploadButton}) {
1133                if (button != null) {
1134                    button.removeFromParent();
1135                }
1136            }
1137            m_uploadButton = null;
1138            m_specialUploadButton = null;
1139        }
1140
1141    }
1142}