com.google.gwt.gen2.complexpanel.client.FastTreeItem.java Source code

Java tutorial

Introduction

Here is the source code for com.google.gwt.gen2.complexpanel.client.FastTreeItem.java

Source

/*
 * Copyright 2009 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.gen2.complexpanel.client;

import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.ui.Focusable;
import com.google.gwt.user.client.ui.HasHTML;
import com.google.gwt.user.client.ui.UIObject;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.widgetideas.client.overrides.DOMHelper;

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

/**
 * Fast tree item.
 */
public class FastTreeItem extends UIObject implements HasFastTreeItems, HasHTML {

    /**
     * Interface used to allow the widget access to css style names.
     * <p/>
     * The class names indicate the default gwt names for these styles.
     */
    static interface Css extends FastTree.Css {

        String children();

        String closed();

        String content();

        String leaf();

        String leafDefault();

        String open();

        String selected();
    }

    /**
     * Creates the standard css instance used for this widget and groups all the
     * css processing under it.
     */
    static class StandardCss extends FastTree.StandardCss implements Css {

        public static Css DEFAULT_TREEITEM_CSS;

        static Css ensureDefaultCss() {
            if (DEFAULT_TREEITEM_CSS == null) {
                DEFAULT_TREEITEM_CSS = new StandardCss(FastTree.STYLENAME_DEFAULT);
            }
            return DEFAULT_TREEITEM_CSS;
        }

        public StandardCss(String styleName) {
            super(styleName);
        }

        public String children() {
            return pad(STYLENAME_CHILDREN);
        }

        public String closed() {
            return pad(STYLENAME_CLOSED);
        }

        public String content() {
            return pad(STYLENAME_CONTENT);
        }

        public String leaf() {
            return pad(STYLENAME_LEAF);
        }

        public String leafDefault() {
            return pad(STYLENAME_LEAF_DEFAULT);
        }

        public String open() {
            return pad(STYLENAME_OPEN);
        }

        public String selected() {
            return pad(STYLENAME_SELECTED);
        }

        private String pad(String styleName) {
            return " " + styleName;
        }
    }

    enum TreeNodeState {
        TREE_NODE_LEAF, TREE_NODE_INTERIOR_NEVER_OPENED, TREE_NODE_INTERIOR_OPEN, TREE_NODE_INTERIOR_CLOSED
    }

    private static final String STYLENAME_SELECTED = "selected";
    private static final String STYLENAME_CHILDREN = "children";
    private static final String STYLENAME_LEAF = "gwt-FastTreeItem-leaf";
    private static final String STYLENAME_LEAF_DEFAULT = "gwt-FastTreeItem " + STYLENAME_LEAF;
    private static final String STYLENAME_OPEN = "open";

    private static final String STYLENAME_CLOSED = "closed";

    private static final String STYLENAME_CONTENT = "treeItemContent";

    /**
     * The base tree item element that will be cloned.
     */
    private static Element TREE_LEAF;

    /**
     * Static constructor to set up clonable elements.
     */
    static {
        if (GWT.isClient()) {
            // Create the base element that will be cloned
            TREE_LEAF = DOM.createDiv();
            // leaf contents.
            setStyleName(TREE_LEAF, STYLENAME_LEAF_DEFAULT);
            Element content = DOM.createDiv();
            setStyleName(content, STYLENAME_CONTENT);
            DOM.appendChild(TREE_LEAF, content);
        }
    }

    /*
     * Default state.
     */
    private TreeNodeState state = TreeNodeState.TREE_NODE_LEAF;
    private ArrayList<FastTreeItem> children;
    private Element contentElement, childElems;
    private FastTreeItem parent;
    /**
     * The tree that this is associated with currently.
     */
    private FastTree tree;
    private Widget widget;
    private Css css;

    /**
     * Creates an empty tree item.
     */
    public FastTreeItem() {
        css = StandardCss.ensureDefaultCss();
        Element elem = createLeafElement();
        setElement(elem);
    }

    /**
     * Constructs a tree item with the given HTML.
     * 
     * @param html the item's HTML
     * @deprecated use {@link #FastTreeItem()} and {@link #setHTML(String)} instead
     */
    @Deprecated
    public FastTreeItem(String html) {
        this();
        DOM.setInnerHTML(getElementToAttach(), html);
    }

