Package com.google.gwt.user.cellview.client

Source Code of com.google.gwt.user.cellview.client.CellBrowser$TreeNode

/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.gwt.user.cellview.client;

import com.google.gwt.animation.client.Animation;
import com.google.gwt.cell.client.AbstractCell;
import com.google.gwt.cell.client.Cell;
import com.google.gwt.cell.client.ValueUpdater;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.dom.client.Style.Overflow;
import com.google.gwt.dom.client.Style.Position;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.dom.client.Style.Visibility;
import com.google.gwt.resources.client.ClientBundle;
import com.google.gwt.resources.client.CssResource;
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.resources.client.ImageResource.ImageOptions;
import com.google.gwt.resources.client.ImageResource.RepeatStyle;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.HasAnimation;
import com.google.gwt.user.client.ui.ProvidesResize;
import com.google.gwt.user.client.ui.RequiresResize;
import com.google.gwt.user.client.ui.ScrollPanel;
import com.google.gwt.user.client.ui.SplitLayoutPanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.view.client.ListView;
import com.google.gwt.view.client.PagingListView;
import com.google.gwt.view.client.ProvidesKey;
import com.google.gwt.view.client.Range;
import com.google.gwt.view.client.SelectionModel;
import com.google.gwt.view.client.TreeViewModel;
import com.google.gwt.view.client.PagingListView.Pager;
import com.google.gwt.view.client.TreeViewModel.NodeInfo;

import java.util.ArrayList;
import java.util.List;

