Package org.apache.wicket.extensions.markup.html.tree

Source Code of org.apache.wicket.extensions.markup.html.tree.Tree$DefaultNodePanel

/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements.  See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.wicket.extensions.markup.html.tree;

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

import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeModelListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;

import org.apache.wicket.AttributeModifier;
import org.apache.wicket.Component;
import org.apache.wicket.ResourceReference;
import org.apache.wicket.WicketRuntimeException;
import org.apache.wicket.behavior.HeaderContributor;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.image.Image;
import org.apache.wicket.markup.html.link.Link;
import org.apache.wicket.markup.html.list.ListItem;
import org.apache.wicket.markup.html.list.ListView;
import org.apache.wicket.markup.html.list.Loop;
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.markup.html.resources.CompressedResourceReference;
import org.apache.wicket.model.IModel;


/**
* An tree that renders as a flat (not-nested) list, using spacers for
* indentation and nodes at the end of one row.
* <p>
* The visible tree rows are put in one flat list. For each row, a list is
* constructed with fillers, that can be used to create indentation. After the
* fillers, the actual node content is put.
* </p>
* <p>
* </p>
*
* @author Eelco Hillenius
*/
public class Tree extends AbstractTree implements TreeModelListener
{
  /**
   * The default node panel. If you provide your own panel by overriding
   * Tree.newNodePanel, but only want to override the markup, not the
   * components that are added, you <i>may</i> extend this class. If you want
   * to use other components than the default, provide a panel or fragment
   * instead (and that's probably what you want as the look and feel of what
   * this panel renders may be adjusted by overriding
   * {@link Tree#createJunctionLink(DefaultMutableTreeNode)} and
   * {@link Tree#createNodeLink(DefaultMutableTreeNode)}.
   */
  public static class DefaultNodePanel extends Panel
  {
    private static final long serialVersionUID = 1L;

    /**
     * Construct.
     *
     * @param panelId
     *            The component id
     * @param tree
     *            The containing tree component
     * @param node
     *            The tree node for this panel
     */
    public DefaultNodePanel(String panelId, Tree tree, DefaultMutableTreeNode node)
    {
      super(panelId);
      // create a link for expanding and collapsing the node
      Link expandCollapsLink = tree.createJunctionLink(node);
      add(expandCollapsLink);
      // create a link for selecting a node
      Link selectLink = tree.createNodeLink(node);
      add(selectLink);
    }
  }

  /**
   * Renders spacer items.
   */
  private static final class SpacerList extends Loop
  {
    private static final long serialVersionUID = 1L;

    /**
     * Construct.
     *
     * @param id
     *            component id
     * @param size
     *            size of loop
     */
    public SpacerList(String id, int size)
    {
      super(id, size);
    }

    /**
     * @see org.apache.wicket.markup.html.list.Loop#populateItem(LoopItem)
     */
    protected void populateItem(final Loop.LoopItem loopItem)
    {
      // nothing needed; we just render the tags and use CSS to indent
    }
  }

  /**
   * List view for tree paths.
   */
  private final class TreePathsListView extends ListView
  {
    private static final long serialVersionUID = 1L;

    /**
     * Construct.
     *
     * @param name
     *            name of the component
     */
    public TreePathsListView(String name)
    {
      super(name, treePathsModel);
    }

    /**
     * @see org.apache.wicket.markup.html.list.ListView#getReuseItems()
     */
    public boolean getReuseItems()
    {
      return Tree.this.getOptimizeItemRemoval();
    }

    /**
     * @see org.apache.wicket.markup.html.list.ListView#newItem(int)
     */
    protected ListItem newItem(final int index)
    {
      IModel listItemModel = getListItemModel(getModel(), index);

      // create a list item that is smart enough to determine whether
      // it should be displayed or not
      return new ListItem(index, listItemModel)
      {
        private static final long serialVersionUID = 1L;

        public boolean isVisible()
        {
          TreeState treeState = getTreeState();
          DefaultMutableTreeNode node = (DefaultMutableTreeNode)getModelObject();
          final TreePath path = new TreePath(node.getPath());
          final int row = treeState.getRowForPath(path);

          // if the row is -1, it is not visible, otherwise it is
          return (row != -1);
        }
      };
    }

