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