Package com.tensegrity.wpalo.client.ui.mvc.fasttree

Source Code of com.tensegrity.wpalo.client.ui.mvc.fasttree.FastMSTree

package com.tensegrity.wpalo.client.ui.mvc.fasttree;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.NoSuchElementException;

import com.extjs.gxt.ui.client.data.LoadEvent;
import com.extjs.gxt.ui.client.event.LoadListener;
import com.google.gwt.core.client.GWT;
import com.google.gwt.i18n.client.LocaleInfo;
import com.google.gwt.libideas.client.StyleInjector;
import com.google.gwt.libideas.resources.client.DataResource;
import com.google.gwt.libideas.resources.client.ImmutableResourceBundle;
import com.google.gwt.libideas.resources.client.TextResource;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.FocusListener;
import com.google.gwt.user.client.ui.FocusListenerCollection;
import com.google.gwt.user.client.ui.HasFocus;
import com.google.gwt.user.client.ui.HasWidgets;
import com.google.gwt.user.client.ui.KeyboardListener;
import com.google.gwt.user.client.ui.KeyboardListenerCollection;
import com.google.gwt.user.client.ui.MouseListener;
import com.google.gwt.user.client.ui.MouseListenerCollection;
import com.google.gwt.user.client.ui.Panel;
import com.google.gwt.user.client.ui.UIObject;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.user.client.ui.impl.FocusImpl;
import com.google.gwt.widgetideas.client.overrides.DOMHelper;
import com.tensegrity.wpalo.client.ui.model.TreeNode;
import com.tensegrity.wpalo.client.ui.mvc.cubeview.SelectionCountListener;