    /**
     * @see org.apache.wicket.markup.html.list.ListView#populateItem(org.apache.wicket.markup.html.list.ListItem)
     */
    protected void populateItem(ListItem listItem)
    {
      // get the model object which is a tree node
      DefaultMutableTreeNode node = (DefaultMutableTreeNode)listItem.getModelObject();

      // add spacers
      int level = node.getLevel();
      listItem.add(new SpacerList("spacers", level));

      // add node panel
      Component nodePanel = newNodePanel("node", node);
      if (nodePanel == null)
      {
        throw new WicketRuntimeException("node panel must be not-null");
      }
      if (!"node".equals(nodePanel.getId()))
      {
        throw new WicketRuntimeException("panel must have id 'node' assigned");
      }

      listItem.add(nodePanel);

      // add attr modifier for highlighting the selection
      listItem.add(new AttributeModifier("class", true, new SelectedPathReplacementModel(
          Tree.this, node)));
    }
  }

  /**
   * Model for the paths of the tree.
   */
  private final class TreePathsModel implements IModel
  {
    private static final long serialVersionUID = 1L;

    /** whether this model is dirty. */
    boolean dirty = true;

    /** tree paths. */
    private List paths = new ArrayList();

    private transient boolean attached = false;

    /**
     * Inserts the given node in the path list with the given index.
     *
     * @param index
     *            the index where the node should be inserted in
     * @param node
     *            node to insert
     */
    void add(int index, DefaultMutableTreeNode node)
    {
      paths.add(index, node);
    }

    /**
     * Gives the index of the given node withing this tree.
     *
     * @param node
     *            node to look for
     * @return the index of the given node withing this tree
     */
    int indexOf(DefaultMutableTreeNode node)
    {
      return paths.indexOf(node);
    }

    /**
     * Removes the given node from the path list.
     *
     * @param node
     *            the node to remove
     */
    void remove(DefaultMutableTreeNode node)
    {
      paths.remove(node);
    }


    public Object getObject()
    {
      if (dirty && !attached)
      {
        paths.clear();
        TreeModel model = getTreeState().getModel();
        DefaultMutableTreeNode rootNode = (DefaultMutableTreeNode)model.getRoot();
        Enumeration e = rootNode.preorderEnumeration();
        while (e.hasMoreElements())
        {
          DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode)e.nextElement();
          // TreePath path = new TreePath(treeNode.getPath());
          paths.add(treeNode);
        }
        dirty = false;
      }
      attached = true;
      return paths;
    }

    public void setObject(Object object)
    {
      throw new UnsupportedOperationException("This is a read-only model");
    }