    /**
     * Constructs a tree item with the given <code>Widget</code>.
     * 
     * @param widget the item's widget
     */
    public FastTreeItem(Widget widget) {
        this();
        addWidget(widget);
    }

    public FastTreeItem addHtmlItem(String itemHtml) {
        FastTreeItem ret = new FastTreeItem();
        ret.setHTML(itemHtml);
        addItem(ret);
        return ret;
    }

    public void addItem(FastTreeItem item) {
        // Detach item from existing parent.
        if ((item.getParentItem() != null) || (item.getTree() != null)) {
            item.remove();
        }
        if (isLeafNode()) {
            becomeInteriorNode();
        }
        if (children == null) {
            // Never had children.
            children = new ArrayList<FastTreeItem>();
        }
        // Logical attach.
        item.setParentItem(this);
        children.add(item);

        // Physical attach.
        if (state != TreeNodeState.TREE_NODE_INTERIOR_NEVER_OPENED) {
            DOM.appendChild(childElems, item.getElement());
        }

        // Adopt.
        if (tree != null) {
            item.setTree(tree);
        }
    }

    @Deprecated
    public FastTreeItem addItem(String itemHtml) {
        return addHtmlItem(itemHtml);
    }

    public FastTreeItem addItem(Widget widget) {
        FastTreeItem ret = new FastTreeItem(widget);
        addItem(ret);
        return ret;
    }

    public FastTreeItem addTextItem(String itemText) {
        FastTreeItem ret = new FastTreeItem();
        ret.setText(itemText);
        addItem(ret);
        return ret;
    }

    /**
     * Become an interior node.
     */
    public void becomeInteriorNode() {
        if (!isInteriorNode()) {
            state = TreeNodeState.TREE_NODE_INTERIOR_NEVER_OPENED;

            Element control = DOM.createDiv();
            setStyleName(control, css.closed());
            DOM.appendChild(control, contentElement);
            convertElementToInteriorNode(control);
        }
    }

    public void becomeLeaf() {
        becomeLeaf(true);
    }

    /**
     * @param removeChildNodes
     */
    public void becomeLeaf(boolean removeChildNodes) {
        if (!isLeafNode()) {

            // remove child nodes
            if (removeChildNodes) {
                if (isOpen()) {
                    removeItems();
                } else {
                    if (children != null) {
                        children.clear();
                    }
                    children = null;
                }
            }

            // clear element
            contentElement.getParentElement().removeChild(contentElement);
            getElement().setInnerHTML("");

            // set leaf state
            state = TreeNodeState.TREE_NODE_LEAF;
            DOM.appendChild(getElement(), contentElement);
            setStyleName(getElement(), css.leafDefault());
        }
    }

    public FastTreeItem getChild(int index) {
        if ((index < 0) || (index >= getChildCount())) {
            throw new IndexOutOfBoundsException("No child at index " + index);
        }
        return children.get(index);
    }

    public int getChildCount() {
        if (children == null) {
            return 0;
        }
        return children.size();
    }

    public int getChildIndex(FastTreeItem child) {
        if (children == null) {
            return -1;
        }
        return children.indexOf(child);
    }

    /**
     * Returns the width of the control open/close image. Must be overridden if
     * the TreeItem is using a control image that is <i>not</i> 16 pixels wide.
     * 
     * @return the width of the control image
     */
    public int getControlImageWidth() {
        return 16;
    }

    public String getHTML() {
        return DOM.getInnerHTML(getElementToAttach());
    }

    /**
     * Gets this item's parent.
     * 
     * @return the parent item
     */
    public FastTreeItem getParentItem() {
        return parent;
    }

    public String getText() {
        return DOM.getInnerText(getElementToAttach());
    }

    /**
     * Gets the tree that contains this item.
     * 
     * @return the containing tree
     */
    public FastTree getTree() {
        return tree;
    }

    /**
     * Gets the {@code Widget} associated with this tree item.
     */
    public Widget getWidget() {
        return widget;
    }

    /**
     * Has this {@link FastTreeItem} ever been opened?
     * 
     * @return whether the {@link FastTreeItem} has ever been opened.
     */
    public boolean hasBeenOpened() {
        return state == TreeNodeState.TREE_NODE_INTERIOR_OPEN;
    }

    /**
     * Does this {@link FastTreeItem} represent an interior node?
     */
    public boolean isInteriorNode() {
        return state.compareTo(TreeNodeState.TREE_NODE_INTERIOR_NEVER_OPENED) >= 0;
    }

