All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.codename1.ui.tree.Tree Maven / Gradle / Ivy

There is a newer version: 7.0.161
Show newest version
/*
 * Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores
 * CA 94065 USA or visit www.oracle.com if you need additional information or
 * have any questions.
 */
package com.codename1.ui.tree;

import com.codename1.components.SpanButton;
import com.codename1.ui.Button;
import com.codename1.ui.Component;
import com.codename1.ui.ComponentSelector;
import com.codename1.ui.Container;
import com.codename1.ui.Display;
import com.codename1.ui.FontImage;
import com.codename1.ui.Image;
import com.codename1.ui.animations.CommonTransitions;
import com.codename1.ui.animations.Transition;
import com.codename1.ui.events.ActionEvent;
import com.codename1.ui.events.ActionListener;
import com.codename1.ui.geom.Dimension;
import com.codename1.ui.layouts.BorderLayout;
import com.codename1.ui.layouts.BoxLayout;
import com.codename1.ui.plaf.Style;
import com.codename1.ui.plaf.UIManager;
import com.codename1.ui.util.EventDispatcher;
import java.util.HashSet;
import java.util.Set;
import java.util.Vector;

/**
 * 

The {@code Tree} component allows constructing simple tree component hierarchies that can be expanded * seamlessly with no limit. The tree is bound to a model that can provide data with free form depth such as file system * or similarly structured data.
* To customize the look of the tree the component can be derived and component creation can be replaced.

* * * Tree sample code * *

* And heres a more "real world" example showing an XML hierarchy in a {@code Tree}: *

* * Tree with XML data * *

* Another real world example showing the {@link com.codename1.io.FileSystemStorage} as a tree: *

* * Simple sample of a tree for the FileSystemStorage API * * @author Shai Almog */ public class Tree extends Container { private static final String KEY_OBJECT = "TREE_OBJECT"; private static final String KEY_PARENT = "TREE_PARENT"; private static final String KEY_EXPANDED = "TREE_NODE_EXPANDED"; private static final String KEY_DEPTH = "TREE_DEPTH"; private EventDispatcher leafListener = new EventDispatcher(); private ActionListener expansionListener = new Handler(); private TreeModel model; private static Image folder; private static Image openFolder; private static Image nodeImage; private int depthIndent = 2; private boolean multilineMode; /** * A marker interface used for Tree state returned from {@link #getTreeState() } and * passed to {@link #setTreeState(com.codename1.ui.tree.Tree.TreeState) } for retaining * state in a Tree when the model is changed. */ public static interface TreeState { } private static class State implements TreeState { Set expandedSet = new HashSet(); private void extractStateFrom(final Tree tree) { expandedSet.clear(); ComponentSelector.select("*", tree).each(new ComponentSelector.ComponentClosure() { public void call(Component c) { if (tree.isExpanded(c)) { Object o = c.getClientProperty(KEY_OBJECT); expandedSet.add(o); } } }); } private void applyStateTo(Tree tree) { applyStateTo(tree, tree); } private void applyStateTo(final Tree tree, Container parent) { ComponentSelector.select("*", parent).each(new ComponentSelector.ComponentClosure() { public void call(Component cmp) { Object o = cmp.getClientProperty(KEY_OBJECT); if (o != null) { if(expandedSet.contains(o)) { if (!tree.isExpanded(cmp)) { Container dest = tree.expandNode(false, cmp, false); applyStateTo(tree, dest); } } else { if (tree.isExpanded(cmp)) { tree.collapseNode(cmp, null); } } } } }); } } /** * Constructor for usage by GUI builder and automated tools, normally one * should use the version that accepts the model */ public Tree() { this(new StringArrayTreeModel(new String[][] { {"Colors", "Letters", "Numbers"}, {"Red", "Green", "Blue"}, {"A", "B", "C"}, {"1", "2", "3"} })); } /** * Gets the state of the tree in a format that can be restored later * by either the same tree or a different tree whose model includes the same * nodes. * @return A TreeState object that can be passed to {@link #setTreeState(com.codename1.ui.tree.Tree.TreeState) } */ public TreeState getTreeState() { State out = new State(); out.extractStateFrom(this); return out; } /** * Sets the tree state. * @param state The state, which was returned from the {@link #getTreeState() } method. */ public void setTreeState(TreeState state) { if (state instanceof State) { ((State)state).applyStateTo(this); } } /** * Toggles a mode where rows in the tree can be broken since span buttons will * be used instead of plain buttons. * @return the multilineMode */ public boolean isMultilineMode() { return multilineMode; } /** * Toggles a mode where rows in the tree can be broken since span buttons will * be used instead of plain buttons. * @param multilineMode the multilineMode to set */ public void setMultilineMode(boolean multilineMode) { this.multilineMode = multilineMode; } static class StringArrayTreeModel implements TreeModel { String[][] arr; StringArrayTreeModel(String[][] arr) { this.arr = arr; } public Vector getChildren(Object parent) { if(parent == null) { Vector v = new Vector(); int a0len = arr[0].length; for(int iter = 0 ; iter < a0len ; iter++) { v.addElement(arr[0][iter]); } return v; } int alen = arr.length; int aolen = arr[0].length; Vector v = new Vector(); for(int iter = 0 ; iter < aolen ; iter++) { if(parent == arr[0][iter]) { if(alen > iter + 1 && arr[iter + 1] != null) { int ailen = arr[iter + 1].length; for(int i = 0 ; i < ailen ; i++) { v.addElement(arr[iter + 1][i]); } } } } return v; } public boolean isLeaf(Object node) { Vector v = getChildren(node); return v == null || v.size() == 0; } } /** * {@inheritDoc} */ public String[] getPropertyNames() { return new String[] {"data"}; } /** * {@inheritDoc} */ public Class[] getPropertyTypes() { return new Class[] {com.codename1.impl.CodenameOneImplementation.getStringArray2DClass()}; } /** * {@inheritDoc} */ public String[] getPropertyTypeNames() { return new String[] {"String[][]"}; } /** * {@inheritDoc} */ public Object getPropertyValue(String name) { if(name.equals("data")) { return ((StringArrayTreeModel)model).arr; } return null; } /** * {@inheritDoc} */ public String setPropertyValue(String name, Object value) { if(name.equals("data")) { setModel(new StringArrayTreeModel((String[][])value)); return null; } return super.setPropertyValue(name, value); } /** * Construct a tree with the given tree model * * @param model represents the contents of the tree */ public Tree(TreeModel model) { this.model = model; setLayout(new BoxLayout(BoxLayout.Y_AXIS)); if(folder == null) { folder = UIManager.getInstance().getThemeImageConstant("treeFolderImage"); openFolder = UIManager.getInstance().getThemeImageConstant("treeFolderOpenImage"); nodeImage = UIManager.getInstance().getThemeImageConstant("treeNodeImage"); } buildBranch(null, 0, this); setScrollableY(true); setUIID("Tree"); } /** * Returns the tree model instance * * @return the tree model */ public TreeModel getModel() { return model; } /** * Sets the tree model to a new value * * @param model the model of the tree */ public void setModel(TreeModel model) { this.model = model; removeAll(); buildBranch(null, 0, this); } /** * Sets the icon for a tree folder * * @param folderIcon the icon for a folder within the tree */ public static void setFolderIcon(Image folderIcon) { folder = folderIcon; } /** * Sets the icon for a tree folder in its expanded state * * @param folderIcon the icon for a folder within the tree */ public static void setFolderOpenIcon(Image folderIcon) { openFolder = folderIcon; } /** * Sets the icon for a tree node * * @param nodeIcon the icon for a node within the tree */ public static void setNodeIcon(Image nodeIcon) { nodeImage = nodeIcon; } private Container expandNode(boolean animate, Component c) { return expandNode(animate, c, true); } private Container expandNode(boolean animate, Component c, boolean revalidate) { return expandNodeImpl(animate, c, revalidate); } private Container expandNodeImpl(boolean animate, Component c) { return expandNodeImpl(animate, c, true); } private Container expandNodeImpl(boolean animate, Component c, boolean revalidate) { Container p = c.getParent().getLeadParent(); if(p != null) { c = p; } c.putClientProperty(KEY_EXPANDED, "true"); if(openFolder == null) { setNodeMaterialIcon(FontImage.MATERIAL_FOLDER, c, 3); } else { setNodeIcon(openFolder, c); } int depth = ((Integer)c.getClientProperty(KEY_DEPTH)).intValue(); Container parent = c.getParent(); Object o = c.getClientProperty(KEY_OBJECT); Container dest = new Container(new BoxLayout(BoxLayout.Y_AXIS)); parent.addComponent(BorderLayout.CENTER, dest); buildBranch(o, depth, dest); if(isInitialized() && animate) { dest.setHeight(0); dest.setVisible(true); animateLayoutAndWait(300); //parent.animateHierarchyAndWait(300); if(multilineMode) { revalidate(); } } else { if (revalidate) { parent.revalidate(); } } return dest; } /** * This method returns true if the given node is expanded. * @param node a Component that represents a tree node. * @return true if this tree node is expanded */ protected boolean isExpanded(Component node) { Object e = node.getClientProperty(KEY_EXPANDED); return e != null && e.equals("true"); } private Container expandPathNode(boolean animate, Container parent, Object node) { int cc = parent.getComponentCount(); for(int iter = 0 ; iter < cc ; iter++) { Component current = parent.getComponentAt(iter); Object o = current.getClientProperty(KEY_OBJECT); if(!model.isLeaf(o)){ //if(current instanceof Container) { BorderLayout bl = (BorderLayout)((Container)current).getLayout(); // the tree component is always at north expanded or otherwise current = bl.getNorth(); if(o == node || o != null && o.equals(node)) { if(isExpanded(current)) { return (Container)bl.getCenter(); } return expandNodeImpl(animate, current); } } } return null; } private void collapsePathNode(Container parent, Object node) { int cc = parent.getComponentCount(); for(int iter = 0 ; iter < cc ; iter++) { Component current = parent.getComponentAt(iter); if(isExpanded(current)) { BorderLayout bl = (BorderLayout)((Container)current).getLayout(); // the tree component is always at north expanded or otherwise current = bl.getNorth(); Object o = current.getClientProperty(KEY_OBJECT); if(o != null && o.equals(node)) { if(isExpanded(current)) { collapseNode(current, null); } return; } } } } /** * Finds the component for a model node. * @param node The node from the model. * @return The corresponding component in the UI or null if not found. * @since 7.0 */ public Component findNodeComponent(Object node) { return findNodeComponent(node, this); } /** * Finds the component for a model node. * @param node Model node whose view we seek. * @param root A root component - we check root and its descendents. * @return The corresponding UI component for node, or null if not found. * @since 7.0 */ public Component findNodeComponent(Object node, Component root) { if (root == null) { return findNodeComponent(node, this); } Object rootNode = (Object)root.getClientProperty(KEY_OBJECT); if (node.equals(rootNode)) { return root; } if (root instanceof Container) { int len = ((Container)root).getComponentCount(); for (int i=0; i