    public void detach()
    {
      attached = false;
    }
  }

  /** Name of the junction image component; value = 'junctionImage'. */
  public static final String JUNCTION_IMAGE_NAME = "junctionImage";

  /** Name of the node image component; value = 'nodeImage'. */
  public static final String NODE_IMAGE_NAME = "nodeImage";

  /** Blank image. */
  private static final ResourceReference BLANK = new ResourceReference(Tree.class, "blank.gif");

  /**
   * Reference to the css file.
   */
  private static final ResourceReference CSS = new CompressedResourceReference(Tree.class,
      "tree.css");

  /** Minus sign image. */
  private static final ResourceReference MINUS = new ResourceReference(Tree.class, "minus.gif");

  /** Plus sign image. */
  private static final ResourceReference PLUS = new ResourceReference(Tree.class, "plus.gif");

  private static final long serialVersionUID = 1L;

  /**
   * If true, re-rendering the tree is more efficient if the tree model
   * doesn't get changed. However, if this is true, you need to push changes
   * to this tree. This can easility be done by registering this tree as the
   * listener for tree model events (TreeModelListener), but you should <b>be
   * carefull</b> not to create a memory leak by doing this (e.g. when you
   * store the tree model in your session, the tree you registered cannot be
   * GC-ed). TRUE by default.
   */
  private boolean reuseItems = true;

  /** List view for tree paths. */
  private TreePathsListView treePathsListView;

  /** Model for the paths of the tree. */
  private TreePathsModel treePathsModel;

  /**
   * Constructor.
   *
   * @param id
   *            The id of this container
   * @param model
   *            the underlying tree model
   */
  public Tree(final String id, final TreeModel model)
  {
    super(id, model);
    this.treePathsModel = new TreePathsModel();
    add(treePathsListView = createTreePathsListView());

    ResourceReference css = getCss();
    add(HeaderContributor.forCss(css.getScope(), css.getName()));
  }

  /**
   * Construct using the given tree state that holds the model to be used as
   * the tree model.
   *
   * @param id
   *            The id of this container
   * @param treeState
   *            treeState that holds the underlying tree model
   */
  public Tree(String id, TreeState treeState)
  {
    super(id, treeState);
    this.treePathsModel = new TreePathsModel();
    add(treePathsListView = createTreePathsListView());

    ResourceReference css = getCss();
    add(HeaderContributor.forCss(css.getScope(), css.getName()));
  }

  /**
   * Gets whether item removal should be optimized. If true, re-rendering the
   * tree is more efficient if the tree model doesn't get changed. However, if
   * this is true, you need to push changes to this tree. This can easility be
   * done by registering this tree as the listener for tree model events
   * (TreeModelListener), but you should <b>be carefull</b> not to create a
   * memory leak by doing this (e.g. when you store the tree model in your
   * session, the tree you registered cannot be GC-ed). TRUE by default.
   *
   * @return whether item removal should be optimized
   * @deprecated Will be replaced by {@link #getReuseItems()}
   */
  // TODO Post 1.2: Remove
  public boolean getOptimizeItemRemoval()
  {
    return getReuseItems();
  }

  /**
   * Gets whether items should be reused. If true, re-rendering the tree is
   * more efficient if the tree model doesn't get changed. However, if this is
   * true, you need to push changes to this tree. This can easility be done by
   * registering this tree as the listener for tree model events
   * (TreeModelListener), but you should <b>be carefull</b> not to create a
   * memory leak by doing this (e.g. when you store the tree model in your
   * session, the tree you registered cannot be GC-ed). TRUE by default.
   *
   * @return whether items should be reused
   */
  public boolean getReuseItems()
  {
    return reuseItems;
  }

  /**
   * Sets whether items should be reused. If true, re-rendering the tree is
   * more efficient if the tree model doesn't get changed. However, if this is
   * true, you need to push changes to this tree. This can easility be done by
   * registering this tree as the listener for tree model events
   * (TreeModelListener), but you should <b>be carefull</b> not to create a
   * memory leak by doing this (e.g. when you store the tree model in your
   * session, the tree you registered cannot be GC-ed). TRUE by default.
   *
   * @param optimizeItemRemoval
   *            whether the child items should be reused
   * @deprecated Will be replaced by {@link #setReuseItems(boolean)}
   */
  // TODO Post 1.2: Remove
  public void setOptimizeItemRemoval(boolean optimizeItemRemoval)
  {
    setReuseItems(optimizeItemRemoval);
  }

  /**
   * Sets whether item removal should be optimized. If true, re-rendering the
   * tree is more efficient if the tree model doesn't get changed. However, if
   * this is true, you need to push changes to this tree. This can easility be
   * done by registering this tree as the listener for tree model events
   * (TreeModelListener), but you should <b>be carefull</b> not to create a
   * memory leak by doing this (e.g. when you store the tree model in your
   * session, the tree you registered cannot be GC-ed). TRUE by default.
   *
   * @param reuseItems
   *            whether the child items should be reused
   * @return This
   */
  public Tree setReuseItems(boolean reuseItems)
  {
    this.reuseItems = reuseItems;
    return this;
  }

  /**
   * Sets the current tree model.
   *
   * @param treeModel
   *            the tree model to set as the current one
   */
  public void setTreeModel(final TreeModel treeModel)
  {
    super.setTreeModel(treeModel);
    this.treePathsModel = new TreePathsModel();
    treePathsListView = createTreePathsListView();
    replace(treePathsListView);
  }

  /**
   * Sets the current tree state to the given tree state.
   *
   * @param treeState
   *            the tree state to set as the current one
   */
  public void setTreeState(final TreeState treeState)
  {
    super.setTreeState(treeState);
    this.treePathsModel = new TreePathsModel();
    treePathsListView = createTreePathsListView();
    replace(treePathsListView);
  }

  /**
   * @see javax.swing.event.TreeModelListener#treeNodesChanged(javax.swing.event.TreeModelEvent)
   */
  public void treeNodesChanged(TreeModelEvent e)
  {
    // nothing to do here
  }

  /**
   * @see javax.swing.event.TreeModelListener#treeNodesInserted(javax.swing.event.TreeModelEvent)
   */
  public void treeNodesInserted(TreeModelEvent e)
  {
    modelChanging();
    Object[] newNodes = e.getChildren();
    int len = newNodes.length;
    for (int i = 0; i < len; i++)
    {
      DefaultMutableTreeNode newNode = (DefaultMutableTreeNode)newNodes[i];
      DefaultMutableTreeNode previousNode = newNode.getPreviousSibling();
      int insertRow;
      if (previousNode == null)
      {
        previousNode = (DefaultMutableTreeNode)newNode.getParent();
      }
      if (previousNode != null)
      {
        insertRow = treePathsModel.indexOf(previousNode) + 1;
        if (insertRow == -1)
        {
          throw new IllegalStateException("node " + previousNode
              + " not found in backing list");
        }
      }
      else
      {
        insertRow = 0;
      }
      treePathsModel.add(insertRow, newNode);
    }
    modelChanged();
  }

  /**
   * @see javax.swing.event.TreeModelListener#treeNodesRemoved(javax.swing.event.TreeModelEvent)
   */
  public void treeNodesRemoved(TreeModelEvent e)
  {
    modelChanging();
    Object[] deletedNodes = e.getChildren();
    int len = deletedNodes.length;
    for (int i = 0; i < len; i++)
    {
      DefaultMutableTreeNode deletedNode = (DefaultMutableTreeNode)deletedNodes[i];
      treePathsModel.remove(deletedNode);
    }
    modelChanged();
  }

  /**
   * @see javax.swing.event.TreeModelListener#treeStructureChanged(javax.swing.event.TreeModelEvent)
   */
  public void treeStructureChanged(TreeModelEvent e)
  {
    treePathsModel.dirty = true;
    modelChanged();
  }

  /**
   * Creates a junction link.
   *
   * @param node
   *            the node
   * @return link for expanding/ collapsing the tree
   */
  protected Link createJunctionLink(final DefaultMutableTreeNode node)
  {
    final Link junctionLink = new Link("junctionLink")
    {
      private static final long serialVersionUID = 1L;

      public void onClick()
      {
        junctionLinkClicked(node);
      }
    };
    junctionLink.add(getJunctionImage(node));
    return junctionLink;
  }

  /**
   * Creates a node link.
   *
   * @param node
   *            the model of the node
   * @return link for selection
   */
  protected Link createNodeLink(final DefaultMutableTreeNode node)
  {
    final Link nodeLink = new Link("nodeLink")
    {
      private static final long serialVersionUID = 1L;

      public void onClick()
      {
        nodeLinkClicked(node);
      }
    };
    nodeLink.add(getNodeImage(node));
    nodeLink.add(new Label("label", getNodeLabel(node)));
    return nodeLink;
  }

  /**
   * Creates the tree paths list view.
   *
   * @return the tree paths list view
   */
  protected final TreePathsListView createTreePathsListView()
  {
    final TreePathsListView treePaths = new TreePathsListView("tree");
    return treePaths;
  }

  /**
   * Returns whether the path and the selected path are equal. This method is
   * used by the {@link AttributeModifier}that is used for setting the CSS
   * class for the selected row.
   *
   * @param path
   *            the path
   * @param selectedPath
   *            the selected path
   * @return true if the path and the selected are equal, false otherwise
   */
  protected boolean equals(final TreePath path, final TreePath selectedPath)
  {
    Object pathNode = path.getLastPathComponent();
    Object selectedPathNode = selectedPath.getLastPathComponent();
    return (pathNode != null && selectedPathNode != null && pathNode.equals(selectedPathNode));
  }

  /**
   * Gets the stylesheet.
   *
   * @return the stylesheet
   */
  protected ResourceReference getCss()
  {
    return CSS;
  }

  /**
   * Get image for a junction; used by method createExpandCollapseLink. If you
   * use the packaged panel (Tree.html), you must name the component using
   * JUNCTION_IMAGE_NAME.
   *
   * @param node
   *            the tree node
   * @return the image for the junction
   */
  protected Image getJunctionImage(final DefaultMutableTreeNode node)
  {
    if (!node.isLeaf())
    {
      // we want the image to be dynamically, yet resolving to a static
      // image.
      return new Image(JUNCTION_IMAGE_NAME)
      {
        private static final long serialVersionUID = 1L;

        protected ResourceReference getImageResourceReference()
        {
          if (isExpanded(node))
          {
            return MINUS;
          }
          else
          {
            return PLUS;
          }
        }
      };
    }
    else
    {
      return new Image(JUNCTION_IMAGE_NAME, BLANK);
    }
  }

  /**
   * Get image for a node; used by method createNodeLink. If you use the
   * packaged panel (Tree.html), you must name the component using
   * NODE_IMAGE_NAME.
   *
   * @param node
   *            the tree node
   * @return the image for the node
   */
  protected Image getNodeImage(final DefaultMutableTreeNode node)
  {
    return new Image(NODE_IMAGE_NAME, BLANK);
  }

  /**
   * Gets the label of the node that is used for the node link. Defaults to
   * treeNodeModel.getUserObject().toString(); override to provide a custom
   * label
   *
   * @param node
   *            the tree node
   * @return the label of the node that is used for the node link
   */
  protected String getNodeLabel(final DefaultMutableTreeNode node)
  {
    return String.valueOf(node.getUserObject());
  }

  /**
   * @see org.apache.wicket.Component#onAttach()
   */
  protected void onAttach()
  {
    super.onAttach();
    // if we don't optimize, rebuild the paths on every request
    if (!getOptimizeItemRemoval())
    {
      treePathsModel.dirty = true;
    }
  }

  /**
   * Handler that is called when a junction link is clicked; this
   * implementation sets the expanded state to one that corresponds with the
   * node selection.
   *
   * @param node
   *            the tree node
   */
  protected void junctionLinkClicked(final DefaultMutableTreeNode node)
  {
    setExpandedState(node);
  }

  /**
   * Create a new panel for a tree node. This method can be overriden to
   * provide a custom panel. This way, you can effectively nest anything you
   * want in the tree, like input fields, images, etc.
   * <p>
   * <strong> you must use the provide panelId as the id of your custom panel
   * </strong><br>
   * for example, do:
   *
   * <pre>
   * return new MyNodePanel(panelId, node);
   * </pre>
   *
   * </p>
   * <p>
   * You can choose to either let your own panel extend from DefaultNodePanel
   * when you just want to provide different markup but want to reuse the
   * default components on this panel, or extend from NodePanel directly, and
   * provide any component structure you like.
   * </p>
   *
   * @param panelId
   *            the id that the panel MUST use
   * @param node
   *            the tree node for the panel
   * @return a new Panel
   */
  protected Component newNodePanel(String panelId, DefaultMutableTreeNode node)
  {
    return new DefaultNodePanel(panelId, this, node);
  }

  /**
   * Handler that is called when a node link is clicked; this implementation
   * sets the expanded state just as a click on a junction would do. Override
   * this for custom behavior.
   *
   * @param node
   *            the tree node model
   */
  protected void nodeLinkClicked(final DefaultMutableTreeNode node)
  {
    setSelected(node);
  }
}
TOP

Related Classes of org.apache.wicket.extensions.markup.html.tree.Tree$DefaultNodePanel

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.