com.google.gwt.widgetideas.client.FastTreeItem.java Source code

Java tutorial

Introduction

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

Source

/*
 * Copyright 2008 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.widgetideas.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.HasFocus;
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;

/**
 * An item that can be contained within a
 * {@link com.google.gwt.widgetideas.client.FastTree}.
 * <p>
 * <h3>Example</h3>
 * {@example com.google.gwt.examples.TreeExample}
 */

public class FastTreeItem extends UIObject implements HasHTML, HasFastTreeItems {
    private static final String STYLENAME_SELECTED = "selected";

    // TODO(ECC) change states to enums and move style names to FastTree where
    // they below.
    private static final int TREE_NODE_LEAF = 1;
    private static final int TREE_NODE_INTERIOR_NEVER_OPENED = 2;
    private static final int TREE_NODE_INTERIOR_OPEN = 3;
    private static final int TREE_NODE_INTERIOR_CLOSED = 4;
    private static final String STYLENAME_CHILDREN = "children";
    private static final String STYLENAME_LEAF_DEFAULT = "gwt-FastTreeItem gwt-FastTreeItem-leaf";
    private static final String STYLENAME_OPEN = "open";
    private static final String STYLENAME_CLOSED = "closed";
    private static final String STYLENAME_LEAF = "leaf";

    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);
        }
    }

    private int state = TREE_NODE_LEAF;
    private ArrayList<FastTreeItem> children;
    private Element contentElem, childElems;
    private FastTreeItem parent;
    private FastTree tree;
    private Widget widget;

    /**
     * Creates an empty tree item.
     */
    public FastTreeItem() {
        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)} instaed
     */
    @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);
    }

    /**
     * This constructor is only for use by {@link DecoratedFastTreeItem}.
     * 
     * @param element element
     */
    FastTreeItem(Element element) {
        setElement(element);
    }

    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 != 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 = TREE_NODE_INTERIOR_NEVER_OPENED;

            Element control = DOM.createDiv();
            setStyleName(control, STYLENAME_CLOSED);
            DOM.appendChild(control, contentElem);
            convertElementToInteriorNode(control);
        }
    }

    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 final FastTree getTree() {
        return tree;
    }

    /**
     * Gets the <code>Widget</code> 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 == TREE_NODE_INTERIOR_OPEN;
    }

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

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

    /**
     * Is the {@link FastTreeItem} open? Returns false if the {@link FastTreeItem}
     * is closed or a leaf node.
     */
    public boolean isOpen() {
        return state == 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, tree items do not automatically become
     * a leaf node again if the last child is removed. (
     */
    public void removeItem(FastTreeItem item) {
        // Validate.
        if (children == null || !children.contains(item)) {
            return;
        }

        // Orphan.
        item.clearTree();

        // Physical detach.
        if (state != 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) {
            beforeOpen();
            if (state == TREE_NODE_INTERIOR_NEVER_OPENED) {
                ensureChildren();
                childElems = DOM.createDiv();
                UIObject.setStyleName(childElems, STYLENAME_CHILDREN);
                convertElementToHaveChildren(childElems);

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

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

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

    public void setWidget(Widget widget) {
        // Physical detach old from self.
        // Clear out any existing content before adding a widget.

        DOM.setInnerHTML(getElementToAttach(), "");
        clearWidget();
        addWidget(widget);
    }

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

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

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

    /**
     * Called before the tree item is opened.
     */
    protected void beforeOpen() {
    }

    /**
     * Called when tree item is being unselected. Returning <code>false</code>
     * cancels the unselection.
     * 
     */
    protected boolean beforeSelectionLost() {
        return true;
    }

    /**
     * Fired when a tree item receives a request to open for the first time.
     * Should be overridden in child clases.
     */
    protected void ensureChildren() {
    }

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

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

    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) {
        setStyleName(getElement(), getStylePrimaryName() + "-leaf", false);
        DOM.appendChild(getElement(), control);
    }

    Element createLeafElement() {
        Element elem = DOMHelper.clone(TREE_LEAF, true);
        contentElem = 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 getContentElem() {
        return contentElem;
    }

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

    Element getElementToAttach() {
        return contentElem;
    }

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

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

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

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

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

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

    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);
            }
        }
    }

    private void bidiSupport() {
    }

    private void clearWidget() {
        // Detach old child from tree.
        if (widget != null && tree != null) {
            tree.treeOrphan(widget);
            widget = null;
        }
    }

    private void showClosedImage() {
        setStyleName(getControlElement(), STYLENAME_OPEN, false);
        setStyleName(getControlElement(), STYLENAME_CLOSED, true);
    }

    private void showOpenImage() {
        setStyleName(getControlElement(), STYLENAME_CLOSED, false);
        setStyleName(getControlElement(), STYLENAME_OPEN, true);
    }

}