/* * This file is part of LibrePlan * * Copyright (C) 2009-2010 Fundación para o Fomento da Calidade Industrial e * Desenvolvemento Tecnolóxico de Galicia * Copyright (C) 2010-2011 Igalia, S.L. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ package org.zkoss.ganttz; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.ListIterator; import java.util.Map; import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.zkoss.ganttz.LeftTasksTreeRow.ILeftTasksTreeNavigator; import org.zkoss.ganttz.adapters.IDisabilityConfiguration; import org.zkoss.ganttz.data.Position; import org.zkoss.ganttz.data.Task; import org.zkoss.ganttz.data.TaskContainer; import org.zkoss.ganttz.data.TaskContainer.IExpandListener; import org.zkoss.ganttz.util.ComponentsFinder; import org.zkoss.ganttz.util.MutableTreeModel; import org.zkoss.zk.ui.Component; import org.zkoss.zk.ui.Executions; import org.zkoss.zk.ui.HtmlMacroComponent; import org.zkoss.zk.ui.event.Event; import org.zkoss.zk.ui.event.EventListener; import org.zkoss.zk.ui.event.OpenEvent; import org.zkoss.zul.Tree; import org.zkoss.zul.TreeModel; import org.zkoss.zul.Treecell; import org.zkoss.zul.Treeitem; import org.zkoss.zul.TreeitemRenderer; /** * Tree element to display tasks structure in the planning Gantt
* * @author Óscar González Fernández * @author Manuel Rego Casasnovas * @author Lorenzo Tilve Álvaro */ public class LeftTasksTree extends HtmlMacroComponent { private final class TaskBeanRenderer implements TreeitemRenderer { private Map expandListeners = new HashMap(); public void render(final Treeitem item, Object data) throws Exception { Task task = (Task) data; item.setOpen(isOpened(task)); if (task instanceof TaskContainer) { final TaskContainer container = (TaskContainer) task; IExpandListener expandListener = new IExpandListener() { @Override public void expandStateChanged(boolean isNowExpanded) { item.setOpen(isNowExpanded); } }; expandListeners.put(container, expandListener); container.addExpandListener(expandListener); } LeftTasksTreeRow leftTasksTreeRow = LeftTasksTreeRow.create( disabilityConfiguration, task, new TreeNavigator( tasksTreeModel, task), planner); if (task.isContainer()) { expandWhenOpened((TaskContainer) task, item); } Component row; if (disabilityConfiguration.isTreeEditable()) { row = Executions.getCurrent().createComponents( "~./ganttz/zul/leftTasksTreeRow.zul", item, null); } else { row = Executions.getCurrent().createComponents( "~./ganttz/zul/leftTasksTreeRowLabels.zul", item, null); } leftTasksTreeRow.doAfterCompose(row); List rowChildren = row.getChildren(); List treeCells = ComponentsFinder.findComponentsOfType( Treecell.class, rowChildren); detailsForBeans.put(task, leftTasksTreeRow); deferredFiller.isBeingRendered(task, item); } private void expandWhenOpened(final TaskContainer taskBean, Treeitem item) { item.addEventListener("onOpen", new EventListener() { @Override public void onEvent(Event event) { OpenEvent openEvent = (OpenEvent) event; taskBean.setExpanded(openEvent.isOpen()); } }); } } public boolean isOpened(Task task) { return task.isLeaf() || task.isExpanded(); } private static final class DetailsForBeans { private Map map = new HashMap(); private Set focusRequested = new HashSet(); public void put(Task task, LeftTasksTreeRow leftTasksTreeRow) { map.put(task, leftTasksTreeRow); if (focusRequested.contains(task)) { focusRequested.remove(task); leftTasksTreeRow.receiveFocus(); } } public void requestFocusFor(Task task) { focusRequested.add(task); } public LeftTasksTreeRow get(Task taskbean) { return map.get(taskbean); } } private DetailsForBeans detailsForBeans = new DetailsForBeans(); private final class TreeNavigator implements ILeftTasksTreeNavigator { private final int[] pathToNode; private final Task task; private TreeNavigator(TreeModel treemodel, Task task) { this.task = task; this.pathToNode = tasksTreeModel.getPath(tasksTreeModel.getRoot(), task); } @Override public LeftTasksTreeRow getAboveRow() { Task parent = getParent(pathToNode); int lastPosition = pathToNode[pathToNode.length - 1]; if (lastPosition != 0) { return getChild(parent, lastPosition - 1); } else if (tasksTreeModel.getRoot() != parent) { return getDetailFor(parent); } return null; } private LeftTasksTreeRow getChild(Task parent, int position) { Task child = tasksTreeModel.getChild(parent, position); return getDetailFor(child); } private LeftTasksTreeRow getDetailFor(Task child) { return detailsForBeans.get(child); } @Override public LeftTasksTreeRow getBelowRow() { if (isExpanded() && hasChildren()) { return getChild(task, 0); } for (ChildAndParent childAndParent : group(task, tasksTreeModel .getParents(task))) { if (childAndParent.childIsNotLast()) { return getDetailFor(childAndParent.getNextToChild()); } } // it's the last one, it has none below return null; } public List group(Task origin, List parents) { ArrayList result = new ArrayList(); Task child = origin; Task parent; ListIterator listIterator = parents.listIterator(); while (listIterator.hasNext()) { parent = listIterator.next(); result.add(new ChildAndParent(child, parent)); child = parent; } return result; } private class ChildAndParent { private final Task parent; private final Task child; private Integer positionOfChildCached; private ChildAndParent(Task child, Task parent) { this.parent = parent; this.child = child; } public Task getNextToChild() { return tasksTreeModel .getChild(parent, getPositionOfChild() + 1); } public boolean childIsNotLast() { return getPositionOfChild() < numberOfChildrenForParent() - 1; } private int numberOfChildrenForParent() { return tasksTreeModel.getChildCount(parent); } private int getPositionOfChild() { if (positionOfChildCached != null) { return positionOfChildCached; } int[] path = tasksTreeModel.getPath(parent, child); return positionOfChildCached = path[path.length - 1]; } } private boolean hasChildren() { return task.isContainer() && task.getTasks().size() > 0; } private boolean isExpanded() { return task.isContainer() && task.isExpanded(); } private Task getParent(int[] path) { Task current = tasksTreeModel.getRoot(); for (int i = 0; i < path.length - 1; i++) { current = tasksTreeModel.getChild(current, path[i]); } return current; } } /** * This class is a workaround for an issue with zk {@link Tree}. Once the * tree is created, adding a node with children is troublesome. Only the top * element is added to the tree, although the element has children. The Tree * discards the adding event for the children because the parent says it's * not loaded. This is the condition that is not satisfied:
* if(parent != null && (!(parent instanceof Treeitem) || ((Treeitem)parent).isLoaded())){
* This problem is present in zk 3.6.1 at least. * @author Óscar González Fernández * @see Tree#onTreeDataChange */ private class DeferredFiller { private Set pendingToAddChildren = new HashSet(); public void addParentOfPendingToAdd(Task parent) { pendingToAddChildren.add(parent); } public void isBeingRendered(final Task parent, final Treeitem item) { if (!pendingToAddChildren.contains(parent)) { return; } markLoaded(item); fillModel(parent, 0, parent.getTasks(), false); pendingToAddChildren.remove(parent); } private void markLoaded(Treeitem item) { try { Method method = getSetLoadedMethod(); method.invoke(item, true); } catch (Exception e) { throw new RuntimeException(e); } } private Method setLoadedMethod = null; private Method getSetLoadedMethod() { if (setLoadedMethod != null) { return setLoadedMethod; } try { Method method = Treeitem.class.getDeclaredMethod("setLoaded", Boolean.TYPE); method.setAccessible(true); return setLoadedMethod = method; } catch (Exception e) { throw new RuntimeException(e); } } } private static Log LOG = LogFactory.getLog(LeftTasksTree.class); private final DeferredFiller deferredFiller = new DeferredFiller(); private final List tasks; private MutableTreeModel tasksTreeModel; private Tree tasksTree; private CommandContextualized goingDownInLastArrowCommand; private final IDisabilityConfiguration disabilityConfiguration; private FilterAndParentExpandedPredicates predicate; private final List visibleTasks = new ArrayList(); private Planner planner; public LeftTasksTree(IDisabilityConfiguration disabilityConfiguration, Planner planner, FilterAndParentExpandedPredicates predicate) { this.disabilityConfiguration = disabilityConfiguration; this.tasks = planner.getTaskList().getAllTasks(); this.predicate = predicate; this.planner = planner; } private void fillModel(Collection tasks, boolean firstTime) { fillModel(this.tasksTreeModel.getRoot(), 0, tasks, firstTime); } private void fillModel(Task parent, Integer insertionPosition, Collection children, final boolean firstTime) { if (predicate.isFilterContainers()) { parent = this.tasksTreeModel.getRoot(); } if (firstTime) { for (Task node : children) { if (predicate.accpetsFilterPredicateAndContainers(node)) { if (!visibleTasks.contains(node)) { this.tasksTreeModel.add(parent, node); visibleTasks.add(node); } } else { if (visibleTasks.contains(node)) { this.tasksTreeModel.remove(node); visibleTasks.remove(node); } } if (node.isContainer()) { fillModel(node, 0, node.getTasks(), firstTime); } } } else { for (Task node : children) { if (node.isContainer()) { if (predicate.accpetsFilterPredicateAndContainers(node)) { if (!visibleTasks.contains(node)) { this.deferredFiller.addParentOfPendingToAdd(node); } } } } // the node must be added after, so the multistepTreeFiller is // ready for (Task node : children) { if (predicate.accpetsFilterPredicateAndContainers(node)) { if (!visibleTasks.contains(node)) { this.tasksTreeModel.add(parent, insertionPosition, Arrays.asList(node)); visibleTasks.add(node); } } else { if (visibleTasks.contains(node)) { this.tasksTreeModel.remove(node); removeTaskAndAllChildren(visibleTasks, node); } } if (node.isContainer()) { fillModel(node, 0, node.getTasks(), firstTime); } if (visibleTasks.contains(node)) { insertionPosition++; } } } } private void removeTaskAndAllChildren(List visibleTasks, Task task) { visibleTasks.remove(task); if (task.isContainer()) { for (Task node : task.getTasks()) { removeTaskAndAllChildren(visibleTasks, node); } } } public void taskRemoved(Task taskRemoved) { tasksTreeModel.remove(taskRemoved); } @Override public void afterCompose() { setClass("listdetails"); super.afterCompose(); tasksTree = (Tree) getFellow("tasksTree"); tasksTreeModel = MutableTreeModel.create(Task.class); fillModel(tasks, true); tasksTree.setModel(tasksTreeModel); tasksTree.setTreeitemRenderer(new TaskBeanRenderer()); } void addTask(Position position, Task task) { if (position.isAppendToTop()) { fillModel(Arrays.asList(task), false); detailsForBeans.requestFocusFor(task); } else { List toAdd = Arrays.asList(task); fillModel(position.getParent(), position.getInsertionPosition(), toAdd, false); } } public void addTasks(Position position, Collection newTasks) { Task root = tasksTreeModel.getRoot(); if (position.isAppendToTop()) { fillModel(root, tasksTreeModel.getChildCount(root), newTasks, false); } else if (position.isAtTop()) { fillModel(root, position.getInsertionPosition(), newTasks, false); } else { fillModel(position.getParent(), position.getInsertionPosition(), newTasks, false); } } public CommandContextualized getGoingDownInLastArrowCommand() { return goingDownInLastArrowCommand; } public void setGoingDownInLastArrowCommand( CommandContextualized goingDownInLastArrowCommand) { this.goingDownInLastArrowCommand = goingDownInLastArrowCommand; } public void setPredicate(FilterAndParentExpandedPredicates predicate) { this.predicate = predicate; visibleTasks.clear(); tasksTreeModel = MutableTreeModel.create(Task.class); fillModel(tasks, true); tasksTree.setModel(tasksTreeModel); } }