From 4eca299ef80448d2caa08beef7eeea3ce3b128ce Mon Sep 17 00:00:00 2001 From: Heng Sin Low Date: Sun, 20 Jul 2008 22:16:06 +0000 Subject: [PATCH] * More complete implementation of ad tree tab - Sync of state between tree and form - Support movement of node using drap and drop - No search support for tree yet. --- .../webui/component/ADTreeOnDropListener.java | 220 ++++++++++++++++++ .../webui/component/SimpleTreeModel.java | 148 +++++++++++- .../org/adempiere/webui/panel/ADTabpanel.java | 96 ++++---- 3 files changed, 420 insertions(+), 44 deletions(-) create mode 100644 zkwebui/WEB-INF/src/org/adempiere/webui/component/ADTreeOnDropListener.java diff --git a/zkwebui/WEB-INF/src/org/adempiere/webui/component/ADTreeOnDropListener.java b/zkwebui/WEB-INF/src/org/adempiere/webui/component/ADTreeOnDropListener.java new file mode 100644 index 0000000000..272f094fee --- /dev/null +++ b/zkwebui/WEB-INF/src/org/adempiere/webui/component/ADTreeOnDropListener.java @@ -0,0 +1,220 @@ +/****************************************************************************** + * 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 org.adempiere.webui.window.FDialog; +import org.compiere.model.MTree; +import org.compiere.model.MTreeNode; +import org.compiere.util.CLogger; +import org.compiere.util.DB; +import org.compiere.util.Trx; +import org.zkoss.zk.ui.event.DropEvent; +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.Menuitem; +import org.zkoss.zul.Menupopup; +import org.zkoss.zul.SimpleTreeNode; +import org.zkoss.zul.Tree; +import org.zkoss.zul.Treeitem; +import org.zkoss.zul.Treerow; + +/** + * + * @author Low Heng Sin + * + */ +public class ADTreeOnDropListener implements EventListener { + + private SimpleTreeModel treeModel; + private MTree mTree; + private int windowNo; + private Tree tree; + + private static final CLogger log = CLogger.getCLogger(ADTreeOnDropListener.class); + + /** + * + * @param tree + * @param model + * @param mTree + * @param windowNo + */ + public ADTreeOnDropListener(Tree tree, SimpleTreeModel model, MTree mTree, int windowNo) { + this.tree = tree; + this.treeModel = model; + this.mTree = mTree; + this.windowNo = windowNo; + } + + /** + * @param event + */ + public void onEvent(Event event) throws Exception { + if (event instanceof DropEvent) { + DropEvent de = (DropEvent) event; + log.fine("Source=" + de.getDragged() + " Target=" + de.getTarget()); + if (de.getDragged() != de.getTarget()) { + Treeitem src = (Treeitem) ((Treerow) de.getDragged()).getParent(); + Treeitem target = (Treeitem) ((Treerow) de.getTarget()).getParent(); + moveNode((SimpleTreeNode)src.getValue(), (SimpleTreeNode)target.getValue()); + } + } + } + + /** + * Move TreeNode + * @param movingNode The node to be moved + * @param toNode The target node + */ + private void moveNode(SimpleTreeNode movingNode, SimpleTreeNode toNode) + { + log.info(movingNode.toString() + " to " + toNode.toString()); + + if (movingNode == toNode) + return; + + MTreeNode toMNode = (MTreeNode) toNode.getData(); + + SimpleTreeNode newParent; + int index; + if (!toMNode.isSummary()) // drop on a child node + { + moveNode(movingNode, toNode, false); + } + else // drop on a summary node + { + //prompt user to select insert after or drop into the summary node + int path[] = treeModel.getPath(treeModel.getRoot(), toNode); + Treeitem toItem = tree.renderItemByPath(path); + + tree.setSelectedItem(toItem); + Events.sendEvent(tree, new Event(Events.ON_SELECT, tree)); + + MenuListener listener = new MenuListener(movingNode, toNode); + + //TODO: translation + Menupopup popup = new Menupopup(); + Menuitem menuItem = new Menuitem("Insert After"); + menuItem.setValue("InsertAfter"); + menuItem.setParent(popup); + menuItem.addEventListener(Events.ON_CLICK, listener); + + menuItem = new Menuitem("Move Into"); + menuItem.setValue("MoveInto"); + menuItem.setParent(popup); + menuItem.addEventListener(Events.ON_CLICK, listener); + + popup.setPage(tree.getPage()); + popup.open(toItem.getTreerow()); + } + + } // moveNode + + private void moveNode(SimpleTreeNode movingNode, SimpleTreeNode toNode, boolean moveInto) + { + SimpleTreeNode newParent; + int index; + if (!moveInto) + { + newParent = treeModel.getParent(toNode); + index = newParent.getChildren().indexOf(toNode) + 1; // the next node + } + else // drop on a summary node + { + newParent = toNode; + index = 0; // the first node + } + + // remove + SimpleTreeNode oldParent = treeModel.getParent(movingNode); + treeModel.removeNode(movingNode); + + // insert + treeModel.addNode(newParent, movingNode, index); + + int path[] = treeModel.getPath(treeModel.getRoot(), movingNode); + Treeitem movingItem = tree.renderItemByPath(path); + tree.setSelectedItem(movingItem); + Events.sendEvent(tree, new Event(Events.ON_SELECT, tree)); + + // *** Save changes to disk + Trx trx = Trx.get (Trx.createTrxName("ADTree"), true); + try + { + int no = 0; + MTreeNode oldMParent = (MTreeNode) oldParent.getData(); + for (int i = 0; i < oldParent.getChildCount(); i++) + { + SimpleTreeNode nd = (SimpleTreeNode)oldParent.getChildAt(i); + MTreeNode md = (MTreeNode) nd.getData(); + StringBuffer sql = new StringBuffer("UPDATE "); + sql.append(mTree.getNodeTableName()) + .append(" SET Parent_ID=").append(oldMParent.getNode_ID()) + .append(", SeqNo=").append(i) + .append(", Updated=SysDate") + .append(" WHERE AD_Tree_ID=").append(mTree.getAD_Tree_ID()) + .append(" AND Node_ID=").append(md.getNode_ID()); + log.fine(sql.toString()); + no = DB.executeUpdate(sql.toString(),trx.getTrxName()); + } + if (oldParent != newParent) + { + MTreeNode newMParent = (MTreeNode) newParent.getData(); + for (int i = 0; i < newParent.getChildCount(); i++) + { + SimpleTreeNode nd = (SimpleTreeNode)newParent.getChildAt(i); + MTreeNode md = (MTreeNode) nd.getData(); + StringBuffer sql = new StringBuffer("UPDATE "); + sql.append(mTree.getNodeTableName()) + .append(" SET Parent_ID=").append(newMParent.getNode_ID()) + .append(", SeqNo=").append(i) + .append(", Updated=SysDate") + .append(" WHERE AD_Tree_ID=").append(mTree.getAD_Tree_ID()) + .append(" AND Node_ID=").append(md.getNode_ID()); + log.fine(sql.toString()); + no = DB.executeUpdate(sql.toString(),trx.getTrxName()); + } + } + // COMMIT ********************* + trx.commit(true); + } + catch (Exception e) + { + trx.rollback(); + FDialog.error(windowNo, tree, "TreeUpdateError", e.getLocalizedMessage()); + } + trx.close(); + trx = null; + } + + class MenuListener implements EventListener { + private SimpleTreeNode movingNode; + private SimpleTreeNode toNode; + MenuListener(SimpleTreeNode movingNode, SimpleTreeNode toNode) { + this.movingNode = movingNode; + this.toNode = toNode; + } + public void onEvent(Event event) throws Exception { + if (Events.ON_CLICK.equals(event.getName()) && event.getTarget() instanceof Menuitem) { + Menuitem menuItem = (Menuitem) event.getTarget(); + if ("InsertAfter".equals(menuItem.getValue())) { + moveNode(movingNode, toNode, false); + } else if ("MoveInto".equals(menuItem.getValue())) { + moveNode(movingNode, toNode, true); + } + } + } + + } +} diff --git a/zkwebui/WEB-INF/src/org/adempiere/webui/component/SimpleTreeModel.java b/zkwebui/WEB-INF/src/org/adempiere/webui/component/SimpleTreeModel.java index b9580daabc..e139b05650 100644 --- a/zkwebui/WEB-INF/src/org/adempiere/webui/component/SimpleTreeModel.java +++ b/zkwebui/WEB-INF/src/org/adempiere/webui/component/SimpleTreeModel.java @@ -9,34 +9,70 @@ * 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. * - * For the text or an alternative of this public license, you may reach us * - * Posterita Ltd., 3, Draper Avenue, Quatre Bornes, Mauritius * - * or via info@posterita.org or http://www.posterita.org/ * *****************************************************************************/ package org.adempiere.webui.component; import java.util.ArrayList; import java.util.Enumeration; +import java.util.List; +import java.util.logging.Level; +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.SimpleTreeNode; +import org.zkoss.zul.Tree; 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; /** * * @author Low Heng Sin * */ -public class SimpleTreeModel extends org.zkoss.zul.SimpleTreeModel implements TreeitemRenderer { +public class SimpleTreeModel extends org.zkoss.zul.SimpleTreeModel implements TreeitemRenderer, EventListener { + + private static final CLogger logger = CLogger.getCLogger(SimpleTreeModel.class); + + private boolean itemDraggable; + private List onDropListners = new ArrayList(); public SimpleTreeModel(SimpleTreeNode root) { super(root); } + public static SimpleTreeModel initADTree(Tree tree, int AD_Tree_ID, int windowNo) { + MTree vTree = new MTree (Env.getCtx(), AD_Tree_ID, false, true, null); + MTreeNode root = vTree.getRoot(); + SimpleTreeModel treeModel = SimpleTreeModel.createFrom(root); + treeModel.setItemDraggable(true); + treeModel.addOnDropEventListener(new ADTreeOnDropListener(tree, treeModel, vTree, windowNo)); + + Treecols treeCols = new Treecols(); + tree.appendChild(treeCols); + Treecol treeCol = new Treecol(); + treeCols.appendChild(treeCol); + tree.setPageSize(-1); + try { + tree.setTreeitemRenderer(treeModel); + tree.setModel(treeModel); + } catch (Exception e) { + logger.log(Level.SEVERE, "Failed to setup tree"); + } + + return treeModel; + } + public static SimpleTreeModel createFrom(MTreeNode root) { SimpleTreeModel model = null; Enumeration nodeEnum = root.children(); @@ -70,8 +106,15 @@ public class SimpleTreeModel extends org.zkoss.zul.SimpleTreeModel implements Tr Treecell tc = new Treecell(Objects.toString(node)); Treerow tr = null; if(ti.getTreerow()==null){ - tr = new Treerow(); + tr = new Treerow(); tr.setParent(ti); + if (isItemDraggable()) { + tr.setDraggable("true"); + } + if (!onDropListners.isEmpty()) { + tr.setDroppable("true"); + tr.addEventListener(Events.ON_DROP, this); + } }else{ tr = ti.getTreerow(); tr.getChildren().clear(); @@ -81,4 +124,99 @@ public class SimpleTreeModel extends org.zkoss.zul.SimpleTreeModel implements Tr ti.setValue(node); } + /** + * Add to root + * @param newNode + */ + public void addNode(SimpleTreeNode newNode) { + SimpleTreeNode root = (SimpleTreeNode) getRoot(); + root.getChildren().add(newNode); + fireEvent(root, root.getChildCount() - 1, root.getChildCount() - 1, TreeDataEvent.INTERVAL_ADDED); + } + + @Override + public SimpleTreeNode getRoot() { + return (SimpleTreeNode) super.getRoot(); + } + + @Override + public SimpleTreeNode getChild(Object parent, int index) { + return (SimpleTreeNode) super.getChild(parent, index); + } + + public void removeNode(SimpleTreeNode treeNode) { + int path[] = this.getPath(getRoot(), treeNode); + + if (path != null && path.length > 0) { + SimpleTreeNode parentNode = getRoot(); + int index = path.length - 1; + for (int i = 0; i < index; i++) { + parentNode = getChild(parentNode, path[i]); + } + + + parentNode.getChildren().remove(path[index]); + fireEvent(parentNode, path[index], path[index], TreeDataEvent.INTERVAL_REMOVED); + } + } + + public void setItemDraggable(boolean b) { + itemDraggable = b; + } + + public boolean isItemDraggable() { + return itemDraggable; + } + + public void addOnDropEventListener(EventListener listener) { + onDropListners.add(listener); + } + + public void onEvent(Event event) throws Exception { + if (Events.ON_DROP.equals(event.getName())) { + for (EventListener listener : onDropListners) { + listener.onEvent(event); + } + } + } + + public SimpleTreeNode getParent(SimpleTreeNode treeNode) { + int path[] = this.getPath(getRoot(), treeNode); + + if (path != null && path.length > 0) { + SimpleTreeNode parentNode = getRoot(); + int index = path.length - 1; + for (int i = 0; i < index; i++) { + parentNode = getChild(parentNode, path[i]); + } + + return parentNode; + } + + return null; + } + + public void addNode(SimpleTreeNode newParent, SimpleTreeNode newNode, + int index) { + newParent.getChildren().add(index, newNode); + fireEvent(newParent, index, index, TreeDataEvent.INTERVAL_ADDED); + } + + public SimpleTreeNode find(SimpleTreeNode 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++ ) { + SimpleTreeNode child = getChild(fromNode, i); + SimpleTreeNode treeNode = find(child, recordId); + if (treeNode != null) + return treeNode; + } + return null; + } } diff --git a/zkwebui/WEB-INF/src/org/adempiere/webui/panel/ADTabpanel.java b/zkwebui/WEB-INF/src/org/adempiere/webui/panel/ADTabpanel.java index 1c53084124..26d7214129 100644 --- a/zkwebui/WEB-INF/src/org/adempiere/webui/panel/ADTabpanel.java +++ b/zkwebui/WEB-INF/src/org/adempiere/webui/panel/ADTabpanel.java @@ -74,8 +74,6 @@ import org.zkoss.zul.Separator; import org.zkoss.zul.SimpleTreeNode; import org.zkoss.zul.Toolbarbutton; import org.zkoss.zul.Tree; -import org.zkoss.zul.Treecol; -import org.zkoss.zul.Treecols; import org.zkoss.zul.Treeitem; /** @@ -394,21 +392,7 @@ DataStatusListener, ValueChangeListener, IADTabpanel if (gridTab.isTreeTab() && tree != null) { int AD_Tree_ID = MTree.getDefaultAD_Tree_ID ( Env.getAD_Client_ID(Env.getCtx()), gridTab.getKeyColumnName()); - MTree vTree = new MTree (Env.getCtx(), AD_Tree_ID, false, true, null); - MTreeNode root = vTree.getRoot(); - SimpleTreeModel treeModel = SimpleTreeModel.createFrom(root); - - Treecols treeCols = new Treecols(); - tree.appendChild(treeCols); - Treecol treeCol = new Treecol(); - treeCols.appendChild(treeCol); - tree.setPageSize(-1); - try { - tree.setTreeitemRenderer(treeModel); - tree.setModel(treeModel); - } catch (Exception e) { - logger.log(Level.SEVERE, "Failed to setup tree"); - } + SimpleTreeModel.initADTree(tree, AD_Tree_ID, windowNo); } } @@ -689,7 +673,7 @@ DataStatusListener, ValueChangeListener, IADTabpanel public void dataStatusChanged(DataStatusEvent e) { - //TODO: ignore non-ui thread event for now + //ignore background event if (Executions.getCurrent() == null) return; int col = e.getChangedColumn(); @@ -730,12 +714,60 @@ DataStatusListener, ValueChangeListener, IADTabpanel //sync tree if (tree != null) { - setSelectedNode(gridTab.getRecord_ID()); + if ("Deleted".equalsIgnoreCase(e.getAD_Message())) + if (e.Record_ID != null + && e.Record_ID instanceof Integer + && ((Integer)e.Record_ID != gridTab.getRecord_ID())) + deleteNode((Integer)e.Record_ID); + else + setSelectedNode(gridTab.getRecord_ID()); + else + setSelectedNode(gridTab.getRecord_ID()); } } - private void setSelectedNode(int recordId) { - if (recordId < 0) return; + private void deleteNode(int recordId) { + if (recordId <= 0) return; + + SimpleTreeModel model = (SimpleTreeModel) tree.getModel(); + + if (tree.getSelectedItem() != null) { + SimpleTreeNode treeNode = (SimpleTreeNode) tree.getSelectedItem().getValue(); + MTreeNode data = (MTreeNode) treeNode.getData(); + if (data.getNode_ID() == recordId) { + model.removeNode(treeNode); + return; + } + } + + SimpleTreeNode treeNode = model.find(null, recordId); + if (treeNode != null) { + model.removeNode(treeNode); + } + } + + private void addNewNode() { + if (gridTab.getRecord_ID() > 0) { + String name = (String)gridTab.getValue("Name"); + String description = (String)gridTab.getValue("Description"); + boolean summary = gridTab.getValueAsBoolean("IsSummary"); + String imageIndicator = (String)gridTab.getValue("Action"); // Menu - Action + // + SimpleTreeModel model = (SimpleTreeModel) tree.getModel(); + SimpleTreeNode treeNode = model.getRoot(); + MTreeNode root = (MTreeNode) treeNode.getData(); + MTreeNode node = new MTreeNode (gridTab.getRecord_ID(), 0, name, description, + root.getNode_ID(), summary, imageIndicator, false, null); + SimpleTreeNode newNode = new SimpleTreeNode(node, new ArrayList()); + model.addNode(newNode); + int[] path = model.getPath(model.getRoot(), newNode); + Treeitem ti = tree.renderItemByPath(path); + tree.setSelectedItem(ti); + } + } + + private void setSelectedNode(int recordId) { + if (recordId <= 0) return; if (tree.getSelectedItem() != null) { SimpleTreeNode treeNode = (SimpleTreeNode) tree.getSelectedItem().getValue(); @@ -744,31 +776,16 @@ DataStatusListener, ValueChangeListener, IADTabpanel } SimpleTreeModel model = (SimpleTreeModel) tree.getModel(); - SimpleTreeNode root = (SimpleTreeNode) model.getRoot(); - SimpleTreeNode treeNode = find(model, root, recordId); + SimpleTreeNode treeNode = model.find(null, recordId); if (treeNode != null) { int[] path = model.getPath(model.getRoot(), treeNode); Treeitem ti = tree.renderItemByPath(path); tree.setSelectedItem(ti); + } else { + addNewNode(); } } - private SimpleTreeNode find(SimpleTreeModel model, SimpleTreeNode root, int recordId) { - MTreeNode data = (MTreeNode) root.getData(); - if (data.getNode_ID() == recordId) - return root; - if (model.isLeaf(root)) - return null; - int cnt = model.getChildCount(root); - for(int i = 0; i < cnt; i++ ) { - SimpleTreeNode child = (SimpleTreeNode) model.getChild(root, i); - SimpleTreeNode treeNode = find(model, child, recordId); - if (treeNode != null) - return treeNode; - } - return null; - } - public void valueChange(ValueChangeEvent e) { if (gridTab.isProcessed()) // only active records @@ -847,3 +864,4 @@ DataStatusListener, ValueChangeListener, IADTabpanel } } +