    /**
     * Is this {@link FastTreeItem} a leaf node?
     */
    public boolean isLeafNode() {
        // return state <= TREE_NODE_LEAF;
        return state.compareTo(TreeNodeState.TREE_NODE_LEAF) < 1;
    }

    /**
     * Is the {@link FastTreeItem} open? Returns false if the {@link FastTreeItem}
     * is closed or a leaf node.
     */
    public boolean isOpen() {
        return state == TreeNodeState.TREE_NODE_INTERIOR_OPEN;
    }

    /**
     * Determines whether this item is currently selected.
     * 
     * @return <code>true</code> if it is selected
     */
    public boolean isSelected() {
        if (tree == null) {
            return false;
        } else {
            return tree.getSelectedItem() == this;
        }
    }

    /**
     * Returns whether the tree is currently showing this {@link FastTreeItem}.
     */
    public boolean isShowing() {
        if (tree == null || isVisible() == false) {
            return false;
        } else if (parent == null) {
            return true;
        } else if (!parent.isOpen()) {
            return false;
        } else {
            return parent.isShowing();
        }
    }

    /**
     * Removes this item from its tree.
     */
    public void remove() {
        if (parent != null) {
            // If this item has a parent, remove self from it.
            parent.removeItem(this);
        } else if (tree != null) {
            // If the item has no parent, but is in the Tree, it must be a top-level
            // element.
            tree.removeItem(this);
        }
    }

    /**
     * Removes an item from the tree. Note, if you want to switch the node back to
     * be a leaf node, must call becomeLeaf()
     */
    public void removeItem(FastTreeItem item) {
        // Validate.
        if (children == null || !children.contains(item)) {
            return;
        }

        // Orphan.
        item.clearTree();

        // Physical detach.
        if (state != TreeNodeState.TREE_NODE_INTERIOR_NEVER_OPENED) {
            DOM.removeChild(childElems, item.getElement());
        }

        // Logical detach.
        item.setParentItem(null);
        children.remove(item);
    }

    /**
     * Removes all of this item's children.
     */
    public void removeItems() {
        while (getChildCount() > 0) {
            removeItem(getChild(0));
        }
    }

    public void setHTML(String html) {
        clearWidget();
        DOM.setInnerHTML(getElementToAttach(), html);
    }

    /**
     * Sets whether this item's children are displayed.
     * 
     * @param open whether the item is open
     */
    public final void setState(boolean open) {
        setState(open, true);
    }

    /**
     * Sets whether this item's children are displayed.
     * 
     * @param open whether the item is open
     * @param fireEvents <code>true</code> to allow open/close events to be fired
     */
    public void setState(boolean open, boolean fireEvents) {
        if (open == isOpen()) {
            return;
        }
        // Cannot open leaf nodes.
        if (isLeafNode()) {
            return;
        }
        if (open) {
            boolean isFirstTime = state == TreeNodeState.TREE_NODE_INTERIOR_NEVER_OPENED;
            if (beforeOpen(isFirstTime)) {
                childElems = DOM.createDiv();
                UIObject.setStyleName(childElems, css.children());
                convertElementToHaveChildren(childElems);

                if (children != null) {
                    for (FastTreeItem item : children) {
                        DOM.appendChild(childElems, item.getElement());
                    }
                }
            }

            state = TreeNodeState.TREE_NODE_INTERIOR_OPEN;
        } else {
            beforeClose();
            state = TreeNodeState.TREE_NODE_INTERIOR_CLOSED;
        }
        updateState();
        if (open) {
            afterOpen();
        } else {
            afterClose();
        }
    }

    public void setText(String text) {
        clearWidget();
        DOM.setInnerText(getElementToAttach(), text);
    }

    public void setTree(FastTree newTree) {
        if (this.tree == newTree) {
            return;
        }

        // Early out.
        if (this.tree != null) {
            throw new IllegalStateException(
                    "Each Tree Item must be removed from its current tree before being added to another.");
        }
        this.tree = newTree;

        if (widget != null) {
            // Add my widget to the new tree.
            newTree.adopt(widget, this);
        }

        for (int i = 0, n = getChildCount(); i < n; ++i) {
            children.get(i).setTree(newTree);
        }
    }

    public void setWidget(Widget widget) {

        // Physical detach old from self.
        // Clear out any existing content before adding a widget.
        clearWidget();
        DOM.setInnerHTML(getElementToAttach(), "");
        addWidget(widget);
    }

