/******************************************************************************
* Copyright (C) 2008 Low Heng Sin *
* This program is free software; you can redistribute it and/or modify it *
* under the terms version 2 of the GNU General Public License as published *
* by the Free Software Foundation. This program 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 for more details. *
* You should have received a copy of the GNU General Public License along *
* with this program; if not, write to the Free Software Foundation, Inc., *
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. *
*****************************************************************************/
package org.adempiere.webui.component;
import java.awt.Color;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.logging.Level;
import org.adempiere.webui.ClientInfo;
import org.adempiere.webui.adwindow.ADTreePanel;
import org.adempiere.webui.panel.TreeSearchPanel;
import org.compiere.model.MTree;
import org.compiere.model.MTreeNode;
import org.compiere.util.CLogger;
import org.compiere.util.Env;
import org.zkoss.lang.Objects;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.EventListener;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zul.DefaultTreeNode;
import org.zkoss.zul.Tree;
import org.zkoss.zul.TreeNode;
import org.zkoss.zul.Treecell;
import org.zkoss.zul.Treecol;
import org.zkoss.zul.Treecols;
import org.zkoss.zul.Treeitem;
import org.zkoss.zul.TreeitemRenderer;
import org.zkoss.zul.Treerow;
import org.zkoss.zul.event.TreeDataEvent;
/**
* {@link org.zkoss.zul.DefaultTreeModel} implementation that also implements the {@link TreeitemRenderer} interface.
* Use by {@link ADTreePanel}.
* @author Low Heng Sin
*
*/
public class SimpleTreeModel extends org.zkoss.zul.DefaultTreeModel implements TreeitemRenderer, EventListener {
/**
* generated serial id
*/
private static final long serialVersionUID = 4945968834244672653L;
private static final CLogger logger = CLogger.getCLogger(SimpleTreeModel.class);
/** True if each tree item is draggable */
private boolean itemDraggable;
/** Listeners for ON_DROP event */
private List> onDropListners = new ArrayList>();
/**
* @param root Root node
*/
public SimpleTreeModel(DefaultTreeNode root) {
super(root);
}
/**
* @param tree
* @param AD_Tree_ID
* @param windowNo
* @return SimpleTreeModel
*/
public static SimpleTreeModel initADTree(Tree tree, int AD_Tree_ID, int windowNo) {
return initADTree(tree, AD_Tree_ID, windowNo, true, null);
}
/**
* @param tree
* @param AD_Tree_ID
* @param windowNo
* @param linkColName
* @param linkID
* @return
*/
public static SimpleTreeModel initADTree(Tree tree, int AD_Tree_ID, int windowNo, String linkColName, int linkID) {
return initADTree(tree, AD_Tree_ID, windowNo, true, null, linkColName, linkID);
}
/**
* @param tree
* @param AD_Tree_ID
* @param windowNo
* @param editable
* @param trxName
* @return SimpleTreeModel
*/
public static SimpleTreeModel initADTree(Tree tree, int AD_Tree_ID, int windowNo, boolean editable, String trxName) {
return initADTree(tree, AD_Tree_ID, windowNo, editable, trxName, null, 0);
}
/**
* Create tree model from AD_Tree structure
* @param tree
* @param AD_Tree_ID
* @param windowNo
* @param editable
* @param trxName
* @param linkColName
* @param linkID
* @return SimpleTreeModel
*/
public static SimpleTreeModel initADTree(Tree tree, int AD_Tree_ID, int windowNo, boolean editable, String trxName, String linkColName, int linkID) {
MTree vTree = new MTree (Env.getCtx(), AD_Tree_ID, editable, true, trxName, linkColName, linkID);
MTreeNode root = vTree.getRoot();
SimpleTreeModel treeModel = SimpleTreeModel.createFrom(root);
treeModel.setItemDraggable(true);
treeModel.setTreeDrivenByValue(vTree.isTreeDrivenByValue());
treeModel.setIsValueDisplayed(vTree.isValueDisplayed());
treeModel.addOnDropEventListener(new ADTreeOnDropListener(tree, treeModel, vTree, windowNo));
if (tree.getTreecols() == null)
{
Treecols treeCols = new Treecols();
tree.getTreecols();
tree.appendChild(treeCols);
Treecol treeCol = new Treecol();
treeCols.appendChild(treeCol);
}
tree.setPageSize(-1);
try {
tree.setItemRenderer(treeModel);
tree.setModel(treeModel);
} catch (Exception e) {
logger.log(Level.SEVERE, "Failed to setup tree");
}
return treeModel;
}
private boolean isTreeDrivenByValue = false;
/**
* @return true if sort by Value, false otherwise
*/
public boolean isTreeDrivenByValue() {
return isTreeDrivenByValue;
}
/**
* Set tree sorted by Value or Name
* @param isTreeDrivenByValue
*/
public void setTreeDrivenByValue(boolean isTreeDrivenByValue) {
this.isTreeDrivenByValue = isTreeDrivenByValue;
}
private boolean isValueDisplayed = false;
/**
* @return true if Value is display in front of Name
*/
public boolean isValueDisplayed() {
return isValueDisplayed;
}
/**
* Set whether Value is display
* @param isValueDisplayed
*/
public void setIsValueDisplayed(boolean isValueDisplayed) {
this.isValueDisplayed = isValueDisplayed;
}
/**
* Create model from {@link MTreeNode} structure
* @param root Root node
* @return SimpleTreeModel
*/
public static SimpleTreeModel createFrom(MTreeNode root) {
SimpleTreeModel model = null;
Enumeration> nodeEnum = root.children();
DefaultTreeNode stRoot = new DefaultTreeNode(root, nodeEnum.hasMoreElements() ? new ArrayList>() : null);
while(nodeEnum.hasMoreElements()) {
MTreeNode childNode = (MTreeNode)nodeEnum.nextElement();
DefaultTreeNode stChildNode = childNode.getChildCount() > 0 ? new DefaultTreeNode(childNode, new ArrayList>())
: new DefaultTreeNode(childNode);
stRoot.getChildren().add(stChildNode);
if (childNode.getChildCount() > 0) {
populate(stChildNode, childNode);
}
}
model = new SimpleTreeModel(stRoot);
return model;
}
/**
*
* @param stParentNode DefaultTreeNode wrapper for parentNode
* @param parentNode
*/
private static void populate(DefaultTreeNode stParentNode, MTreeNode parentNode) {
Enumeration> nodeEnum = parentNode.children();
while(nodeEnum.hasMoreElements()) {
MTreeNode childNode = (MTreeNode)nodeEnum.nextElement();
DefaultTreeNode stChildNode = childNode.getChildCount() > 0 ? new DefaultTreeNode(childNode, new ArrayList>())
: new DefaultTreeNode(childNode);
stParentNode.getChildren().add(stChildNode);
if (childNode.getChildCount() > 0) {
populate(stChildNode, childNode);
}
}
}
/**
* @param ti
* @param node
* @param index
*/
@Override
public void render(Treeitem ti, Object node, int index) {
Treecell tc = new Treecell(Objects.toString(node));
Treerow tr = null;
if(ti.getTreerow()==null){
tr = new Treerow();
tr.setParent(ti);
if (isItemDraggable()) {
//use different approach on mobile, dnd not working well
if (ClientInfo.isMobile()) {
tr.setAttribute(TreeSearchPanel.TREE_ROW_MOVABLE, Boolean.TRUE);
} else {
tr.setDraggable("true");
}
}
if (!onDropListners.isEmpty()) {
tr.setDroppable("true");
tr.addEventListener(Events.ON_DROP, this);
}
// Color
Object data = ((DefaultTreeNode>) node).getData();
if (data instanceof MTreeNode) {
final MTreeNode mNode = (MTreeNode) data;
Color color = mNode.getColor();
if (color != null){
String hex = ZkCssHelper.createHexColorString(color);
ZkCssHelper.appendStyle(tc, "color: #" + hex);
}
ti.setTooltiptext(mNode.getDescription());
if (mNode.isSummary())
ZkCssHelper.appendStyle(tc, "font-weight: bold");
}
// End color
}else{
tr = ti.getTreerow();
tr.getChildren().clear();
}
tc.setParent(tr);
ti.setValue(node);
}
/**
* Add to root
* @param newNode
*/
public void addNode(DefaultTreeNode newNode) {
DefaultTreeNode root = getRoot();
root.getChildren().add(newNode);
}
@Override
public DefaultTreeNode getRoot() {
return (DefaultTreeNode) super.getRoot();
}
/**
* @param treeNode
*/
public void removeNode(DefaultTreeNode treeNode) {
int path[] = this.getPath(treeNode);
if (path != null && path.length > 0) {
DefaultTreeNode parentNode = getRoot();
int index = path.length - 1;
for (int i = 0; i < index; i++) {
parentNode = (DefaultTreeNode) getChild((TreeNode)parentNode, path[i]);
}
parentNode.getChildren().remove(path[index]);
}
}
/**
* Set draggable or not draggable for each tree node
* @param b
*/
public void setItemDraggable(boolean b) {
itemDraggable = b;
}
/**
* @return true if tree item is draggable, false otherwise
*/
public boolean isItemDraggable() {
return itemDraggable;
}
/**
* Add listener for ON_DROP event
* @param listener
*/
public void addOnDropEventListener(EventListener listener) {
onDropListners.add(listener);
}
/**
* @param event
* @see EventListener#onEvent(Event)
*/
@Override
public void onEvent(Event event) throws Exception {
if (Events.ON_DROP.equals(event.getName())) {
for (EventListener listener : onDropListners) {
listener.onEvent(event);
}
}
}
/**
* Find parent node for treeNode
* @param treeNode
* @return DefaultTreeNode or null if not found
*/
public DefaultTreeNode getParent(DefaultTreeNode treeNode) {
int path[] = this.getPath(treeNode);
if (path != null && path.length > 0) {
DefaultTreeNode parentNode = getRoot();
int index = path.length - 1;
for (int i = 0; i < index; i++) {
parentNode = (DefaultTreeNode) getChild((TreeNode)parentNode, path[i]);
}
return parentNode;
}
return null;
}
/**
* @param newParent
* @param newNode
* @param index
* @return parent node. this could be a new instance created to replace the newParent node parameter
*/
public DefaultTreeNode addNode(DefaultTreeNode newParent, DefaultTreeNode newNode,
int index) {
DefaultTreeNode parent = newParent;
if (newParent.getChildren() == null) {
parent = new DefaultTreeNode(newParent.getData(), new ArrayList>());
newParent.getParent().insert(parent, newParent.getParent().getIndex(newParent));
removeNode(newParent);
}
parent.getChildren().add(index, newNode);
return parent;
}
/**
* Find node with Node_ID=recordId starting from fromNode or root node.
* @param fromNode Optional starting node. If null, start from Root node
* @param recordId Node_ID
* @return DefaultTreeNode
*/
public DefaultTreeNode find(DefaultTreeNode fromNode, int recordId) {
if (fromNode == null)
fromNode = getRoot();
MTreeNode data = (MTreeNode) fromNode.getData();
if (data.getNode_ID() == recordId)
return fromNode;
if (isLeaf(fromNode))
return null;
int cnt = getChildCount(fromNode);
for(int i = 0; i < cnt; i++ ) {
DefaultTreeNode child = (DefaultTreeNode) getChild(fromNode, i);
DefaultTreeNode treeNode = find(child, recordId);
if (treeNode != null)
return treeNode;
}
return null;
}
/**
* Fire CONTENTS_CHANGED event for node
* @param node DefaultTreeNode
*/
public void nodeUpdated(DefaultTreeNode node) {
DefaultTreeNode parent = getParent(node);
if (parent != null) {
int i = parent.getChildren().indexOf(node);
fireEvent(TreeDataEvent.CONTENTS_CHANGED, getPath(parent), i, i);
}
}
}