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.gwt.client.ui.input.category;
029
030import org.opencms.gwt.client.Messages;
031import org.opencms.gwt.client.ui.CmsList;
032import org.opencms.gwt.client.ui.CmsPushButton;
033import org.opencms.gwt.client.ui.CmsScrollPanel;
034import org.opencms.gwt.client.ui.CmsSimpleListItem;
035import org.opencms.gwt.client.ui.I_CmsButton;
036import org.opencms.gwt.client.ui.I_CmsButton.ButtonStyle;
037import org.opencms.gwt.client.ui.I_CmsButton.Size;
038import org.opencms.gwt.client.ui.I_CmsListItem;
039import org.opencms.gwt.client.ui.I_CmsTruncable;
040import org.opencms.gwt.client.ui.css.I_CmsInputLayoutBundle;
041import org.opencms.gwt.client.ui.css.I_CmsLayoutBundle;
042import org.opencms.gwt.client.ui.input.CmsCategoryField;
043import org.opencms.gwt.client.ui.input.CmsCheckBox;
044import org.opencms.gwt.client.ui.input.CmsSelectBox;
045import org.opencms.gwt.client.ui.input.CmsTextBox;
046import org.opencms.gwt.client.ui.tree.CmsTreeItem;
047import org.opencms.gwt.shared.CmsCategoryBean;
048import org.opencms.gwt.shared.CmsCategoryTreeEntry;
049import org.opencms.util.CmsStringUtil;
050
051import java.util.ArrayList;
052import java.util.Collection;
053import java.util.Collections;
054import java.util.HashMap;
055import java.util.Iterator;
056import java.util.LinkedHashMap;
057import java.util.List;
058import java.util.Map;
059
060import com.google.gwt.core.client.GWT;
061import com.google.gwt.core.client.Scheduler;
062import com.google.gwt.core.client.Scheduler.ScheduledCommand;
063import com.google.gwt.dom.client.Style;
064import com.google.gwt.dom.client.Style.Float;
065import com.google.gwt.dom.client.Style.Unit;
066import com.google.gwt.event.dom.client.ClickEvent;
067import com.google.gwt.event.dom.client.ClickHandler;
068import com.google.gwt.event.logical.shared.HasValueChangeHandlers;
069import com.google.gwt.event.logical.shared.ValueChangeEvent;
070import com.google.gwt.event.logical.shared.ValueChangeHandler;
071import com.google.gwt.event.shared.HandlerRegistration;
072import com.google.gwt.uibinder.client.UiBinder;
073import com.google.gwt.uibinder.client.UiField;
074import com.google.gwt.user.client.Timer;
075import com.google.gwt.user.client.rpc.IsSerializable;
076import com.google.gwt.user.client.ui.Composite;
077import com.google.gwt.user.client.ui.FlowPanel;
078import com.google.gwt.user.client.ui.HasText;
079import com.google.gwt.user.client.ui.Label;
080import com.google.gwt.user.client.ui.Widget;
081
082/**
083 * Builds the category tree.<p>
084 * */
085public class CmsCategoryTree extends Composite implements I_CmsTruncable, HasValueChangeHandlers<List<String>> {
086
087    /** Sorting parameters. */
088    public enum SortParams implements IsSerializable {
089
090        /** Date last modified ascending. */
091        dateLastModified_asc,
092
093        /** Date last modified descending. */
094        dateLastModified_desc,
095
096        /** Resource path ascending sorting. */
097        path_asc,
098
099        /** Resource path descending sorting.*/
100        path_desc,
101
102        /** Title ascending sorting. */
103        title_asc,
104
105        /** Title descending sorting. */
106        title_desc,
107
108        /** Tree.*/
109        tree,
110
111        /** Resource type ascending sorting. */
112        type_asc,
113
114        /** Resource type descending sorting. */
115        type_desc;
116    }
117
118    /**
119     * @see com.google.gwt.uibinder.client.UiBinder
120     */
121    interface I_CmsCategoryTreeUiBinder extends UiBinder<Widget, CmsCategoryTree> {
122        // GWT interface, nothing to do here
123    }
124
125    /**
126     * Inner class for select box handler.<p>
127     */
128    private class CategoryValueChangeHandler implements ValueChangeHandler<String> {
129
130        /**
131         * Default Constructor.<p>
132         */
133        public CategoryValueChangeHandler() {
134
135            // nothing to do
136        }
137
138        /**
139         * Will be triggered if the value in the select box changes.<p>
140         *
141         * @see com.google.gwt.event.logical.shared.ValueChangeHandler#onValueChange(com.google.gwt.event.logical.shared.ValueChangeEvent)
142         */
143        public void onValueChange(ValueChangeEvent<String> event) {
144
145            cancelQuickFilterTimer();
146            if (event.getSource() == m_sortSelectBox) {
147
148                List<CmsTreeItem> categories = new ArrayList<CmsTreeItem>(m_categories.values());
149                SortParams sort = SortParams.valueOf(event.getValue());
150                sort(categories, sort);
151            }
152            if ((event.getSource() == m_quickSearch)) {
153                if (!m_listView) {
154                    m_listView = true;
155                    m_sortSelectBox.setFormValueAsString(SortParams.title_asc.name());
156                }
157                if (hasQuickFilter()) {
158
159                    if ((CmsStringUtil.isEmptyOrWhitespaceOnly(event.getValue()) || (event.getValue().length() >= 2))) {
160                        // only act if filter length is at least 3 characters or empty
161                        scheduleQuickFilterTimer();
162                    }
163                } else {
164                    checkQuickSearchStatus();
165                }
166            }
167        }
168
169    }
170
171    /**
172     * Inner class for check box handler.<p>
173     */
174    private class CheckBoxValueChangeHandler implements ValueChangeHandler<Boolean> {
175
176        /** Path of the TreeItem. */
177        private CmsTreeItem m_item;
178
179        /**
180         * Default constructor.<p>
181         * @param item The CmsTreeItem of this check box
182         */
183        public CheckBoxValueChangeHandler(CmsTreeItem item) {
184
185            m_item = item;
186
187        }
188
189        /**
190         * Is triggered if an check box is selected or deselected.<p>
191         *
192         * @param event The event that is triggered
193         */
194        public void onValueChange(ValueChangeEvent<Boolean> event) {
195
196            toggleSelection(m_item, false);
197        }
198
199    }
200
201    /**
202     * Inner class for check box handler.<p>
203     */
204    private class DataValueClickHander implements ClickHandler {
205
206        /** The TreeItem. */
207        private CmsTreeItem m_item;
208
209        /** Constructor to set the right CmsTreeItem for this handler.<p>
210         *
211         * @param item the CmsTreeItem of this Handler
212         */
213        public DataValueClickHander(CmsTreeItem item) {
214
215            m_item = item;
216        }
217
218        /**
219         * Is triggered if the DataValue widget is clicked.<p>
220         * If its check box was selected the click will deselect this box otherwise it will select it.
221         *
222         * @param event The event that is triggered
223         * */
224        public void onClick(ClickEvent event) {
225
226            if (isEnabled()) {
227                toggleSelection(m_item, true);
228            }
229        }
230
231    }
232
233    /** The filtering delay. */
234    private static final int FILTER_DELAY = 100;
235
236    /** Text metrics key. */
237    private static final String TM_GALLERY_SORT = "gallerySort";
238
239    /** The ui-binder instance for this class. */
240    private static I_CmsCategoryTreeUiBinder uiBinder = GWT.create(I_CmsCategoryTreeUiBinder.class);
241
242    /** Map of categories. */
243    protected Map<String, CmsTreeItem> m_categories;
244
245    /** Map from category paths to the paths of their children. */
246    protected Map<String, List<String>> m_childrens;
247
248    /** A label for displaying additional information about the tab. */
249    protected HasText m_infoLabel;
250
251    /** Vale to store the widget mode. True means the single selection. */
252    protected boolean m_isSingleSelection;
253
254    /** Vale to store the view mode. True means the list view. */
255    protected boolean m_listView;
256
257    /** The option panel. */
258    @UiField
259    protected FlowPanel m_options;
260
261    /** The quick search box. */
262    protected CmsTextBox m_quickSearch;
263
264    /** List of categories selected from the server. */
265    protected List<CmsCategoryTreeEntry> m_resultList;
266
267    /** List of categories. */
268    protected CmsList<CmsTreeItem> m_scrollList;
269
270    /** The quick search button. */
271    protected CmsPushButton m_searchButton;
272
273    /**
274     * List of all selected categories.
275     *
276     * <p>IMPORTANT: This may unfortunately contain either category paths or category site paths.
277     *  */
278    protected Collection<String> m_selectedCategories;
279
280    /** Result string for single selection. */
281    protected String m_singleResult = "";
282
283    /** The select box to change the sort order. */
284    protected CmsSelectBox m_sortSelectBox;
285
286    /** The scroll panel. */
287    @UiField
288    CmsScrollPanel m_list;
289
290    /** The main panel. */
291    @UiField
292    FlowPanel m_tab;
293
294    /** The disable reason, will be displayed as check box title. */
295    private String m_disabledReason;
296
297    /** The quick filter timer. */
298    private Timer m_filterTimer;
299
300    /** The category selection enabled flag. */
301    private boolean m_isEnabled;
302
303    /** Flag, indicating if the category tree should be collapsed when shown first. */
304    private boolean m_showCollapsed;
305
306    /**
307     * Default Constructor.<p>
308     */
309    public CmsCategoryTree() {
310
311        uiBinder.createAndBindUi(this);
312        initWidget(uiBinder.createAndBindUi(this));
313        m_isEnabled = true;
314    }
315
316    /**
317     * Constructor to collect all categories and build a view tree.<p>
318     *
319     * @param selectedCategories A list of all selected categories
320     * @param height The height of this widget
321     * @param isSingleValue Sets the modes of this widget
322     * @param categories the categories
323     **/
324    public CmsCategoryTree(
325        Collection<String> selectedCategories,
326        int height,
327        boolean isSingleValue,
328        List<CmsCategoryTreeEntry> categories) {
329
330        this(selectedCategories, height, isSingleValue, categories, false);
331    }
332
333    /**
334     * Constructor to collect all categories and build a view tree.<p>
335     *
336     * @param selectedCategories A list of all selected categories
337     * @param height The height of this widget
338     * @param isSingleValue Sets the modes of this widget
339     * @param categories the categories
340     * @param showCollapsed if true, the category tree will be collapsed when opened.
341     **/
342    public CmsCategoryTree(
343        Collection<String> selectedCategories,
344        int height,
345        boolean isSingleValue,
346        List<CmsCategoryTreeEntry> categories,
347        boolean showCollapsed) {
348
349        this();
350        m_isSingleSelection = isSingleValue;
351        addStyleName(I_CmsInputLayoutBundle.INSTANCE.inputCss().categoryItem());
352        m_list.addStyleName(I_CmsInputLayoutBundle.INSTANCE.inputCss().categoryScrollPanel());
353        m_selectedCategories = selectedCategories;
354        Iterator<String> it = selectedCategories.iterator();
355        while (it.hasNext()) {
356            m_singleResult = it.next();
357        }
358        m_scrollList = createScrollList();
359        m_list.setHeight(height + "px");
360        m_resultList = categories;
361        m_list.add(m_scrollList);
362        m_showCollapsed = showCollapsed;
363        m_childrens = new HashMap<>();
364        m_categories = new HashMap<>();
365        updateContentTree();
366        normalizeSelectedCategories();
367        init();
368    }
369
370    /**
371     * Adds children item to the category tree and select the categories.<p>
372     *
373     * @param parent the parent item
374     * @param children the list of children
375     * @param selectedCategories the list of categories to select
376     */
377    public void addChildren(
378        CmsTreeItem parent,
379        List<CmsCategoryTreeEntry> children,
380        Collection<String> selectedCategories) {
381
382        if (children != null) {
383            for (CmsCategoryTreeEntry child : children) {
384                // set the category tree item and add to parent tree item
385                CmsTreeItem treeItem = buildTreeItem(child, selectedCategories);
386                m_childrens.get(parent.getId()).add(treeItem.getId());
387                m_childrens.put(treeItem.getId(), new ArrayList<>(child.getChildren().size()));
388                if ((selectedCategories != null) && selectedCategories.contains(child.getPath())) {
389                    openWithParents(parent);
390
391                }
392                if (m_isSingleSelection) {
393                    if (treeItem.getCheckBox().isChecked()) {
394                        parent.getCheckBox().setChecked(false);
395                    }
396                }
397                parent.addChild(treeItem);
398                addChildren(treeItem, child.getChildren(), selectedCategories);
399            }
400        }
401    }
402
403    /**
404     * @see com.google.gwt.event.logical.shared.HasValueChangeHandlers#addValueChangeHandler(com.google.gwt.event.logical.shared.ValueChangeHandler)
405     */
406    public HandlerRegistration addValueChangeHandler(ValueChangeHandler<List<String>> handler) {
407
408        return addHandler(handler, ValueChangeEvent.getType());
409    }
410
411    /**
412     * Disabled the category selection.<p>
413     *
414     * @param disabledReason the disable reason, will be displayed as check box title
415     */
416    public void disable(String disabledReason) {
417
418        if (m_isEnabled
419            || (CmsStringUtil.isNotEmptyOrWhitespaceOnly(disabledReason) && !disabledReason.equals(m_disabledReason))) {
420            m_isEnabled = false;
421            m_disabledReason = disabledReason;
422            m_scrollList.addStyleName(I_CmsInputLayoutBundle.INSTANCE.inputCss().disabled());
423            setListEnabled(m_scrollList, false, disabledReason);
424        }
425    }
426
427    /**
428     * Enables the category selection.<p>
429     */
430    public void enable() {
431
432        if (!m_isEnabled) {
433            m_isEnabled = true;
434            m_disabledReason = null;
435            m_scrollList.removeStyleName(I_CmsInputLayoutBundle.INSTANCE.inputCss().disabled());
436            setListEnabled(m_scrollList, true, null);
437        }
438    }
439
440    /**
441     * Represents a value change event.<p>
442     */
443    public void fireValueChange() {
444
445        ValueChangeEvent.fire(this, getAllSelected());
446    }
447
448    /**
449     * Returns a list of all selected values.<p>
450     *
451     * @return a list of selected values
452     */
453    public List<String> getAllSelected() {
454
455        List<String> result = new ArrayList<String>();
456        for (String cat : m_selectedCategories) {
457            result.add(m_categories.get(cat).getId());
458        }
459        return result;
460    }
461
462    /**
463     * Returns a list of all selected values as Sidepath.<p>
464     *
465     * @return a list of selected values
466     */
467    public List<String> getAllSelectedSitePath() {
468
469        List<String> result = new ArrayList<String>();
470        for (String cat : m_selectedCategories) {
471            result.add(((CmsDataValue)m_categories.get(cat).getMainWidget()).getParameter(2));
472        }
473        return result;
474    }
475
476    /**
477     * Returns the scrollpanel of this widget.<p>
478     *
479     * @return CmsScrollPanel the scrollpanel of this widget
480     * */
481    public CmsScrollPanel getScrollPanel() {
482
483        return m_list;
484    }
485
486    /**
487     * Returns the last selected value.<p>
488     *
489     * @return the last selected value
490     */
491    public List<String> getSelected() {
492
493        List<String> result = new ArrayList<String>();
494        result.add(
495            m_singleResult.isEmpty()
496            ? ""
497            : ((CmsDataValue)m_categories.get(m_singleResult).getMainWidget()).getParameter(2));
498        return result;
499    }
500
501    /**
502     * Returns if the category selection is enabled.<p>
503     *
504     * @return <code>true</code> if the category selection is enabled
505     */
506    public boolean isEnabled() {
507
508        return m_isEnabled;
509    }
510
511    /**
512     * Goes up the tree and opens the parents of the item.<p>
513     *
514     * @param item the child item to start from
515     */
516    public void openWithParents(CmsTreeItem item) {
517
518        if (item != null) {
519            item.setOpen(true);
520            openWithParents(item.getParentItem());
521        }
522    }
523
524    /**
525     * Shows the tab list is empty label.<p>
526     */
527    public void showIsEmptyLabel() {
528
529        CmsSimpleListItem item = new CmsSimpleListItem();
530        Label isEmptyLabel = new Label(Messages.get().key(Messages.GUI_CATEGORIES_IS_EMPTY_0));
531        item.add(isEmptyLabel);
532        m_scrollList.add(item);
533    }
534
535    /**
536     * @see org.opencms.gwt.client.ui.I_CmsTruncable#truncate(java.lang.String, int)
537     */
538    public void truncate(String textMetricsKey, int clientWidth) {
539
540        m_scrollList.truncate(textMetricsKey, clientWidth);
541    }
542
543    /**
544     * Updates the content of the categories list.<p>
545     *
546     * @param treeItemsToShow the updates list of categories tree item beans
547     */
548    public void updateContentList(List<CmsTreeItem> treeItemsToShow) {
549
550        m_scrollList.clearList();
551        if ((treeItemsToShow != null) && !treeItemsToShow.isEmpty()) {
552            for (CmsTreeItem dataValue : treeItemsToShow) {
553                dataValue.removeOpener();
554                m_scrollList.add(dataValue);
555                CmsScrollPanel scrollparent = (CmsScrollPanel)m_scrollList.getParent();
556                scrollparent.onResizeDescendant();
557            }
558        } else {
559            showIsEmptyLabel();
560        }
561        scheduleResize();
562    }
563
564    /**
565     * Updates the content of the categories tree.<p>
566     */
567    public void updateContentTree() {
568
569        m_scrollList.clearList();
570        m_childrens.clear();
571        m_categories.clear();
572        if ((m_resultList != null) && !m_resultList.isEmpty()) {
573            // add the first level and children
574            for (CmsCategoryTreeEntry category : m_resultList) {
575                // set the category tree item and add to list
576                CmsTreeItem treeItem = buildTreeItem(category, m_selectedCategories);
577                m_childrens.put(treeItem.getId(), new ArrayList<>(category.getChildren().size()));
578                addChildren(treeItem, category.getChildren(), m_selectedCategories);
579                if (!category.getPath().isEmpty() || (treeItem.getChildCount() > 0)) {
580                    m_scrollList.add(treeItem);
581                }
582                treeItem.setOpen(!m_showCollapsed);
583            }
584        } else {
585            showIsEmptyLabel();
586        }
587        scheduleResize();
588    }
589
590    /**
591     * Cancels the quick filter timer.<p>
592     */
593    protected void cancelQuickFilterTimer() {
594
595        if (m_filterTimer != null) {
596            m_filterTimer.cancel();
597        }
598    }
599
600    /**
601     * Checks the quick search input and enables/disables the search button accordingly.<p>
602     */
603    protected void checkQuickSearchStatus() {
604
605        if ((m_quickSearch != null) && (m_searchButton != null)) {
606            if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(m_quickSearch.getFormValueAsString())) {
607                m_searchButton.enable();
608            } else {
609                m_searchButton.disable("Enter a search query");
610            }
611        }
612    }
613
614    /**
615     * Creates the quick search/finder box.<p>
616     */
617    protected void createQuickBox() {
618
619        m_quickSearch = new CmsTextBox();
620        // m_quickFilter.setVisible(hasQuickFilter());
621        m_quickSearch.getElement().getStyle().setFloat(Float.RIGHT);
622        m_quickSearch.setTriggerChangeOnKeyPress(true);
623        m_quickSearch.setGhostValue(Messages.get().key(Messages.GUI_QUICK_FINDER_SEARCH_0), true);
624        m_quickSearch.setGhostModeClear(true);
625        m_options.insert(m_quickSearch, 0);
626        m_searchButton = new CmsPushButton();
627        m_searchButton.setImageClass(I_CmsButton.SEARCH);
628        m_searchButton.setButtonStyle(ButtonStyle.FONT_ICON, null);
629        m_searchButton.setSize(Size.small);
630        m_searchButton.getElement().getStyle().setFloat(Style.Float.RIGHT);
631        m_searchButton.getElement().getStyle().setMarginTop(4, Unit.PX);
632        m_searchButton.getElement().getStyle().setMarginLeft(4, Unit.PX);
633        m_options.insert(m_searchButton, 0);
634        m_quickSearch.addValueChangeHandler(new CategoryValueChangeHandler());
635
636        m_filterTimer = new Timer() {
637
638            @Override
639            public void run() {
640
641                quickSearch();
642
643            }
644        };
645        m_searchButton.setTitle(Messages.get().key(Messages.GUI_QUICK_FINDER_SEARCH_0));
646
647        m_searchButton.addClickHandler(new ClickHandler() {
648
649            public void onClick(ClickEvent arg0) {
650
651                quickSearch();
652            }
653        });
654    }
655
656    /**
657     * Creates the list which should contain the list items of the tab.<p>
658     *
659     * @return the newly created list widget
660     */
661    protected CmsList<CmsTreeItem> createScrollList() {
662
663        return new CmsList<CmsTreeItem>();
664    }
665
666    /**
667     * Gets the filtered list of categories.<p>
668     *
669     * @param filter the search string to use for filtering
670     *
671     * @return the filtered category beans
672     */
673    protected List<CmsTreeItem> getFilteredCategories(String filter) {
674
675        List<CmsTreeItem> result = new ArrayList<CmsTreeItem>();
676        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(filter)) {
677            result = new ArrayList<CmsTreeItem>();
678            for (CmsTreeItem category : m_categories.values()) {
679                if (((CmsDataValue)category.getMainWidget()).matchesFilter(filter, 0, 1)) {
680                    result.add(category);
681                }
682            }
683        } else {
684            Iterator<CmsTreeItem> it = m_categories.values().iterator();
685            while (it.hasNext()) {
686                result.add(it.next());
687            }
688
689        }
690        return result;
691    }
692
693    /**
694     * List of all sort parameters.<p>
695     *
696     * @return List of all sort parameters
697     */
698    protected LinkedHashMap<String, String> getSortList() {
699
700        LinkedHashMap<String, String> list = new LinkedHashMap<String, String>();
701        list.put(SortParams.tree.name(), Messages.get().key(Messages.GUI_SORT_LABEL_HIERARCHIC_0));
702        list.put(SortParams.title_asc.name(), Messages.get().key(Messages.GUI_SORT_LABEL_TITLE_ASC_0));
703        list.put(SortParams.title_desc.name(), Messages.get().key(Messages.GUI_SORT_LABEL_TITLE_DECS_0));
704        list.put(SortParams.path_asc.name(), Messages.get().key(Messages.GUI_SORT_LABEL_PATH_ASC_0));
705        list.put(SortParams.path_desc.name(), Messages.get().key(Messages.GUI_SORT_LABEL_PATH_DESC_0));
706
707        return list;
708    }
709
710    /**
711     * Returns true if this widget hat an QuickFilter.<p>
712     *
713     * @return true if this widget hat an QuickFilter
714     */
715    protected boolean hasQuickFilter() {
716
717        // allow filter if not in tree mode
718        return SortParams.tree != SortParams.valueOf(m_sortSelectBox.getFormValueAsString());
719    }
720
721    /**
722     * Call after all handlers have been set.<p>
723     */
724    protected void init() {
725
726        LinkedHashMap<String, String> sortList = getSortList();
727        if (sortList != null) {
728            // generate the sort select box
729            m_sortSelectBox = new CmsSelectBox(sortList);
730            // add the right handler
731            m_sortSelectBox.addValueChangeHandler(new CategoryValueChangeHandler());
732            // style the select box
733            m_sortSelectBox.getElement().getStyle().setWidth(200, Unit.PX);
734            m_sortSelectBox.truncate(TM_GALLERY_SORT, 200);
735            // add it to the right panel
736            m_options.add(m_sortSelectBox);
737            // create the box label
738            Label infoLabel = new Label();
739            infoLabel.setStyleName(I_CmsLayoutBundle.INSTANCE.categoryDialogCss().infoLabel());
740            m_infoLabel = infoLabel;
741            // add it to the right panel
742            m_options.insert(infoLabel, 0);
743            // create quick search box
744            createQuickBox();
745        }
746
747    }
748
749    /**
750     * Sets the search query an selects the result tab.<p>
751     */
752    protected void quickSearch() {
753
754        List<CmsTreeItem> categories = new ArrayList<CmsTreeItem>();
755        if ((m_quickSearch != null)) {
756            categories = getFilteredCategories(hasQuickFilter() ? m_quickSearch.getFormValueAsString() : null);
757            sort(categories, SortParams.valueOf(m_sortSelectBox.getFormValueAsString()));
758        }
759    }
760
761    /**
762     * Removes the quick search/finder box.<p>
763     */
764    protected void removeQuickBox() {
765
766        if (m_quickSearch != null) {
767            m_quickSearch.removeFromParent();
768            m_quickSearch = null;
769        }
770        if (m_searchButton != null) {
771            m_searchButton.removeFromParent();
772            m_searchButton = null;
773        }
774    }
775
776    /**
777     * Schedules the quick filter action.<p>
778     */
779    protected void scheduleQuickFilterTimer() {
780
781        m_filterTimer.schedule(FILTER_DELAY);
782    }
783
784    /**
785     * Sorts a list of tree items according to the sort parameter.<p>
786     *
787     * @param items the items to sort
788     * @param sort the sort parameter
789     */
790    protected void sort(List<CmsTreeItem> items, SortParams sort) {
791
792        int sortParam = -1;
793        boolean ascending = true;
794        switch (sort) {
795            case tree:
796                m_quickSearch.setFormValueAsString("");
797                m_listView = false;
798                updateContentTree();
799                break;
800            case title_asc:
801                sortParam = 0;
802                break;
803            case title_desc:
804                sortParam = 0;
805                ascending = false;
806                break;
807            case path_asc:
808                sortParam = 1;
809                break;
810            case path_desc:
811                sortParam = 1;
812                ascending = false;
813                break;
814            default:
815                break;
816        }
817        if (sortParam != -1) {
818            m_listView = true;
819            items = getFilteredCategories(hasQuickFilter() ? m_quickSearch.getFormValueAsString() : null);
820            Collections.sort(items, new CmsListItemDataComparator(sortParam, ascending));
821            updateContentList(items);
822        }
823    }
824
825    /**
826     * Called if a category is selected/deselected.
827     *
828     * The checkbox state of the selected/deselected item has to be the state BEFORE toggling.
829     *
830     * @param item the tree item that should be selected/deselected.
831     * @param changeState flag, indicating if the checkbox state should be changed.
832     */
833    protected void toggleSelection(CmsTreeItem item, boolean changeState) {
834
835        boolean select = item.getCheckBox().isChecked();
836        select = changeState ? !select : select;
837        if (m_isSingleSelection) {
838            m_selectedCategories.clear();
839            uncheckAll(m_scrollList);
840        }
841        if (select) {
842            if (m_isSingleSelection) {
843                m_singleResult = item.getId();
844            }
845            CmsTreeItem currentItem = item;
846            do {
847                currentItem.getCheckBox().setChecked(true);
848                String id = currentItem.getId();
849                if (!m_selectedCategories.contains(id)) {
850                    m_selectedCategories.add(id);
851                }
852                currentItem = currentItem.getParentItem();
853            } while (currentItem != null);
854        } else {
855            if (m_isSingleSelection) {
856                m_singleResult = "";
857            }
858            deselectChildren(item);
859            CmsTreeItem currentItem = item;
860            do {
861                currentItem.getCheckBox().setChecked(false);
862                String id = currentItem.getId();
863                if (m_selectedCategories.contains(id)) {
864                    m_selectedCategories.remove(id);
865                }
866                currentItem = currentItem.getParentItem();
867            } while ((currentItem != null) && !hasSelectedChildren(currentItem));
868        }
869        fireValueChange();
870    }
871
872    /**
873     * Builds a tree item for the given category.<p>
874     *
875     * @param category the category
876     * @param selectedCategories the selected categories
877     *
878     * @return the tree item widget
879     */
880    private CmsTreeItem buildTreeItem(CmsCategoryTreeEntry category, Collection<String> selectedCategories) {
881
882        // generate the widget that should be shown in the list
883        CmsDataValue dataValue = new CmsDataValue(
884            600,
885            3,
886            CmsCategoryBean.SMALL_ICON_CLASSES,
887            true,
888            category.getTitle(),
889            "rtl:" + category.getPath(),
890            "hide:" + category.getSitePath());
891
892        // create the check box for this item
893        CmsCheckBox checkBox = new CmsCheckBox();
894        // if it has to be selected, select it
895        boolean isPartofPath = false;
896        isPartofPath = CmsCategoryField.isParentCategoryOfSelected(category.getPath(), selectedCategories);
897        if (isPartofPath) {
898            checkBox.setChecked(true);
899        }
900        if (!isEnabled()) {
901            checkBox.disable(m_disabledReason);
902        }
903        if (category.getPath().isEmpty()) {
904            checkBox.setVisible(false);
905        }
906        // bild the CmsTreeItem out of the widget and the check box
907        CmsTreeItem treeItem = new CmsTreeItem(true, checkBox, dataValue);
908        // abb the handler to the check box
909        dataValue.addClickHandler(new DataValueClickHander(treeItem));
910
911        checkBox.addValueChangeHandler(new CheckBoxValueChangeHandler(treeItem));
912
913        // set the right style for the small view
914        treeItem.setSmallView(true);
915        treeItem.setId(category.getPath());
916        // add it to the list of all categories
917        m_categories.put(treeItem.getId(), treeItem);
918        return treeItem;
919    }
920
921    /**
922     * Deselects all child items of the provided item.
923     * @param item the item for which all childs should be deselected.d
924     */
925    private void deselectChildren(CmsTreeItem item) {
926
927        for (String childId : m_childrens.get(item.getId())) {
928            CmsTreeItem child = m_categories.get(childId);
929            deselectChildren(child);
930            child.getCheckBox().setChecked(false);
931            if (m_selectedCategories.contains(childId)) {
932                m_selectedCategories.remove(childId);
933            }
934        }
935    }
936
937    /**
938     * Return true if at least one child of the given tree item is selected.<p>
939     * @param item The CmsTreeItem to start the check
940     * @return true if the given CmsTreeItem or its children is selected
941     */
942    private boolean hasSelectedChildren(CmsTreeItem item) {
943
944        for (String childId : m_childrens.get(item.getId())) {
945            CmsTreeItem child = m_categories.get(childId);
946            if (child.getCheckBox().isChecked() || hasSelectedChildren(child)) {
947                return true;
948            }
949        }
950        return false;
951    }
952
953    /**
954     * Normalize the list of selected categories to fit for the ids of the tree items.
955     */
956    private void normalizeSelectedCategories() {
957
958        Collection<String> normalizedCategories = new ArrayList<String>(m_selectedCategories.size());
959        for (CmsTreeItem item : m_categories.values()) {
960            if (item.getCheckBox().isChecked()) {
961                normalizedCategories.add(item.getId());
962            }
963        }
964        m_selectedCategories = normalizedCategories;
965
966    }
967
968    /**
969     * Schedules the execution of onResize deferred.<p>
970     */
971    private void scheduleResize() {
972
973        Scheduler.get().scheduleDeferred(new ScheduledCommand() {
974
975            public void execute() {
976
977                m_list.onResizeDescendant();
978            }
979        });
980    }
981
982    /**
983     * Sets the given tree list enabled/disabled.<p>
984     *
985     * @param list the list of tree items
986     * @param enabled <code>true</code> to enable
987     * @param disabledReason the disable reason, will be displayed as check box title
988     */
989    private void setListEnabled(CmsList<? extends I_CmsListItem> list, boolean enabled, String disabledReason) {
990
991        for (Widget child : list) {
992            CmsTreeItem treeItem = (CmsTreeItem)child;
993            if (enabled) {
994                treeItem.getCheckBox().enable();
995            } else {
996                treeItem.getCheckBox().disable(disabledReason);
997            }
998            setListEnabled(treeItem.getChildren(), enabled, disabledReason);
999        }
1000    }
1001
1002    /**
1003     * Uncheck all items in the list including all sub-items.
1004     * @param list list of CmsTreeItem entries.
1005     */
1006    private void uncheckAll(CmsList<? extends I_CmsListItem> list) {
1007
1008        for (Widget it : list) {
1009            CmsTreeItem treeItem = (CmsTreeItem)it;
1010            treeItem.getCheckBox().setChecked(false);
1011            uncheckAll(treeItem.getChildren());
1012        }
1013    }
1014
1015}