/**
* A standard hierarchical tree widget. The tree contains a hierarchy of
* {@link FastMSTreeItem}s.
*
* Explicitly call FastTree.addDefaultCSS() to include the default style sheet.
*
* <p>
* <h3>CSS Style Rules</h3>
* <ul class='css'>
* <li>.gwt-FastTree { the tree itself }</li>
* <li>.gwt-FastTree .gwt-FastMSTreeItem { a tree item }</li>
* <li>.gwt-FastTree .selection-bar {the selection bar used to highlight the
* selected tree item}</li> </ul>
*/
public class FastMSTree extends Panel implements HasWidgets, HasFocus,
    HasFastMSTreeItems {
  public static final int SELECT   = 0;
  public static final int ADD      = 1;
  public static final int INTERVAL = 2;
  public static final int ADDONLY  = 3;
 
  private final boolean isMultiSelect;
  private boolean childSelect;
  private boolean listenToStateChange = false;
 
  /**
   * Resources used.
   */
  public interface DefaultResources extends ImmutableResourceBundle {

    /**
     * The css file.
     */
    @Resource("FastTree.css")
    TextResource css();

    /**
     * The rtl css file.
     */
    @Resource("FastTreeRTL.css")
    TextResource cssRTL();

    /**
     * The gif used to highlight selection.
     */
    @Resource("selectionBar.gif")
    DataResource selectionBar();

    /**
     * "+" gif.
     */
    @Resource("treeClosed.gif")
    DataResource treeClosed();

    /**
     * "-" gif.
     */
    @Resource("treeOpen.gif")
    DataResource treeOpen();
  }

  private static final String STYLENAME_DEFAULT = "gwt-FastTree";

  private static final String STYLENAME_SELECTION = "selection-bar";

  private static FocusImpl impl = FocusImpl.getFocusImplForPanel();

  public void setListenToStateChange(boolean listen) {
   this.listenToStateChange = listen;
  }
 
  public boolean isListenToStateChange() {
    return listenToStateChange;   
  }
 
  /**
   * Add the default style sheet and images.
   *
   * This method is not called by the Tree and should be called by explicitly
   * by the consumer to include the default style sheet.
   */
  public static void addDefaultCSS() {
    DefaultResources instance = GWT.create(DefaultResources.class);
    if (LocaleInfo.getCurrentLocale().isRTL()) {
      StyleInjector.injectStylesheet(instance.cssRTL().getText(), instance);
    } else {
      StyleInjector.injectStylesheet(instance.css().getText(), instance);
    }
  }

  private static boolean hasModifiers(Event event) {
    boolean alt = event.getAltKey();
    boolean ctrl = event.getCtrlKey();
    boolean meta = event.getMetaKey();
    boolean shift = event.getShiftKey();

    return alt || ctrl || meta || shift;
  }

  private boolean lostMouseDown = true;
  /**
   * Map of TreeItem.widget -> TreeItem.
   */
  private final HashMap<Widget, FastMSTreeItem> childWidgets = new HashMap<Widget, FastMSTreeItem>();
  private final LinkedHashSet<FastMSTreeItem> curSelection = new LinkedHashSet<FastMSTreeItem>();
  private final Element focusable;
  private FocusListenerCollection focusListeners;
  private KeyboardListenerCollection keyboardListeners;
  private MouseListenerCollection mouseListeners;
  private final FastMSTreeItem root;
  private Event keyDown;
  private final ArrayList <LoadListener> loadListeners = new ArrayList<LoadListener>();
  private Event lastKeyDown;
  private FastMSTreeItem lastSelectedItem = null;
  private boolean firingLoadedEvent = false;
  private ArrayList <LoadListener> toBeRemoved = new ArrayList<LoadListener>();
  private ArrayList <LoadListener> toBeAdded = new ArrayList<LoadListener>();
  private final ArrayList <DoubleClickListener> doubleClickListeners = new ArrayList<DoubleClickListener>();
  private final ArrayList <SelectionCountListener> selectionCountListeners = new ArrayList<SelectionCountListener>();
 
  /**
   * Constructs a tree.
   */
  public FastMSTree(boolean ms) {
    childSelect = true;
    isMultiSelect = ms;
    setElement(DOM.createDiv());

    focusable = createFocusElement();
    setStyleName(focusable, STYLENAME_SELECTION);

    sinkEvents(Event.MOUSEEVENTS | Event.ONCLICK | Event.KEYEVENTS
        | Event.MOUSEEVENTS | Event.ONDBLCLICK);

    // The 'root' item is invisible and serves only as a container
    // for all top-level items.
    root = new FastMSTreeItem() {
      @Override
      public void addItem(FastMSTreeItem item) {
        super.addItem(item);

        DOM.appendChild(FastMSTree.this.getElement(), item.getElement());

        // Explicitly set top-level items' parents to null.
        item.setParentItem(null);

        // Use no margin on top-most items.
        DOM.setIntStyleAttribute(item.getElement(), "margin", 0);
      }

      @Override
      public void fastAddItem(FastMSTreeItem item) {
        super.fastAddItem(item);

        DOM.appendChild(FastMSTree.this.getElement(), item.getElement());

        // Explicitly set top-level items' parents to null.
        item.setParentItem(null);

        // Use no margin on top-most items.
        DOM.setIntStyleAttribute(item.getElement(), "margin", 0);
      }

      @Override
      public void removeItem(FastMSTreeItem item) {
        if (!getChildren().contains(item)) {
          return;
        }

        // Update Item state.
        item.clearTree();
        item.setParentItem(null);
        getChildren().remove(item);

        DOM.removeChild(FastMSTree.this.getElement(), item.getElement());
      }
    };
    root.setTree(this);

    setStyleName(STYLENAME_DEFAULT);
    if (!curSelection.isEmpty()) {
      moveSelectionBar(curSelection.iterator().next());
    }
  }

  /**
   * Adds the widget as a root tree item.
   *
   * @see com.google.gwt.user.client.ui.HasWidgets#add(com.google.gwt.user.client.ui.Widget)
   * @param widget widget to add.
   */
  @Override
  public void add(Widget widget) {
    addItem(widget);
  }

  public void expandAll() {
    for (FastMSTreeItem item: getChildren()) {
      item.expandAll();
    }
  }
 
  public void collapseAll() {   
    LinkedHashSet <FastMSTreeItem> sels = new LinkedHashSet<FastMSTreeItem>();
    for (FastMSTreeItem item: getChildren()) {
      item.collapseAll();
      if (item.isSelected()) {
        sels.add(item);
      }
    }   
    fastSetSelectedItems(sels);
  }
 
  public void deepExpand(LinkedHashSet <FastMSTreeItem> nodes) {
    for (FastMSTreeItem node: nodes) {
      node.expandAll();
    }
  }

  public boolean moveItemUp(FastMSTreeItem item) {
    int index = root.getChildren().indexOf(item);
    if (index < 1) {
      return false;
    }
    root.getChildren().remove(index);
    index--;
    root.getChildren().add(index, item);
    Element cElem = item.getElement();
    index = DOM.getChildIndex(FastMSTree.this.getElement(), cElem);
    DOM.removeChild(FastMSTree.this.getElement(), cElem);
    index--;
    DOM.insertChild(FastMSTree.this.getElement(), cElem, index);
    return true;
  }

  public boolean moveItemDown(FastMSTreeItem item) {
    int index = root.getChildren().indexOf(item);
    if (index == -1 || index > root.getChildCount() - 2) {
      return false;
    }
    root.getChildren().remove(index);
    index++;
    root.getChildren().add(index, item);
    Element cElem = item.getElement();
    index = DOM.getChildIndex(FastMSTree.this.getElement(), cElem);
    DOM.removeChild(FastMSTree.this.getElement(), cElem);
    index++;
    DOM.insertChild(FastMSTree.this.getElement(), cElem, index);
    return true;
  }
 
  public int getTotalSize() {
    int result = getChildCount();
    for (FastMSTreeItem it: getChildren()) {
      result += it.getTotalSize();
    }
    return result;
  }
 
  public void addFocusListener(FocusListener listener) {
    if (focusListeners == null) {
      focusListeners = new FocusListenerCollection();
    }
    focusListeners.add(listener);
  }

  /**
   * Adds an item to the root level of this tree.
   *
   * @param item the item to be added
   */
  public void addItem(FastMSTreeItem item) {
    root.addItem(item);   
  }
 
  public void fastAddItem(FastMSTreeItem item) {
    root.fastAddItem(item);
  }

  /**
   * Adds a simple tree item containing the specified text.
   *
   * @param itemText the text of the item to be added
   * @return the item that was added
   */
  public FastMSTreeItem addItem(String itemText) {
    FastMSTreeItem ret = new FastMSTreeItem(itemText);
    addItem(ret);

    return ret;
  }

  /**
   * Adds a new tree item containing the specified widget.
   *
   * @param widget the widget to be added
   */
  public FastMSTreeItem addItem(Widget widget) {
    return root.addItem(widget);
  }

  public void addKeyboardListener(KeyboardListener listener) {
    if (keyboardListeners == null) {
      keyboardListeners = new KeyboardListenerCollection();
    }
    keyboardListeners.add(listener);
  }

  public void addMouseListener(MouseListener listener) {
    if (mouseListeners == null) {
      mouseListeners = new MouseListenerCollection();
    }
    mouseListeners.add(listener);
  }

  /**
   * Clears all tree items from the current tree.
   */
  @Override
  public void clear() {
    int size = root.getChildCount();
    for (int i = size - 1; i >= 0; i--) {
      root.getChild(i).remove();
    }
  }

  /**
   * Ensures that the currently-selected item is visible, opening its parents
   * and scrolling the tree as necessary.
   */
  public void ensureSelectedItemsVisible() {
    if (curSelection == null || curSelection.isEmpty()) {
      return;
    }

    FastMSTreeItem item = curSelection.iterator().next();
     FastMSTreeItem parent = item.getParentItem();
     while (parent != null) {
       parent.setState(true);
       parent = parent.getParentItem();
     }     
     moveFocus(item);

  }

  public void ensurePathOpen(FastMSTreeItem item) {
    if (item == null) {
      return;
    }

    FastMSTreeItem parent = item.getParentItem();
    while (parent != null) {
      parent.setState(true, false);
      parent = parent.getParentItem();
    }   
  }
 
  public void ensureUnselectedItemVisible(FastMSTreeItem item) {
    if (item == null) {
      return;
    }

    FastMSTreeItem parent = item.getParentItem();
    while (parent != null) {
      parent.setState(true);
      parent = parent.getParentItem();
    }
    moveUnselectedFocus(item);
  }
   
  public void ensureItemVisible(FastMSTreeItem item) {
      if (item == null) {
        return;
      }

       FastMSTreeItem parent = item.getParentItem();      
       while (parent != null) {
         parent.setState(true);
         parent = parent.getParentItem();
       }
       moveFocus(item);
    }

  public FastMSTreeItem getChild(int index) {
    return root.getChild(index);
  }

  public int getChildCount() {
    return root.getChildCount();
  }

  public ArrayList <FastMSTreeItem> getChildren() {
    return root.getChildren();
  }
 
  public int getChildIndex(FastMSTreeItem child) {
    return root.getChildIndex(child);
  }

  /**
   * Gets the top-level tree item at the specified index.
   *
   * @param index the index to be retrieved
   * @return the item at that index
   */
  public FastMSTreeItem getItem(int index) {
    return root.getChild(index);
  }

  /**
   * Gets the number of items contained at the root of this tree.
   *
   * @return this tree's item count
   */
  public int getItemCount() {
    return root.getChildCount();
  }

  /**
   * Gets the currently selected item.
   *
   * @return the selected item
   */
  public LinkedHashSet <FastMSTreeItem> getSelectedItems() {
    return curSelection;
  }
 
  public int getNumberOfSelectedItems() {
    return curSelection.size();
  }

  public void addSelectionCountListener(SelectionCountListener l) {
    selectionCountListeners.add(l);
  }
 
  public void removeSelectionCountListener(SelectionCountListener l) {
    selectionCountListeners.remove(l);
  }
 
  public final void fireSelectionNumberChanged() {
   int n = getNumberOfSelectedItems();
    for (SelectionCountListener sl: selectionCountListeners) {
     sl.selectionCountChanged(n);
   }
  }
 
  public int getTabIndex() {
    return impl.getTabIndex(focusable);
  }

  public Iterator<Widget> iterator() {
    final Widget[] widgets = new Widget[childWidgets.size()];
    childWidgets.keySet().toArray(widgets);
    return WidgetIterators.createWidgetIterator(this, widgets);
  }

  @Override
  @SuppressWarnings("fallthrough")
  public void onBrowserEvent(Event event) {
    int eventType = DOM.eventGetType(event);

    switch (eventType) {
      case Event.ONDBLCLICK: {
        doubleClicked(event);
        break;
      }
      case Event.ONCLICK: {
        Element e = DOM.eventGetTarget(event);               
        if (shouldTreeDelegateFocusToElement(e)) {
          // The click event should have given focus to this element already.
          // Avoid moving focus back up to the tree (so that focusable widgets
          // attached to TreeItems can receive keyboard events).
        } else {
//          if (!hasModifiers(event)) {
            clickedOnFocus(DOM.eventGetTarget(event));
//          }
        }
        break;
      }

      case Event.ONMOUSEMOVE: {
        if (mouseListeners != null) {
          mouseListeners.fireMouseEvent(this, event);
        }
        break;
      }

      case Event.ONMOUSEUP: {
        boolean left = event.getButton() == Event.BUTTON_LEFT;

        if (lostMouseDown) {
          // artificial mouse down due to IE bug where mouse downs are lost.

          if (left) {
            elementClicked(root, event);
          }
        }
        if (mouseListeners != null) {
          mouseListeners.fireMouseEvent(this, event);
        }
        lostMouseDown = true;
        break;
      }
      case Event.ONMOUSEDOWN: {
        boolean left = event.getButton() == Event.BUTTON_LEFT;

        lostMouseDown = false;
        if (mouseListeners != null) {
          mouseListeners.fireMouseEvent(this, event);
        }
        if (left) {
          elementClicked(root, event);
        }
        break;
      }
      case Event.ONMOUSEOVER: {
        if (mouseListeners != null) {
          mouseListeners.fireMouseEvent(this, event);
        }
        break;
      }

      case Event.ONMOUSEOUT: {
        if (mouseListeners != null) {
          mouseListeners.fireMouseEvent(this, event);
        }
        break;
      }

      case Event.ONFOCUS:
        // If we already have focus, ignore the focus event.
        if (focusListeners != null) {
          focusListeners.fireFocusEvent(this, event);
        }
        break;

      case Event.ONBLUR: {
        if (focusListeners != null) {
          focusListeners.fireFocusEvent(this, event);
        }

        break;
      }

      case Event.ONKEYDOWN:
        keyDown = event;
        // Intentional fallthrough.
      case Event.ONKEYUP:
//        if (eventType == Event.ONKEYUP) {
//          // If we got here because of a key tab, then we need to make sure the
//          // current tree item is selected.
//          if (DOM.eventGetKeyCode(event) == KeyboardListener.KEY_TAB) {
//            ArrayList<Element> chain = new ArrayList<Element>();
//            collectElementChain(chain, getElement(), DOM.eventGetTarget(event));
//            FastMSTreeItem item = findItemByChain(chain, 0, root);
//            if (item != getSelectedItems()) {
//              setSelectedItems(item, true);
//            }
//          }
//        }

        // Intentional fall through.
      case Event.ONKEYPRESS: {
        if (keyboardListeners != null) {
          keyboardListeners.fireKeyboardEvent(this, event);
        }

        if (hasModifiers(event)) {
          break;
        }

        // Trying to avoid duplicate key downs and fire navigation despite
        // missing key downs.
        if (eventType != Event.ONKEYUP) {
          if (lastKeyDown == null || (!lastKeyDown.equals(keyDown))) {
            //keyboardNavigation(event);
          }
          if (eventType == Event.ONKEYPRESS) {
            lastKeyDown = null;
          } else {
            lastKeyDown = keyDown;
          }
        }
        if (DOMHelper.isArrowKey(DOM.eventGetKeyCode(event))) {
          DOM.eventCancelBubble(event, true);
          DOM.eventPreventDefault(event);
        }
        break;
      }
    }

    // We must call SynthesizedWidget's implementation for all other events.
    super.onBrowserEvent(event);
  }

  private final void doubleClicked(Event event) {
      Element target = DOM.eventGetTarget(event);
      ArrayList<Element> chain = new ArrayList<Element>();
      collectElementChain(chain, getElement(), target);
      FastMSTreeItem item = findItemByChain(chain, 0, root);
      if (item != null) {
        fireDoubleClicked(item);
      }
  }

  public void addDoubleClickListener(DoubleClickListener listener) {
    doubleClickListeners.add(listener);
  }
 
  public void removeDoubleClickListener(DoubleClickListener listener) {
    doubleClickListeners.remove(listener);
  }
 
  private final void fireDoubleClicked(FastMSTreeItem item) {
    for (DoubleClickListener l: doubleClickListeners) {
      l.doubleClicked(item);
    }
  }
 
  @Override
  public boolean remove(Widget w) {
    // Validate.
    FastMSTreeItem item = childWidgets.get(w);
    if (item == null) {
      return false;
    }

    // Delegate to TreeItem.setWidget, which performs correct removal.
    item.setWidget(null);
    return true;
  }

  public void removeFocusListener(FocusListener listener) {
    if (focusListeners != null) {
      focusListeners.remove(listener);
    }
  }

  /**
   * Removes an item from the root level of this tree.
   *
   * @param item the item to be removed
   */
  public void removeItem(FastMSTreeItem item) {
    root.removeItem(item);
  }

  /**
   * Removes all items from the root level of this tree.
   */
  public void removeItems() {
    while (getItemCount() > 0) {
      removeItem(getItem(0));
    }
  }

  public void removeKeyboardListener(KeyboardListener listener) {
    if (keyboardListeners != null) {
      keyboardListeners.remove(listener);
    }
  }

  public void setAccessKey(char key) {
    impl.setAccessKey(focusable, key);
  }

  public void setFocus(boolean focus) {
    if (focus) {
      impl.focus(focusable);
    } else {
      impl.blur(focusable);
    }
  }

  /**
   * Selects a specified item.
   *
   * @param item the item to be selected, or <code>null</code> to deselect all
   *          items
   */
  public void setSelectedItems(LinkedHashSet <FastMSTreeItem> items) {
    setSelectedItems(items, true);
  }

  public void fastSetSelectedItems(LinkedHashSet <FastMSTreeItem> items) {
      Iterator<FastMSTreeItem> it = curSelection.iterator();
      while (it.hasNext()) {     
        FastMSTreeItem item = it.next();
          // Select the item and fire the selection event.
          item.setSelection(false, false);
          it.remove();
    }
    selectTheseItems(items);
  }
 
  /**
   * Selects a specified item.
   *
   * @param item the item to be selected, or <code>null</code> to deselect all
   *          items
   * @param fireEvents <code>true</code> to allow selection events to be fired
   */
  public void setSelectedItems(LinkedHashSet <FastMSTreeItem> items, boolean fireEvents) {
    if (items == null || items.isEmpty()) {
      if (curSelection.isEmpty()) {
        return;
      }
      for (FastMSTreeItem item: curSelection) {
        item.setSelection(false, fireEvents);
      }
      curSelection.clear();
      return;
    } else {
      setSelectedItems(null, false);
    }
   
    for (FastMSTreeItem item: items) {
      onSelection(item, fireEvents, true, ADD);
    }
  }

  public void setTabIndex(int index) {
    impl.setTabIndex(focusable, index);
  }

  /**
   * Iterator of tree items.
   */
  public Iterator<FastMSTreeItem> treeItemIterator() {
    ArrayList<FastMSTreeItem> accum = new ArrayList<FastMSTreeItem>();
    root.dumpTreeItems(accum);
    return accum.iterator();
  }

  @Override
  protected void doAttachChildren() {
    super.doAttachChildren();
    DOM.setEventListener(focusable, this);
  }

  @Override
  protected void doDetachChildren() {
    super.doDetachChildren();
    DOM.setEventListener(focusable, null);
  }

  public FastMSTreeItem getRoot() {
    return root;
  }

//  protected void keyboardNavigation(Event event) {
//    // If nothing's selected, select the first item.
//    if (curSelection == null) {
//      if (root.getChildCount() > 0) {
//        onSelection(root.getChild(0), true, true);
//      }
//      super.onBrowserEvent(event);
//    } else {
//
//      // Handle keyboard events if keyboard navigation is enabled
//
//      switch (DOMHelper.standardizeKeycode(DOM.eventGetKeyCode(event))) {
////        case KeyboardListener.KEY_UP: {
////          moveSelectionUp(curSelection);
////          break;
////        }
////        case KeyboardListener.KEY_DOWN: {
////          moveSelectionDown(curSelection, true);
////          break;
////        }
////        case KeyboardListener.KEY_LEFT: {
////          if (curSelection.isOpen()) {
////            curSelection.setState(false);
////          } else {
////            FastMSTreeItem parent = curSelection.getParentItem();
////            if (parent != null) {
////              setSelectedItems(parent);
////            }
////          }
////          break;
////        }
////        case KeyboardListener.KEY_RIGHT: {
////          if (!curSelection.isOpen()) {
////            curSelection.setState(true);
////          }
////          // Do nothing if the element is already open.
////          break;
////        }
//      }
//    }
//  }

  /**
   * Moves the selection bar around the given {@link FastMSTreeItem}.
   *
   * @param item the item to move selection bar to
   */
  protected void moveSelectionBar(FastMSTreeItem item) {
    if (item == null || item.isShowing() == false) {
      UIObject.setVisible(focusable, false);
      return;
    }
    // focusable is being used for highlight as well.
    // Get the location and size of the given item's content element relative
    // to the tree.
    Element selectedElem = item.getContentElem();
    moveElementOverTarget(focusable, selectedElem);
    UIObject.setVisible(focusable, true);
  }

  @Override
  protected void onLoad() {
    if (!getSelectedItems().isEmpty()) {
      moveSelectionBar(getSelectedItems().iterator().next());
    }
  }

  private final boolean addAllItems(FastMSTreeItem from, FastMSTreeItem to, LinkedHashSet <FastMSTreeItem> items) {
    if (from == null || to == null) {
      return false;
    }
    items.add(from);
    if (from.equals(to)) {
      return true;
    }
    if (childSelect) {
      if (from.getChildren() != null && from.isOpen()) {
        for (FastMSTreeItem kid: from.getChildren()) {       
          if (addAllItems(kid, to, items)) {
            return true;
          }
        }
      }
    } else {
      if (from.getChildren() != null && from.isOpen()) {
        for (FastMSTreeItem kid: from.getChildren()) {       
          if (checkAbortCondition(kid, to)) {
            return true;
          }
        }
      }     
    }
    return false;
  }
 
  private final boolean checkAbortCondition(FastMSTreeItem from, FastMSTreeItem to) {
    if (from == null || to == null) {
      return false;
    }
    if (from.equals(to)) {
      return true;
    }
    if (from.getChildren() != null && from.isOpen()) {
      for (FastMSTreeItem kid: from.getChildren()) {       
        if (checkAbortCondition(kid, to)) {
          return true;
        }
      }
    }
    return false;
  }

  private final boolean collectAllItems(FastMSTreeItem from, FastMSTreeItem to, LinkedHashSet <FastMSTreeItem> items) {
    if (from == null || to == null) {
      return false;
    }
    items.add(from);
    if (from.equals(to)) {
      return true;
    }
    if (childSelect) {
      if (from.getChildren() != null && from.isOpen()) {
        for (FastMSTreeItem kid: from.getChildren()) {
          if (collectAllItems(kid, to, items)) {
            return true;
          }
        }
      }
    } else {
      if (from.getChildren() != null && from.isOpen()) {
        for (FastMSTreeItem kid: from.getChildren()) {
          if (checkAbortCondition(kid, to)) {
            return true;
          }
        }
      }     
    }
    while (from.getParentItem() != null) {
      int index = from.getParentItem().getChildIndex(from);
      index++;
      if (index < from.getParentItem().getChildCount()) {
        for (; index < from.getParentItem().getChildCount(); index++) {
          if (addAllItems(from.getParentItem().getChild(index), to, items)) {
            return true;
          }
        }
      }
      from = from.getParentItem();
    }
    int index = from.getTree().getChildIndex(from);
    if (index != -1) {
      index++;
      if (index < from.getTree().getChildCount()) {
        for (; index < from.getTree().getChildCount(); index++) {
          if (addAllItems(from.getTree().getChild(index), to, items)) {
            return true;
          }
        }
      }
    }
    return false;
  }
 
  public void onDeselection(FastMSTreeItem item) {
    if (item == root) {
      return;
    }
    if (curSelection.isEmpty()) {
      return;
    }
      if (!curSelection.contains(item)) {
        return;
      }
    curSelection.remove(item);
    item.setSelection(false, false);
//    moveSelectionBar(item);
//    moveFocus(item);   
  }
 
  public void selectTheseItems(LinkedHashSet <FastMSTreeItem> items) {
    if (items == null || items.size() == 0) {
      return;
    }
    FastMSTreeItem item = null;
  for (FastMSTreeItem it: items) {
    curSelection.add(it);
        // Select the item and fire the selection event.
        it.setSelection(true, false);
        item = it;
  }     
//    FastMSTreeItem item = items.get(items.size() - 1);
    moveSelectionBar(item);
  lastSelectedItem = item;
  fireSelectionNumberChanged();
  }
   
  public void deselectTheseItems(LinkedHashSet <FastMSTreeItem> items) {
      if (items == null || items.size() == 0) {
        return;
      }
      FastMSTreeItem item = null;
      for (FastMSTreeItem it: items) {
      curSelection.remove(it);
          // Select the item and fire the selection event.
          it.setSelection(false, false);
          item = it;
    }
      moveSelectionBar(item);
      fireSelectionNumberChanged();
  }

  public void setMaySelectChildren(boolean childSelect) {
    this.childSelect = childSelect;
  }
 
  private final boolean checkMaySelect(FastMSTreeItem item) {
    if (childSelect) {
      return true;
    }
    FastMSTreeItem parent = item.getParentItem();
    while (parent != null) {
      if (curSelection.contains(parent)) {
        return false;
      }
      parent = parent.getParentItem();
    }
    return true;
  }
 
  private final LinkedHashSet <FastMSTreeItem> checkDeselectionNecessary(FastMSTreeItem item) {   
    if (childSelect) {
      return null;
    }
    LinkedHashSet <FastMSTreeItem> newSel = new LinkedHashSet<FastMSTreeItem>();
    Iterator <FastMSTreeItem> itemIter = curSelection.iterator();
    while (itemIter.hasNext()) {
      FastMSTreeItem it = itemIter.next();
      FastMSTreeItem parent = it.getParentItem();
      while (parent != null) {
        if (parent.equals(item)) {
          newSel.add(it);
          break;
        }
        parent = parent.getParentItem();
      }
    }
    return newSel.size() == 0 ? null : newSel;
  }
 
  public void onSelection(FastMSTreeItem item, boolean fireEvents,
      boolean moveFocus, int mode) {
    try {
    // 'root' isn't a real item, so don't let it be selected
    // (some cases in the keyboard handler will try to do this)
    if (item == root) {
      return;
    }

//    if (curSelection == item) {
//      return;
//    }
    if (!curSelection.isEmpty()) {
      if (mode == SELECT || !isMultiSelect) {
        Iterator <FastMSTreeItem> iterate = curSelection.iterator();
        while (iterate.hasNext()) {
        FastMSTreeItem it = iterate.next();
        if (!it.beforeSelectionLost()) {
          continue;
        }
        it.setSelection(false, fireEvents);
        iterate.remove();
      }
      }
    }
   
    if (!isMultiSelect) {
      mode = SELECT;
    }
   
    if (isMultiSelect && item != null && mode == INTERVAL && lastSelectedItem != null && lastSelectedItem.isSelected()) {
      // Get all items between item and lastSelectedItem (inclusive)
      if (item.equals(lastSelectedItem)) {
        fireSelectionNumberChanged();
        return;
      }
      FastMSTreeItem from;
      FastMSTreeItem to;
      if (lastSelectedItem.getElement().getAbsoluteTop() < item.getElement().getAbsoluteTop()) {
        // Travel down from lastSelectedItem
        from = lastSelectedItem;
        to = item;
      } else {
        // Travel down from currentItem
        from = item;
        to = lastSelectedItem;
      }
     
      LinkedHashSet <FastMSTreeItem> interval = new LinkedHashSet<FastMSTreeItem>();
      collectAllItems(from, to, interval);
      for (FastMSTreeItem it: interval) {
        curSelection.add(it);
            // Select the item and fire the selection event.
            it.setSelection(true, false);                       
      }
         if (moveFocus) {
           moveFocus(item);
        } else {
             moveSelectionBar(item);
        }
      lastSelectedItem = item;
      fireSelectionNumberChanged();
      return;
    }
    if (item != null) {
      lastSelectedItem = item;
    }   
    if (item != null && !curSelection.contains(item)) {
      if (!checkMaySelect(item)) {
        return;
      }
      LinkedHashSet <FastMSTreeItem> newSel = checkDeselectionNecessary(item);
        if (newSel != null) {
          deselectTheseItems(newSel);
        }
        curSelection.add(item);
        if (!curSelection.isEmpty()) {
          if (moveFocus) {
              moveFocus(item);
            } else {
              // Move highlight even if we do no not need to move focus.
              moveSelectionBar(item);
            }

            // Select the item and fire the selection event.
            item.setSelection(true, fireEvents);
          }       
    } else if (item != null && mode == ADD && isMultiSelect) {
      curSelection.remove(item);
      item.setSelection(false, fireEvents);
        if (moveFocus) {
            moveFocus(item);
          } else {
            // Move highlight even if we do no not need to move focus.
            moveSelectionBar(item);
          }  
    }   
    } catch (Throwable t) {
      t.printStackTrace();
    }
    fireSelectionNumberChanged();
  }

  /**
   * This method is called immediately before a widget will be detached from the
   * browser's document.
   */
  @Override
  protected void onUnload() {
  }

  /**
   * This is called when a valid selectable element is clicked in the tree.
   * Subclasses can override this method to decide whether or not FastTree
   * should keep processing the element clicked. For example, a subclass may
   * decide to return false for this method if selecting a new item in the tree
   * is subject to asynchronous approval from other components of the
   * application.
   *
   * @returns true if element should be processed normally, false otherwise.
   *          Default returns true.
   */
  protected boolean processElementClicked(FastMSTreeItem item) {
    return true;
  }

  void adopt(Widget widget, FastMSTreeItem treeItem) {
    assert (!childWidgets.containsKey(widget));
    childWidgets.put(widget, treeItem);
    super.adopt(widget);
  }

  /*
   * This method exists solely to support unit tests.
   */
  HashMap<Widget, FastMSTreeItem> getChildWidgets() {
    return childWidgets;
  }

  void treeOrphan(Widget widget) {
    super.orphan(widget);

    // Logical detach.
    childWidgets.remove(widget);
  }

  private void clickedOnFocus(Element e) {
    // An element was clicked on that is not focusable, so we use the hidden
    // focusable to not shift focus.
    moveElementOverTarget(focusable, e);
    impl.focus(focusable);
  }

  /**
   * Collects parents going up the element tree, terminated at the tree root.
   */
  private void collectElementChain(ArrayList<Element> chain, Element hRoot,
      Element hElem) {
    if ((hElem == null) || hElem.equals(hRoot)) {
      return;
    }

    collectElementChain(chain, hRoot, DOM.getParent(hElem));
    chain.add(hElem);
  }

  private Element createFocusElement() {
    Element e = impl.createFocusable();
    DOM.setStyleAttribute(e, "position", "absolute");
    DOM.appendChild(getElement(), e);
    DOM.sinkEvents(e, Event.FOCUSEVENTS | Event.ONMOUSEDOWN);
    // Needed for IE only
    DOM.setElementAttribute(e, "focus", "false");
    return e;
  }

  /**
   * Disables the selection text on IE.
   */
  private native void disableSelection(Element element)
  /*-{
    element.onselectstart = function() {
      return false;
    };
  }-*/;

  private void elementClicked(FastMSTreeItem root, Event event) {
    Element target = DOM.eventGetTarget(event);
    ArrayList<Element> chain = new ArrayList<Element>();
    collectElementChain(chain, getElement(), target);
    FastMSTreeItem item = findItemByChain(chain, 0, root);
    if (item != null) {
      if (item.isInteriorNode() && item.getControlElement().equals(target)) {
        item.getTree().setListenToStateChange(true);
        item.setState(!item.isOpen(), true);
        item.getTree().setListenToStateChange(false);
        moveSelectionBar(item);
        disableSelection(target);
      } else if (processElementClicked(item)) {
        if (event.getCtrlKey()) {
        onSelection(item, true, !shouldTreeDelegateFocusToElement(target), ADD);
        disableSelection(target);
        } else if (event.getShiftKey()) {
          onSelection(item, true, !shouldTreeDelegateFocusToElement(target), INTERVAL);
          disableSelection(target);
        } else {
          onSelection(item, true, !shouldTreeDelegateFocusToElement(target), SELECT);
          disableSelection(target);
        }
      }
    }
  }

  private FastMSTreeItem findDeepestOpenChild(FastMSTreeItem item) {
    if (!item.isOpen()) {
      return item;
    }
    return findDeepestOpenChild(item.getChild(item.getChildCount() - 1));
  }

  private FastMSTreeItem findItemByChain(ArrayList<Element> chain, int idx,
      FastMSTreeItem root) {
    if (idx == chain.size()) {
      return root;
    }

    Element hCurElem = chain.get(idx);
    for (int i = 0, n = root.getChildCount(); i < n; ++i) {
      FastMSTreeItem child = root.getChild(i);
      if (child.getElement().equals(hCurElem)) {
        FastMSTreeItem retItem = findItemByChain(chain, idx + 1, root.getChild(i));
        if (retItem == null) {
          return child;
        }
        return retItem;
      }
    }

    return findItemByChain(chain, idx + 1, root);
  }

  private void moveElementOverTarget(Element movable, Element target) {
    int containerTop = getAbsoluteTop();

    int top = DOM.getAbsoluteTop(target) - containerTop;
    int height = DOM.getElementPropertyInt(target, "offsetHeight");

    // Set the element's position and size to exactly underlap the
    // item's content element.

    DOM.setStyleAttribute(movable, "height", height + "px");
    DOM.setStyleAttribute(movable, "top", top + "px");
  }

  /**
   * Move the tree focus to the specified selected item.
   *
   * @param selection
   */
  private void moveFocus(FastMSTreeItem item) {
  moveSelectionBar(item);
     DOM.scrollIntoView(focusable);
     HasFocus focusableWidget = item.getFocusableWidget();
     if (focusableWidget != null) {
       focusableWidget.setFocus(true);
     } else {
       // Ensure Focus is set, as focus may have been previously delegated by
       // tree.
       impl.focus(focusable);
     }
  }

  public void moveUnselectedFocus(FastMSTreeItem item) {
       DOM.scrollIntoView(focusable);
       HasFocus focusableWidget = item.getFocusableWidget();
       if (focusableWidget != null) {
         focusableWidget.setFocus(true);
       } else {
         // Ensure Focus is set, as focus may have been previously delegated by
         // tree.
         impl.focus(focusable);
       }
    }

  /**
   * Moves to the next item, going into children as if dig is enabled.
   */
//  private void moveSelectionDown(FastMSTreeItem sel, boolean dig) {
//    if (sel == root) {
//      return;
//    }
//    FastMSTreeItem parent = sel.getParentItem();
//    if (parent == null) {
//      parent = root;
//    }
//    int idx = parent.getChildIndex(sel);
//
//    if (!dig || !sel.isOpen()) {
//      if (idx < parent.getChildCount() - 1) {
//        onSelection(parent.getChild(idx + 1), true, true);
//      } else {
//        moveSelectionDown(parent, false);
//      }
//    } else if (sel.getChildCount() > 0) {
//      onSelection(sel.getChild(0), true, true);
//    }
//  }

  /**
   * Moves the selected item up one.
   */
//  private void moveSelectionUp(List <FastMSTreeItem> sel) {
//    FastMSTreeItem parent = sel.getParentItem();
//    if (parent == null) {
//      parent = root;
//    }
//    int idx = parent.getChildIndex(sel);
//
//    if (idx > 0) {
//      FastMSTreeItem sibling = parent.getChild(idx - 1);
//      onSelection(findDeepestOpenChild(sibling), true, true);
//    } else {
//      onSelection(parent, true, true);
//    }
//  }

  private native boolean shouldTreeDelegateFocusToElement(Element elem)
  /*-{
    var name = elem.nodeName;
    return ((name == "SELECT") ||
       (name == "INPUT")  ||
       (name == "TEXTAREA") ||
       (name == "OPTION") ||
       (name == "BUTTON") ||
       (name == "LABEL")
    );
  }-*/;
 
  public void addLoadListener(LoadListener listener) {
    if (firingLoadedEvent) {
      toBeAdded.add(listener);
      return;
    }
    loadListeners.add(listener);
 
 
  public void removeLoadListener(LoadListener listener) {
    if (firingLoadedEvent) {
      toBeRemoved.add(listener);
      return;
    }
    loadListeners.remove(listener);
  }
 
  public void loaded(LoadEvent le) {
    try {
      firingLoadedEvent = true;
      for (LoadListener ll: loadListeners) {
        ll.loaderLoad(le);
      }     
      loadListeners.removeAll(toBeRemoved);
      loadListeners.addAll(toBeAdded);
      toBeRemoved.clear();
      toBeAdded.clear();
    } finally {
      firingLoadedEvent = false;
    }
  }
 
  private final void traverse(FastMSTreeItem item, ArrayList <FastMSTreeItem> result) {
    if (item == null) {
      return;
    }
    result.add(item);
    if (item.isOpen()) {
      for (FastMSTreeItem kid: item.getChildren()) {
        traverse(kid, result);
      }
    }
  }
 
  public ArrayList <FastMSTreeItem> getVisibleItems() {
    ArrayList <FastMSTreeItem> result = new ArrayList<FastMSTreeItem>();
    for (FastMSTreeItem kid: root.getChildren()) {
      traverse(kid, result);
    }
    return result;
  }
 
  private final FastMSTreeItem find(FastMSTreeItem item, TreeNode node) {
    if (node.equals(item.getModel())) {
      return item;
    }
    for (FastMSTreeItem kid: item.getChildren()) {
      FastMSTreeItem r = find(kid, node);
      if (r != null) {
        return r;
      }
    }
    return null;
  }
 
//  public FastMSTreeItem find(TreeNode node) {
//    for (FastMSTreeItem kid: root.getChildren()) {
//      FastMSTreeItem r = find(kid, node);
//      if (r != null) {
//        return r;
//      }
//    }
//    return null;
//  }
 
}

/**
* A collection of convenience factories for creating iterators for widgets.
* This mostly helps developers support {@link HasWidgets} without having to
* implement their own {@link Iterator}.
*/
class WidgetIterators {

  /**
   * Wraps an array of widgets to be returned during iteration.
   * <code>null</code> is allowed in the array and will be skipped during
   * iteration.
   *
   * @param container the container of the widgets in <code>contained</code>
   * @param contained the array of widgets
   * @return the iterator
   */
  static final Iterator<Widget> createWidgetIterator(
      final HasWidgets container, final Widget[] contained) {
    return new Iterator<Widget>() {
      int index = -1, last = -1;
      boolean widgetsWasCopied = false;
      Widget[] widgets = contained;

      {
        gotoNextIndex();
      }

      public boolean hasNext() {
        return (index < contained.length);
      }

      public Widget next() {
        if (!hasNext()) {
          throw new NoSuchElementException();
        }
        last = index;
        final Widget w = contained[index];
        gotoNextIndex();
        return w;
      }

      public void remove() {
        if (last < 0) {
          throw new IllegalStateException();
        }

        if (!widgetsWasCopied) {
          widgets = copyWidgetArray(widgets);
          widgetsWasCopied = true;
        }

        container.remove(contained[last]);
        last = -1;
      }

      private void gotoNextIndex() {
        ++index;
        while (index < contained.length) {
          if (contained[index] != null) {
            return;
          }
          ++index;
        }
      }
    };
  }

  private static Widget[] copyWidgetArray(final Widget[] widgets) {
    final Widget[] clone = new Widget[widgets.length];
    for (int i = 0; i < widgets.length; i++) {
      clone[i] = widgets[i];
    }
    return clone;
  }

  private WidgetIterators() {
    // Not instantiable.
  }
}
TOP

Related Classes of com.tensegrity.wpalo.client.ui.mvc.fasttree.FastMSTree

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.