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.tree; 029 030import org.opencms.gwt.client.dnd.CmsDNDHandler.Orientation; 031import org.opencms.gwt.client.dnd.I_CmsDraggable; 032import org.opencms.gwt.client.dnd.I_CmsDropTarget; 033import org.opencms.gwt.client.ui.CmsList; 034import org.opencms.gwt.client.ui.CmsListItem; 035import org.opencms.gwt.client.ui.CmsToggleButton; 036import org.opencms.gwt.client.ui.I_CmsButton; 037import org.opencms.gwt.client.ui.I_CmsButton.ButtonStyle; 038import org.opencms.gwt.client.ui.I_CmsButton.Size; 039import org.opencms.gwt.client.ui.I_CmsListItem; 040import org.opencms.gwt.client.ui.css.I_CmsLayoutBundle; 041import org.opencms.gwt.client.ui.css.I_CmsLayoutBundle.I_CmsListTreeCss; 042import org.opencms.gwt.client.ui.input.CmsCheckBox; 043import org.opencms.gwt.client.util.CmsDomUtil; 044import org.opencms.gwt.client.util.CmsStyleVariable; 045 046import com.google.common.base.Function; 047import com.google.gwt.animation.client.Animation; 048import com.google.gwt.dom.client.Element; 049import com.google.gwt.event.dom.client.ClickEvent; 050import com.google.gwt.event.dom.client.ClickHandler; 051import com.google.gwt.user.client.ui.Label; 052import com.google.gwt.user.client.ui.Widget; 053 054/** 055 * List tree item implementation.<p> 056 * 057 * Implemented as: 058 * <pre> 059 * <li class='listTreeItem listTreeItem*state*'> 060 * <span class='listTreeItemImage'></span> 061 * <div class='listTreeItemContent'>...*content*</div> 062 * <ul class='listTreeItemChildren'> 063 * *children* 064 * </ul> 065 * </li> 066 * </pre> 067 * 068 * Where state can be <code>opened</code>, <code>closed</code> or <code>leaf</code>.<p> 069 * 070 * @since 8.0.0 071 */ 072public class CmsTreeItem extends CmsListItem { 073 074 /** The duration of the animations. */ 075 public static final int ANIMATION_DURATION = 200; 076 077 /** The CSS bundle used for this widget. */ 078 private static final I_CmsListTreeCss CSS = I_CmsLayoutBundle.INSTANCE.listTreeCss(); 079 080 /** The width of the opener. */ 081 private static final int OPENER_WIDTH = 16; 082 083 /** The children list. */ 084 protected CmsList<CmsTreeItem> m_children; 085 086 /** The element showing the open/close icon. */ 087 protected CmsToggleButton m_opener; 088 089 /** Flag to indicate if drag'n drop is enabled. 3-states: if <code>null</code> the tree decides. */ 090 private Boolean m_dropEnabled; 091 092 /** The style variable controlling this tree item's leaf/non-leaf state. */ 093 private CmsStyleVariable m_leafStyleVar; 094 095 /** Flag to indicate if open or closed. */ 096 private boolean m_open; 097 098 /** The item parent. */ 099 private CmsTreeItem m_parentItem; 100 101 /** The style variable controlling this tree item's open/closed state. */ 102 private CmsStyleVariable m_styleVar; 103 104 /** The tree reference. */ 105 private CmsTree<CmsTreeItem> m_tree; 106 107 /** 108 * Creates a new list tree item containing a main widget and a check box.<p> 109 * 110 * @param showOpeners if true, show open/close icons 111 * @param checkbox the check box 112 * @param mainWidget the main widget 113 */ 114 public CmsTreeItem(boolean showOpeners, CmsCheckBox checkbox, Widget mainWidget) { 115 116 this(showOpeners); 117 addMainWidget(mainWidget); 118 addCheckBox(checkbox); 119 initContent(); 120 if (!showOpeners) { 121 hideOpeners(); 122 } 123 } 124 125 /** 126 * Creates a new list tree item containing a main widget.<p> 127 * 128 * @param showOpeners if true, show open/close icons 129 * @param mainWidget the main widget 130 */ 131 public CmsTreeItem(boolean showOpeners, Widget mainWidget) { 132 133 this(showOpeners); 134 addMainWidget(mainWidget); 135 initContent(); 136 if (!showOpeners) { 137 hideOpeners(); 138 } 139 } 140 141 /** 142 * Creates a new tree item with a 24px wide icon.<p> 143 * 144 * @param showOpeners if <code>true</code>, show open/close icons 145 * @param mainWidget the main widget 146 * @param icon the icon style name 147 */ 148 public CmsTreeItem(boolean showOpeners, Widget mainWidget, String icon) { 149 150 this(showOpeners); 151 addMainWidget(mainWidget); 152 Label label = new Label(); 153 label.addStyleName(icon); 154 addDecoration(label, 28, true); 155 initContent(); 156 if (!showOpeners) { 157 hideOpeners(); 158 } 159 } 160 161 /** 162 * Default constructor.<p> 163 * 164 * @param showOpeners if true, the opener icons should be shown 165 */ 166 protected CmsTreeItem(boolean showOpeners) { 167 168 super(); 169 m_styleVar = new CmsStyleVariable(this); 170 m_leafStyleVar = new CmsStyleVariable(this); 171 m_opener = createOpener(); 172 addDecoration(m_opener, showOpeners ? OPENER_WIDTH : 0, true); 173 m_children = new CmsList<CmsTreeItem>(); 174 m_children.setStyleName(CSS.listTreeItemChildren()); 175 m_panel.add(m_children); 176 onChangeChildren(); 177 m_open = true; 178 setOpen(false); 179 } 180 181 /** 182 * Returns the last opened item of a tree fragment.<p> 183 * 184 * @param item the tree item 185 * @param stopLevel the level to stop at, set -1 to go to the very last opened item 186 * @param requiresDropEnabled <code>true</code> if it is required the returned element to be drop enabled 187 * 188 * @return the last visible item of a tree fragment 189 */ 190 protected static CmsTreeItem getLastOpenedItem(CmsTreeItem item, int stopLevel, boolean requiresDropEnabled) { 191 192 if (stopLevel != -1) { 193 // stop level is set 194 int currentLevel = getPathLevel(item.getPath()); 195 if (currentLevel > stopLevel) { 196 // we are past the stop level, prevent further checks 197 stopLevel = -1; 198 } else if (currentLevel == stopLevel) { 199 // matches stop level 200 return item; 201 } 202 } 203 if (item.getChildCount() > 0) { 204 int childIndex = item.getChildCount() - 1; 205 CmsTreeItem child = item.getChild(childIndex); 206 if (requiresDropEnabled) { 207 while (!child.isDropEnabled()) { 208 childIndex--; 209 if (childIndex < 0) { 210 return item; 211 } 212 child = item.getChild(childIndex); 213 } 214 } 215 216 if (child.isOpen()) { 217 return CmsTreeItem.getLastOpenedItem(child, stopLevel, requiresDropEnabled); 218 } 219 } 220 return item; 221 } 222 223 /** 224 * Method determining the path level by counting the number of '/'.<p> 225 * Example: '/xxx/xxx/' has a path-level of 2.<p> 226 * 227 * @param path the path to test 228 * 229 * @return the path level 230 */ 231 protected static native int getPathLevel(String path)/*-{ 232 return path.match(/\//g).length - 1; 233 }-*/; 234 235 /** 236 * Unsupported operation.<p> 237 * 238 * @see org.opencms.gwt.client.ui.CmsListItem#add(com.google.gwt.user.client.ui.Widget) 239 */ 240 @Override 241 public void add(Widget w) { 242 243 throw new UnsupportedOperationException(); 244 } 245 246 /** 247 * Adds a child list item.<p> 248 * 249 * @param item the child to add 250 * 251 * @see org.opencms.gwt.client.ui.CmsList#addItem(org.opencms.gwt.client.ui.I_CmsListItem) 252 */ 253 public void addChild(CmsTreeItem item) { 254 255 m_children.addItem(item); 256 adopt(item); 257 } 258 259 /** 260 * @see com.google.gwt.user.client.ui.HasWidgets#clear() 261 */ 262 public void clear() { 263 264 clearChildren(); 265 } 266 267 /** 268 * Removes all children.<p> 269 * 270 * @see org.opencms.gwt.client.ui.CmsList#clearList() 271 */ 272 public void clearChildren() { 273 274 for (int i = getChildCount(); i > 0; i--) { 275 removeChild(i - 1); 276 } 277 } 278 279 /** 280 * Closes all empty child entries.<p> 281 */ 282 public void closeAllEmptyChildren() { 283 284 for (Widget child : m_children) { 285 if (child instanceof CmsTreeItem) { 286 CmsTreeItem item = (CmsTreeItem)child; 287 if (item.isOpen()) { 288 if (item.getChildCount() == 0) { 289 item.setOpen(false); 290 } else { 291 item.closeAllEmptyChildren(); 292 } 293 } 294 } 295 } 296 } 297 298 /** 299 * Returns the child tree item at the given position.<p> 300 * 301 * @param index the position 302 * 303 * @return the tree item 304 * 305 * @see org.opencms.gwt.client.ui.CmsList#getItem(int) 306 */ 307 public CmsTreeItem getChild(int index) { 308 309 return m_children.getItem(index); 310 } 311 312 /** 313 * Returns the tree item with the given id.<p> 314 * 315 * @param itemId the id of the item to retrieve 316 * 317 * @return the tree item 318 * 319 * @see org.opencms.gwt.client.ui.CmsList#getItem(String) 320 */ 321 public CmsTreeItem getChild(String itemId) { 322 323 CmsTreeItem result = m_children.getItem(itemId); 324 return result; 325 } 326 327 /** 328 * Helper method which gets the number of children.<p> 329 * 330 * @return the number of children 331 * 332 * @see org.opencms.gwt.client.ui.CmsList#getWidgetCount() 333 */ 334 public int getChildCount() { 335 336 return m_children.getWidgetCount(); 337 } 338 339 /** 340 * Returns the children of this list item.<p> 341 * 342 * @return the children list 343 */ 344 public CmsList<? extends I_CmsListItem> getChildren() { 345 346 return m_children; 347 } 348 349 /** 350 * @see org.opencms.gwt.client.dnd.I_CmsDraggable#getDragHelper(I_CmsDropTarget) 351 */ 352 @Override 353 public Element getDragHelper(I_CmsDropTarget target) { 354 355 // disable animation to get a drag helper without any visible children 356 boolean isAnimated = getTree().isAnimationEnabled(); 357 getTree().setAnimationEnabled(false); 358 setOpen(false); 359 getTree().setAnimationEnabled(isAnimated); 360 return super.getDragHelper(target); 361 } 362 363 /** 364 * Returns the given item position.<p> 365 * 366 * @param item the item to get the position for 367 * 368 * @return the item position 369 */ 370 public int getItemPosition(CmsTreeItem item) { 371 372 return m_children.getWidgetIndex(item); 373 } 374 375 /** 376 * Returns the parent item.<p> 377 * 378 * @return the parent item 379 */ 380 public CmsTreeItem getParentItem() { 381 382 return m_parentItem; 383 } 384 385 /** 386 * @see org.opencms.gwt.client.ui.CmsListItem#getParentTarget() 387 */ 388 @Override 389 public I_CmsDropTarget getParentTarget() { 390 391 return getTree(); 392 } 393 394 /** 395 * Returns the path of IDs for the this item.<p> 396 * 397 * @return a path of IDs separated by slash 398 */ 399 public String getPath() { 400 401 StringBuffer path = new StringBuffer("/"); 402 CmsTreeItem current = this; 403 while (current != null) { 404 path.insert(0, current.getId()).insert(0, "/"); 405 current = current.getParentItem(); 406 } 407 String result = path.toString(); 408 if (result.startsWith("//")) { 409 // This happens if the root item has an empty id. 410 // In that case, we cut off the first slash. 411 result = result.substring(1); 412 } 413 return result; 414 } 415 416 /** 417 * Gets the tree to which this tree item belongs, or null if it does not belong to a tree.<p> 418 * 419 * @return a tree or <code>null</code> 420 */ 421 public CmsTree<CmsTreeItem> getTree() { 422 423 return m_tree; 424 } 425 426 /** 427 * Hides the open/close icons for this tree item and its descendants.<p> 428 */ 429 public void hideOpeners() { 430 431 addStyleName(CSS.listTreeItemNoOpeners()); 432 } 433 434 /** 435 * Inserts the given item at the given position.<p> 436 * 437 * @param item the item to insert 438 * @param position the position 439 * 440 * @see org.opencms.gwt.client.ui.CmsList#insertItem(org.opencms.gwt.client.ui.I_CmsListItem, int) 441 */ 442 public void insertChild(CmsTreeItem item, int position) { 443 444 m_children.insert(item, position); 445 adopt(item); 446 } 447 448 /** 449 * Checks if dropping is enabled.<p> 450 * 451 * @return <code>true</code> if dropping is enabled 452 */ 453 public boolean isDropEnabled() { 454 455 if (m_dropEnabled != null) { 456 return m_dropEnabled.booleanValue(); 457 } 458 CmsTree<?> tree = getTree(); 459 if (tree == null) { 460 return false; 461 } 462 return tree.isDropEnabled(); 463 } 464 465 /** 466 * Checks if the item is open or closed.<p> 467 * 468 * @return <code>true</code> if open 469 */ 470 public boolean isOpen() { 471 472 return m_open; 473 } 474 475 /** 476 * @see org.opencms.gwt.client.dnd.I_CmsDraggable#onDragCancel() 477 */ 478 @Override 479 public void onDragCancel() { 480 481 CmsTreeItem parent = getParentItem(); 482 if (parent != null) { 483 parent.insertChild(this, parent.getItemPosition(this)); 484 } 485 super.onDragCancel(); 486 } 487 488 /** 489 * Removes an item from the list.<p> 490 * 491 * @param item the item to remove 492 * 493 * @return the removed item 494 * 495 * @see org.opencms.gwt.client.ui.CmsList#removeItem(org.opencms.gwt.client.ui.I_CmsListItem) 496 */ 497 public CmsTreeItem removeChild(final CmsTreeItem item) { 498 499 item.setParentItem(null); 500 item.setTree(null); 501 if ((m_tree != null) && m_tree.isAnimationEnabled()) { 502 // could be null if already detached 503 // animate 504 (new Animation() { 505 506 /** 507 * @see com.google.gwt.animation.client.Animation#onComplete() 508 */ 509 @Override 510 protected void onComplete() { 511 512 super.onComplete(); 513 m_children.removeItem(item); 514 onChangeChildren(); 515 } 516 517 /** 518 * @see com.google.gwt.animation.client.Animation#onUpdate(double) 519 */ 520 @Override 521 protected void onUpdate(double progress) { 522 523 item.getElement().getStyle().setOpacity(1 - progress); 524 } 525 }).run(ANIMATION_DURATION); 526 } else { 527 m_children.removeItem(item); 528 onChangeChildren(); 529 } 530 return item; 531 } 532 533 /** 534 * Removes the item identified by the given index from the list.<p> 535 * 536 * @param index the index of the item to remove 537 * 538 * @return the removed item 539 * 540 * @see org.opencms.gwt.client.ui.CmsList#remove(int) 541 */ 542 public CmsTreeItem removeChild(int index) { 543 544 return removeChild(m_children.getItem(index)); 545 } 546 547 /** 548 * Removes an item from the list.<p> 549 * 550 * @param itemId the id of the item to remove 551 * 552 * @return the removed item 553 * 554 * @see org.opencms.gwt.client.ui.CmsList#removeItem(String) 555 */ 556 public CmsTreeItem removeChild(String itemId) { 557 558 return removeChild(m_children.getItem(itemId)); 559 } 560 561 /** 562 * Removes the opener widget.<p> 563 */ 564 public void removeOpener() { 565 566 removeDecorationWidget(m_opener, OPENER_WIDTH); 567 } 568 569 /** 570 * Positions the drag and drop placeholder as a sibling or descendant of this element.<p> 571 * 572 * @param x the cursor client x position 573 * @param y the cursor client y position 574 * @param placeholder the placeholder 575 * @param orientation the drag and drop orientation 576 * 577 * @return the placeholder index 578 */ 579 public int repositionPlaceholder(int x, int y, Element placeholder, Orientation orientation) { 580 581 I_CmsDraggable draggable = null; 582 if (getTree().getDnDHandler() != null) { 583 draggable = getTree().getDnDHandler().getDraggable(); 584 } 585 Element itemElement = getListItemWidget().getElement(); 586 // check if the mouse pointer is within the height of the element 587 int top = CmsDomUtil.getRelativeY(y, itemElement); 588 int height = itemElement.getOffsetHeight(); 589 int index; 590 String parentPath; 591 boolean isParentDndEnabled; 592 CmsTreeItem parentItem = getParentItem(); 593 if (parentItem == null) { 594 index = getTree().getItemPosition(this); 595 parentPath = "/"; 596 isParentDndEnabled = getTree().isRootDropEnabled(); 597 } else { 598 index = parentItem.getItemPosition(this); 599 parentPath = getParentItem().getPath(); 600 isParentDndEnabled = getParentItem().isDropEnabled(); 601 } 602 603 if (top < height) { 604 // the mouse pointer is within the widget 605 int diff = x - getListItemWidget().getAbsoluteLeft(); 606 if ((draggable != this) && isDropEnabled() && (diff > 0) && (diff < 32)) { 607 // over icon 608 getTree().setOpenTimer(this); 609 m_children.getElement().insertBefore(placeholder, m_children.getElement().getFirstChild()); 610 getTree().setPlaceholderPath(getPath()); 611 return 0; 612 } 613 getTree().cancelOpenTimer(); 614 615 // In this case try to drop on the parent 616 if (!isParentDndEnabled) { 617 // we are not allowed to drop here 618 // keeping old position 619 return getTree().getPlaceholderIndex(); 620 } 621 int originalPathLevel = -1; 622 if (draggable instanceof CmsTreeItem) { 623 originalPathLevel = getPathLevel(((CmsTreeItem)draggable).getPath()) - 1; 624 } 625 if (shouldInsertIntoSiblingList(originalPathLevel, parentItem, index)) { 626 @SuppressWarnings("null") 627 CmsTreeItem previousSibling = parentItem.getChild(index - 1); 628 if (previousSibling.isOpen()) { 629 // insert as last into the last opened of the siblings tree fragment 630 return CmsTreeItem.getLastOpenedItem( 631 previousSibling, 632 originalPathLevel, 633 true).insertPlaceholderAsLastChild(placeholder); 634 } 635 } 636 // insert place holder at the parent before the current item 637 getElement().getParentElement().insertBefore(placeholder, getElement()); 638 getTree().setPlaceholderPath(parentPath); 639 return index; 640 } else if ((draggable != this) && isOpen()) { 641 getTree().cancelOpenTimer(); 642 // the mouse pointer is on children 643 for (int childIndex = 0; childIndex < getChildCount(); childIndex++) { 644 CmsTreeItem child = getChild(childIndex); 645 Element childElement = child.getElement(); 646 647 boolean over = false; 648 switch (orientation) { 649 case HORIZONTAL: 650 over = CmsDomUtil.checkPositionInside(childElement, x, -1); 651 break; 652 case VERTICAL: 653 over = CmsDomUtil.checkPositionInside(childElement, -1, y); 654 break; 655 case ALL: 656 default: 657 over = CmsDomUtil.checkPositionInside(childElement, x, y); 658 } 659 if (over) { 660 return child.repositionPlaceholder(x, y, placeholder, orientation); 661 } 662 } 663 } 664 getTree().cancelOpenTimer(); 665 // keeping old position 666 return getTree().getPlaceholderIndex(); 667 } 668 669 /** 670 * Enables/disables dropping.<p> 671 * 672 * @param enabled <code>true</code> to enable, or <code>false</code> to disable 673 */ 674 public void setDropEnabled(boolean enabled) { 675 676 if ((m_dropEnabled != null) && (m_dropEnabled.booleanValue() == enabled)) { 677 return; 678 } 679 m_dropEnabled = Boolean.valueOf(enabled); 680 } 681 682 /** 683 * Sets the tree item style to leaf, hiding the list opener.<p> 684 * 685 * @param isLeaf <code>true</code> to set to leaf style 686 */ 687 public void setLeafStyle(boolean isLeaf) { 688 689 if (isLeaf) { 690 m_leafStyleVar.setValue(CSS.listTreeItemLeaf()); 691 } else { 692 m_leafStyleVar.setValue(CSS.listTreeItemInternal()); 693 } 694 } 695 696 /** 697 * Opens or closes this tree item (i.e. shows or hides its descendants).<p> 698 * 699 * @param open if <code>true</code>, open the tree item, else close it 700 */ 701 public void setOpen(boolean open) { 702 703 setOpen(open, true); 704 } 705 706 /** 707 * Opens or closes this tree item (i.e. shows or hides its descendants).<p> 708 * 709 * @param open if <code>true</code>, open the tree item, else close it 710 * @param fireEvents true if the open/close events should be fired 711 */ 712 public void setOpen(boolean open, boolean fireEvents) { 713 714 if (m_open == open) { 715 return; 716 } 717 m_open = open; 718 executeOpen(fireEvents); 719 CmsDomUtil.resizeAncestor(getParent()); 720 } 721 722 /** 723 * Sets the parent item.<p> 724 * 725 * @param parentItem the parent item to set 726 */ 727 public void setParentItem(CmsTreeItem parentItem) { 728 729 m_parentItem = parentItem; 730 } 731 732 /** 733 * Sets the tree to which this tree item belongs.<p> 734 * 735 * This is automatically called when this tree item or one of its ancestors is inserted into a tree.<p> 736 * 737 * @param tree the tree into which the item has been inserted 738 */ 739 public void setTree(CmsTree<CmsTreeItem> tree) { 740 741 m_tree = tree; 742 for (Widget widget : m_children) { 743 if (widget instanceof CmsTreeItem) { 744 ((CmsTreeItem)widget).setTree(tree); 745 } 746 } 747 } 748 749 /** 750 * Shows the open/close icons for this tree item and its descendants.<p> 751 */ 752 public void showOpeners() { 753 754 removeStyleName(CSS.listTreeItemNoOpeners()); 755 } 756 757 /** 758 * Visits all nested tree items with the given visitor function.<p> 759 * 760 * @param visitor the visitor 761 */ 762 public void visit(Function<CmsTreeItem, Boolean> visitor) { 763 764 visitor.apply(this); 765 for (Widget child : m_children) { 766 ((CmsTreeItem)child).visit(visitor); 767 } 768 } 769 770 /** 771 * Adopts the given item.<p> 772 * 773 * @param item the item to adopt 774 */ 775 protected void adopt(final CmsTreeItem item) { 776 777 item.setParentItem(this); 778 item.setTree(m_tree); 779 onChangeChildren(); 780 if ((m_tree != null) && m_tree.isAnimationEnabled()) { 781 // could be null if not yet attached 782 item.getElement().getStyle().setOpacity(0); 783 // animate 784 (new Animation() { 785 786 /** 787 * @see com.google.gwt.animation.client.Animation#onUpdate(double) 788 */ 789 @Override 790 protected void onUpdate(double progress) { 791 792 item.getElement().getStyle().setOpacity(progress); 793 } 794 }).run(ANIMATION_DURATION); 795 } 796 } 797 798 /** 799 * Creates the button for opening/closing this item.<p> 800 * 801 * @return a button 802 */ 803 protected CmsToggleButton createOpener() { 804 805 final CmsToggleButton opener = new CmsToggleButton(); 806 opener.setButtonStyle(ButtonStyle.FONT_ICON, null); 807 opener.setSize(Size.small); 808 opener.addStyleName(CSS.listTreeItemOpener()); 809 opener.setUpFace("", I_CmsButton.TREE_PLUS); 810 opener.setDownFace("", I_CmsButton.TREE_MINUS); 811 opener.addClickHandler(new ClickHandler() { 812 813 /** 814 * @see com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt.event.dom.client.ClickEvent) 815 */ 816 public void onClick(ClickEvent e) { 817 818 setOpen(opener.isDown()); 819 e.stopPropagation(); 820 e.preventDefault(); 821 } 822 }); 823 return opener; 824 } 825 826 /** 827 * Executes the open call.<p> 828 * 829 * @param fireEvents if true, open/close events will be fired 830 */ 831 protected void executeOpen(boolean fireEvents) { 832 833 m_styleVar.setValue(m_open ? CSS.listTreeItemOpen() : CSS.listTreeItemClosed()); 834 setLeafStyle(false); 835 m_children.getElement().getStyle().clearDisplay(); 836 if (m_opener.isDown() != m_open) { 837 m_opener.setDown(m_open); 838 } 839 if (fireEvents) { 840 if (m_open) { 841 fireOpen(); 842 } else { 843 fireClose(); 844 } 845 } 846 // reset the leaf style according to the child count 847 setLeafStyle(0 == getChildCount()); 848 } 849 850 /** 851 * Fires the close event.<p> 852 */ 853 protected void fireClose() { 854 855 if (m_tree != null) { 856 m_tree.fireClose(this); 857 } 858 } 859 860 /** 861 * Fires the open event on the tree.<p> 862 */ 863 protected void fireOpen() { 864 865 if (m_tree != null) { 866 m_tree.fireOpen(this); 867 } 868 } 869 870 /** 871 * Inserts the placeholder element as last child of the children list. 872 * Setting it's path as the current placeholder path and returning the new index.<p> 873 * 874 * @param placeholder the placeholder element 875 * 876 * @return the new index 877 */ 878 protected int insertPlaceholderAsLastChild(Element placeholder) { 879 880 m_children.getElement().appendChild(placeholder); 881 getTree().setPlaceholderPath(getPath()); 882 return getChildCount(); 883 } 884 885 /** 886 * Helper method which is called when the list of children changes.<p> 887 */ 888 protected void onChangeChildren() { 889 890 setLeafStyle(0 == getChildCount()); 891 } 892 893 /** 894 * Determines if the draggable should be inserted into the previous siblings children list.<p> 895 * 896 * @param originalPathLevel the original path level 897 * @param parent the parent item 898 * @param index the current index 899 * 900 * @return <code>true</code> if the item should be inserted into the previous siblings children list 901 */ 902 private boolean shouldInsertIntoSiblingList(int originalPathLevel, CmsTreeItem parent, int index) { 903 904 if ((index <= 0) || (parent == null)) { 905 return false; 906 } 907 return originalPathLevel != getPathLevel(parent.getPath()); 908 } 909}