/**
* A "browsable" view of a tree in which only a single node per level may be
* open at one time.
*/
public class CellBrowser extends Composite implements ProvidesResize,
    RequiresResize, HasAnimation {

  /**
   * A ClientBundle that provides images for this widget.
   */
  public static interface Resources extends ClientBundle {

    /**
     * An image indicating a closed branch.
     */
    ImageResource cellBrowserClosed();

    /**
     * An image indicating an open branch.
     */
    ImageResource cellBrowserOpen();

    /**
     * The background used for open items.
     */
    @ImageOptions(repeatStyle = RepeatStyle.Horizontal)
    ImageResource cellBrowserOpenBackground();

    /**
     * The background used for selected items.
     */
    @Source("cellTreeSelectedBackground.png")
    @ImageOptions(repeatStyle = RepeatStyle.Horizontal)
    ImageResource cellBrowserSelectedBackground();

    /**
     * The styles used in this widget.
     */
    @Source("CellBrowser.css")
    Style cellBrowserStyle();
  }

  /**
   * Styles used by this widget.
   */
  public static interface Style extends CssResource {

    /**
     * Applied to all columns.
     */
    String column();

    /**
     * Applied to the first column.
     */
    String firstColumn();

    /**
     * Applied to all list items.
     */
    String item();

    /***
     * Applied to open items.
     */
    String openItem();

    /***
     * Applied to selected items.
     */
    String selectedItem();
  }

  /**
   * We override the Resources in {@link CellList} so that the styles in
   * {@link CellList} don't conflict with the styles in {@link CellBrowser}.
   */
  static interface CellListResources extends CellList.Resources {
    @Source("CellBrowserOverride.css")
    CellList.Style cellListStyle();
  }

  /**
   * A wrapper around a cell that adds an open button.
   *
   * @param <C> the data type of the cell
   */
  private class CellDecorator<C> extends AbstractCell<C> {

    /**
     * The cell used to render the inner contents.
     */
    private final Cell<C> cell;

    /**
     * The level of this list view.
     */
    private final int level;

    /**
     * The key of the currently open item.
     */
    private Object openKey;

    /**
     * The key provider for the node.
     */
    private final ProvidesKey<C> providesKey;

    /**
     * The selection model for the node.
     */
    private final SelectionModel<? super C> selectionModel;

    /**
     * Construct a new {@link CellDecorator}.
     *
     * @param nodeInfo the {@link NodeInfo} associated with the cell
     * @param level the level of items rendered by this decorator
     */
    public CellDecorator(NodeInfo<C> nodeInfo, int level) {
      this.cell = nodeInfo.getCell();
      this.level = level;
      this.providesKey = nodeInfo.getProvidesKey();
      this.selectionModel = nodeInfo.getSelectionModel();
    }

    @Override
    public boolean consumesEvents() {
      return cell.consumesEvents();
    }

    @Override
    public boolean dependsOnSelection() {
      return cell.dependsOnSelection();
    }

    @Override
    public Object onBrowserEvent(Element parent, C value, Object viewData,
        NativeEvent event, ValueUpdater<C> valueUpdater) {

      // Fire the event to the inner cell.
      viewData = cell.onBrowserEvent(getCellParent(parent), value, viewData,
          event, valueUpdater);

      // Open child nodes.
      if (Event.getTypeInt(event.getType()) == Event.ONMOUSEDOWN) {
        trimToLevel(level);

        // Remove style from currently open item.
        Element curOpenItem = Document.get().getElementById(getOpenId());
        if (curOpenItem != null) {
          setElementOpenState(curOpenItem.getParentElement(), false);
        }
        openKey = null;

        // Save the key of the new open item and update the Element.
        if (!viewModel.isLeaf(value)) {
          NodeInfo<?> nodeInfo = viewModel.getNodeInfo(value);
          if (nodeInfo != null) {
            openKey = providesKey.getKey(value);
            setElementOpenState(parent, true);
            appendTreeNode(nodeInfo);
          }
        }
      }

      return viewData;
    }

    @Override
    public void render(C value, Object viewData, StringBuilder sb) {
      boolean isOpen = (openKey == null) ? false
          : openKey.equals(providesKey.getKey(value));
      boolean isSelected = (selectionModel == null) ? false
          : selectionModel.isSelected(value);
      sb.append("<div style='position:relative;padding-right:");
      sb.append(imageWidth);
      sb.append("px;'");
      sb.append(" class='").append(style.item());
      if (isOpen) {
        sb.append(" ").append(style.openItem());
      }
      if (isSelected) {
        sb.append(" ").append(style.selectedItem());
      }
      sb.append("'");
      if (isOpen) {
        sb.append(" id='").append(getOpenId()).append("'");
      }
      sb.append(">");
      if (isOpen) {
        sb.append(openImageHtml);
      } else if (viewModel.isLeaf(value)) {
        sb.append(LEAF_IMAGE);
      } else {
        sb.append(closedImageHtml);
      }
      sb.append("<div>");
      cell.render(value, viewData, sb);
      sb.append("</div></div>");
    }

    @Override
    public void setValue(Element parent, C value, Object viewData) {
      cell.setValue(getCellParent(parent), value, viewData);
    }

    /**
     * Get the parent element of the decorated cell.
     *
     * @param parent the parent of this cell
     * @return the decorated cell's parent
     */
    private Element getCellParent(Element parent) {
      return parent.getFirstChildElement().getChild(1).cast();
    }

    /**
     * Get the image element of the decorated cell.
     *
     * @param parent the parent of this cell
     * @return the image element
     */
    private Element getImageElement(Element parent) {
      return parent.getFirstChildElement().getFirstChildElement();
    }

    /**
     * Get the ID of the open element.
     *
     * @return the ID
     */
    private String getOpenId() {
      return uniqueId + "-" + level;
    }

    /**
     * Replace the image element of a cell and update the styles associated with
     * the open state.
     *
     * @param parent the parent element of the cell
     * @param open true if open, false if closed
     */
    private void setElementOpenState(Element parent, boolean open) {
      // Update the style name and ID.
      Element wrapper = parent.getFirstChildElement();
      if (open) {
        wrapper.addClassName(style.openItem());
        wrapper.setId(getOpenId());
      } else {
        wrapper.removeClassName(style.openItem());
        wrapper.setId("");
      }

      // Replace the image element.
      String html = open ? openImageHtml : closedImageHtml;
      Element tmp = Document.get().createDivElement();
      tmp.setInnerHTML(html);
      Element imageElem = tmp.getFirstChildElement();
      Element oldImg = getImageElement(parent);
      wrapper.replaceChild(imageElem, oldImg);
    }
  }

  /**
   * The animation used to scroll to the newly added list view.
   */
  private class ScrollAnimation extends Animation {

    /**
     * The starting scroll position.
     */
    private int startScrollLeft;

    /**
     * The ending scroll position.
     */
    private int targetScrollLeft;

    @Override
    protected void onComplete() {
      getElement().setScrollLeft(targetScrollLeft);
    }

    @Override
    protected void onUpdate(double progress) {
      int diff = targetScrollLeft - startScrollLeft;
      getElement().setScrollLeft(startScrollLeft + (int) (diff * progress));
    }

    void scrollToEnd() {
      Element elem = getElement();
      targetScrollLeft = elem.getScrollWidth() - elem.getClientWidth();

      if (isAnimationEnabled()) {
        // Animate the scrolling.
        startScrollLeft = elem.getScrollLeft();
        run(250);
      } else {
        // Scroll instantly.
        onComplete();
      }
    }
  }

  /**
   * A node in the tree.
   *
   * @param <C> the data type of the children of the node
   */
  private class TreeNode<C> {
    private CellDecorator<C> cell;
    private ListView<C> listView;
    private NodeInfo<C> nodeInfo;
    private Widget widget;

    /**
     * Construct a new {@link TreeNode}.
     *
     * @param nodeInfo the nodeInfo for the children nodes
     * @param listView the list view assocated with the node
     * @param widget the widget that represents the list view
     */
    public TreeNode(NodeInfo<C> nodeInfo, ListView<C> listView,
        CellDecorator<C> cell, Widget widget) {
      this.cell = cell;
      this.listView = listView;
      this.nodeInfo = nodeInfo;
      this.widget = widget;
    }

    /**
     * Get the {@link CellDecorator} used to render the node.
     *
     * @return the cell decorator
     */
    public CellDecorator<C> getCell() {
      return cell;
    }

    /**
     * Unregister the list view and remove it from the widget.
     */
    void cleanup() {
      listView.setSelectionModel(null);
      nodeInfo.unsetView();
      getSplitLayoutPanel().remove(widget);
    }
  }

  /**
   * The element used in place of an image when a node has no children.
   */
  private static final String LEAF_IMAGE = "<div style='position:absolute;display:none;'></div>";

  private static Resources DEFAULT_RESOURCES;

  /**
   * The override styles used in {@link CellList}.
   */
  private static CellListResources cellListResource;

  /**
   * Get the {@link CellList.Resources} overrides.
   */
  private static CellListResources getCellListResources() {
    if (cellListResource == null) {
      cellListResource = GWT.create(CellListResources.class);
    }
    return cellListResource;
  }

  private static Resources getDefaultResources() {
    if (DEFAULT_RESOURCES == null) {
      DEFAULT_RESOURCES = GWT.create(Resources.class);
    }
    return DEFAULT_RESOURCES;
  }

  /**
   * The animation used for scrolling.
   */
  private final ScrollAnimation animation = new ScrollAnimation();

  /**
   * The default width of new columns.
   */
  private int defaultWidth = 200;

  /**
   * The HTML used to generate the closed image.
   */
  private final String closedImageHtml;

  /**
   * The unique ID assigned to this tree widget.
   */
  private final String uniqueId = Document.get().createUniqueId();

  /**
   * A boolean indicating whether or not animations are enabled.
   */
  private boolean isAnimationEnabled;

  /**
   * The minimum width of new columns.
   */
  private int minWidth;

  /**
   * The maximum width of the open and closed images.
   */
  private final int imageWidth;

  /**
   * The HTML used to generate the open image.
   */
  private final String openImageHtml;

  /**
   * The styles used by this widget.
   */
  private final Style style;

  /**
   * The element used to maintain the scrollbar when columns are removed.
   */
  private Element scrollLock;

  /**
   * The visible {@link TreeNode}.
   */
  private final List<TreeNode<?>> treeNodes = new ArrayList<TreeNode<?>>();

  /**
   * The {@link TreeViewModel} that backs the tree.
   */
  private final TreeViewModel viewModel;

  /**
   * Construct a new {@link CellBrowser}.
   *
   * @param <T> the type of data in the root node
   * @param viewModel the {@link TreeViewModel} that backs the tree
   * @param rootValue the hidden root value of the tree
   */
  public <T> CellBrowser(TreeViewModel viewModel, T rootValue) {
    this(viewModel, rootValue, getDefaultResources());
  }

  /**
   * Construct a new {@link CellBrowser} with the specified {@link Resources}.
   *
   * @param <T> the type of data in the root node
   * @param viewModel the {@link TreeViewModel} that backs the tree
   * @param rootValue the hidden root value of the tree
   * @param resources the {@link Resources} used for images
   */
  public <T> CellBrowser(TreeViewModel viewModel, T rootValue,
      Resources resources) {
    this.viewModel = viewModel;
    this.style = resources.cellBrowserStyle();
    this.style.ensureInjected();
    initWidget(new SplitLayoutPanel());
    getElement().getStyle().setOverflow(Overflow.AUTO);
    setStyleName("gwt-SideBySideTreeView");

    // Initialize the open and close images strings.
    ImageResource treeOpen = resources.cellBrowserOpen();
    ImageResource treeClosed = resources.cellBrowserClosed();
    openImageHtml = getImageHtml(treeOpen);
    closedImageHtml = getImageHtml(treeClosed);
    imageWidth = Math.max(treeOpen.getWidth(), treeClosed.getWidth());
    minWidth = imageWidth + 20;

    // Add a placeholder to maintain the scroll width.
    scrollLock = Document.get().createDivElement();
    scrollLock.getStyle().setPosition(Position.ABSOLUTE);
    scrollLock.getStyle().setVisibility(Visibility.HIDDEN);
    scrollLock.getStyle().setZIndex(-32767);
    scrollLock.getStyle().setBackgroundColor("red");
    scrollLock.getStyle().setTop(0, Unit.PX);
    scrollLock.getStyle().setLeft(0, Unit.PX);
    scrollLock.getStyle().setHeight(1, Unit.PX);
    scrollLock.getStyle().setWidth(1, Unit.PX);
    getElement().appendChild(scrollLock);

    // Associate the first ListView with the rootValue.
    appendTreeNode(viewModel.getNodeInfo(rootValue));

    // Catch scroll events.
    sinkEvents(Event.ONSCROLL);
  }

  /**
   * Get the default width of new columns.
   *
   * @return the default width in pixels
   */
  public int getDefaultColumnWidth() {
    return defaultWidth;
  }

  /**
   * Get the minimum width of columns.
   *
   * @return the minimum width in pixels
   */
  public int getMinimumColumnWidth() {
    return minWidth;
  }

  public boolean isAnimationEnabled() {
    return isAnimationEnabled;
  }

  @Override
  public void onBrowserEvent(Event event) {
    switch (DOM.eventGetType(event)) {
      case Event.ONSCROLL:
        // Shorten the scroll bar is possible.
        adjustScrollLock();
        break;
    }
    super.onBrowserEvent(event);
  }

  public void onResize() {
    getSplitLayoutPanel().onResize();
  }

  public void setAnimationEnabled(boolean enable) {
    this.isAnimationEnabled = enable;
  }

  /**
   * Set the default width of new columns.
   *
   * @param width the default width in pixels
   */
  public void setDefaultColumnWidth(int width) {
    this.defaultWidth = width;
  }

  /**
   * Set the minimum width of columns.
   *
   * @param minWidth the minimum width in pixels
   */
  public void setMinimumColumnWidth(int minWidth) {
    this.minWidth = minWidth;
  }

  /**
   * Create a Pager to control the list view. The {@link ListView} must extend
   * {@link Widget}.
   *
   * @param <C> the item type in the list view
   * @param listView the list view to add paging too
   * @return the {@link Pager}
   */
  protected <C> Pager<C> createPager(PagingListView<C> listView) {
    return new PageSizePager<C>(listView, listView.getPageSize());
  }

  /**
   * Create a {@link PagingListView} that will display items. The
   * {@link PagingListView} must extend {@link Widget}.
   *
   * @param <C> the item type in the list view
   * @param nodeInfo the node info with child data
   * @param cell the cell to use in the list view
   * @return the {@link ListView}
   */
  protected <C> PagingListView<C> createPagingListView(NodeInfo<C> nodeInfo,
      Cell<C> cell) {
    CellList<C> pagingListView = new CellList<C>(cell, getCellListResources());
    pagingListView.setValueUpdater(nodeInfo.getValueUpdater());
    return pagingListView;
  }

  /**
   * Adjust the size of the scroll lock element based on the new position of the
   * scroll bar.
   */
  private void adjustScrollLock() {
    int scrollLeft = getElement().getScrollLeft();
    if (scrollLeft > 0) {
      int clientWidth = getElement().getClientWidth();
      scrollLock.getStyle().setWidth(scrollLeft + clientWidth, Unit.PX);
    } else {
      scrollLock.getStyle().setWidth(1.0, Unit.PX);
    }
  }

  /**
   * Create a new {@link TreeNode} and append it to the end of the LayoutPanel.
   *
   * @param <C> the data type of the children
   * @param nodeInfo the info about the node
   */
  private <C> void appendTreeNode(final NodeInfo<C> nodeInfo) {
    // Create the list view.
    final int level = treeNodes.size();
    final CellDecorator<C> cell = new CellDecorator<C>(nodeInfo, level);
    final PagingListView<C> listView = createPagingListView(nodeInfo, cell);
    assert (listView instanceof Widget) : "createPagingListView() must return a widget";

    // Create a pager and wrap the components in a scrollable container.
    ScrollPanel scrollable = new ScrollPanel();
    final Pager<C> pager = createPager(listView);
    if (pager != null) {
      assert (pager instanceof Widget) : "createPager() must return a widget";
      FlowPanel flowPanel = new FlowPanel();
      flowPanel.add((Widget) listView);
      flowPanel.add((Widget) pager);
      scrollable.setWidget(flowPanel);
    } else {
      scrollable.setWidget((Widget) listView);
    }
    scrollable.setStyleName(style.column());
    if (level == 0) {
      scrollable.addStyleName(style.firstColumn());
    }

    // Create a delegate list view so we can trap data changes.
    ListView<C> listViewDelegate = new ListView<C>() {
      public Range getRange() {
        return listView.getRange();
      }

      public boolean isDataSizeExact() {
        return listView.isDataSizeExact();
      }

      public void setData(int start, int length, List<C> values) {
        // Trim to the current level if the open node no longer exists.
        TreeNode<?> node = treeNodes.get(level);
        Object openKey = node.getCell().openKey;
        if (openKey != null) {
          boolean stillExists = false;
          ProvidesKey<C> keyProvider = nodeInfo.getProvidesKey();
          for (C value : values) {
            if (openKey.equals(keyProvider.getKey(value))) {
              stillExists = true;
              break;
            }
          }
          if (!stillExists) {
            trimToLevel(level);
          }
        }

        // Refresh the list.
        listView.setData(start, length, values);
      }

      public void setDataSize(int size, boolean isExact) {
        listView.setDataSize(size, isExact);
      }

      public void setDelegate(Delegate<C> delegate) {
        listView.setDelegate(delegate);
      }

      public void setSelectionModel(SelectionModel<? super C> selectionModel) {
        listView.setSelectionModel(selectionModel);
      }
    };

    // Create a TreeNode.
    TreeNode<C> treeNode = new TreeNode<C>(nodeInfo, listViewDelegate, cell,
        scrollable);
    treeNodes.add(treeNode);

    // Attach the view to the selection model and node info.
    listView.setSelectionModel(nodeInfo.getSelectionModel());
    nodeInfo.setView(listViewDelegate);

    // Add the ListView to the LayoutPanel.
    SplitLayoutPanel splitPanel = getSplitLayoutPanel();
    splitPanel.insertWest(scrollable, defaultWidth, null);
    splitPanel.setWidgetMinSize(scrollable, minWidth);
    splitPanel.forceLayout();

    // Scroll to the right.
    animation.scrollToEnd();
  }

  /**
   * Get the HTML representation of an image.
   *
   * @param res the {@link ImageResource} to render as HTML
   * @return the rendered HTML
   */
  private String getImageHtml(ImageResource res) {
    // Add the position and dimensions.
    StringBuilder sb = new StringBuilder();
    sb.append("<div style=\"position:absolute;right:0px;top:0px;height:100%;");
    sb.append("width:").append(res.getWidth()).append("px;");

    // Add the background, vertically centered.
    sb.append("background:url('").append(res.getURL()).append("') ");
    sb.append("no-repeat scroll center center transparent;");

    // Close the div and return.
    sb.append("\"></div>");
    return sb.toString();
  }

  /**
   * Get the {@link SplitLayoutPanel} used to lay out the views.
   *
   * @return the {@link SplitLayoutPanel}
   */
  private SplitLayoutPanel getSplitLayoutPanel() {
    return (SplitLayoutPanel) getWidget();
  }

  /**
   * Reduce the number of {@link ListView}s down to the specified level.
   *
   * @param level the level to trim to
   */
  private void trimToLevel(int level) {
    // Add a placeholder to maintain the same scroll width.
    adjustScrollLock();

    // Remove the listViews that are no longer needed.
    int curLevel = treeNodes.size() - 1;
    while (curLevel > level) {
      TreeNode<?> removed = treeNodes.remove(curLevel);
      removed.cleanup();
      curLevel--;
    }
  }
}
TOP

Related Classes of com.google.gwt.user.cellview.client.CellBrowser$TreeNode

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.