    /**
     * Returns the widget, if any, that should be focused on if this TreeItem is
     * selected.
     * 
     * @return widget to be focused.
     */
    protected Focusable getFocusable() {
        Widget w = getWidget();
        if (w instanceof Focusable) {
            return (Focusable) w;
        } else {
            return null;
        }
    }

    /**
     * Called after the tree item is closed.
     */
    void afterClose() {
        tree.afterClose(this);
    }

    /**
     * Called after the tree item is opened.
     */
    void afterOpen() {
        tree.afterOpen(this);
    }

    /**
     * Called before the tree item is closed.
     */
    void beforeClose() {
        tree.beforeClose(this);
    }

    /**
     * Called before the tree item is opened.
     */
    boolean beforeOpen(boolean isFirstTime) {
        tree.beforeOpen(this, isFirstTime);
        return isFirstTime;
    }

    void clearTree() {
        if (tree != null) {
            if (widget != null) {
                tree.treeOrphan(widget);
            }
            if (tree.getSelectedItem() == this) {
                tree.setSelectedItem(null);
            }
            tree = null;
            for (int i = 0, n = getChildCount(); i < n; ++i) {
                children.get(i).clearTree();
            }
        }
    }

    void convertElementToHaveChildren(Element children) {
        DOM.appendChild(getElement(), children);
    }

    void convertElementToInteriorNode(Element control) {

        // remove dependent style name
        setStyleName(getElement(), getStylePrimaryName() + "-leaf", false);
        DOM.appendChild(getElement(), control);
    }

    Element createLeafElement() {
        Element elem = DOMHelper.clone(TREE_LEAF, true);
        contentElement = DOMHelper.rawFirstChild(elem);
        return elem;
    }

    void dumpTreeItems(List<FastTreeItem> accum) {
        if (isInteriorNode() && getChildCount() > 0) {
            for (int i = 0; i < children.size(); i++) {
                FastTreeItem item = children.get(i);
                accum.add(item);
                item.dumpTreeItems(accum);
            }
        }
    }

    ArrayList<FastTreeItem> getChildren() {
        return children;
    }

    Element getContentElement() {
        return contentElement;
    }

    Element getControlElement() {
        return DOM.getParent(contentElement);
    }

    Element getElementToAttach() {
        return contentElement;
    }

    /**
     * Called when a tree item is selected.
     */
    void onSelected() {
        tree.onSelected(this);
    }

    void setParentItem(FastTreeItem parent) {
        this.parent = parent;
    }

    /**
     * Selects or deselects this item.
     * 
     * @param selected {@code true} to select the item, {@code false} to deselect
     *          it.
     */
    void setSelection(boolean selected, boolean fireEvents) {
        setStyleName(getControlElement(), css.selected(), selected);
        if (selected && fireEvents) {
            onSelected();
        }
    }

    /**
     * Sets the state of the item.
     */
    void updateState() {
        // No work to be done.
        if (isLeafNode()) {
            return;
        }
        if (isOpen()) {
            showOpenImage();
            UIObject.setVisible(childElems, true);
        } else {
            showClosedImage();
            UIObject.setVisible(childElems, false);
        }
    }

    /**
     * Adds a widget to an already empty {@link FastTreeItem}.
     */
    private void addWidget(Widget newWidget) {
        // Detach new child from old parent.
        if (newWidget != null) {
            newWidget.removeFromParent();
        }

        // Logical detach old/attach new.
        widget = newWidget;

        if (newWidget != null) {
            DOM.appendChild(getElementToAttach(), widget.getElement());
            // bidiSupport();
            // Attach child to tree.
            if (tree != null) {
                tree.adopt(widget, this);
            }
        }
    }

    /**
     * Detach the current widget from the tree.
     */
    private void clearWidget() {
        if (widget != null) {
            if (this.tree != null) {
                this.tree.treeOrphan(widget);
            }
            getElementToAttach().removeChild(widget.getElement());
            widget = null;
        }
    }

    private void showClosedImage() {
        setStyleName(getControlElement(), css.open(), false);
        setStyleName(getControlElement(), css.closed(), true);
    }

    private void showOpenImage() {
        setStyleName(getControlElement(), css.closed(), false);
        setStyleName(getControlElement(), css.open(), true);
    }
}