TASKPM/ganttzk/src/main/java/org/zkoss/ganttz/LeftTasksTree.java
Lorenzo Tilve Álvaro 379827f645 Changed Textbox automatic width calculation in LeftTasksTree
As the component is now allowed to use all remaining space, it's not
needed any longer to calculate it based on the tree depth.

FEA: ItEr75S04BugFixing
2011-11-16 15:56:54 +01:00

478 lines
17 KiB
Java

/*
* 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 <http://www.gnu.org/licenses/>.
*/
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 <br />
*
* @author Óscar González Fernández <ogonzalez@igalia.com>
* @author Manuel Rego Casasnovas <mrego@igalia.com>
* @author Lorenzo Tilve Álvaro <ltilve@igalia.com>
*/
public class LeftTasksTree extends HtmlMacroComponent {
private final class TaskBeanRenderer implements TreeitemRenderer {
private Map<TaskContainer, IExpandListener> expandListeners = new HashMap<TaskContainer, IExpandListener>();
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<Object> rowChildren = row.getChildren();
List<Treecell> 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<Task, LeftTasksTreeRow> map = new HashMap<Task, LeftTasksTreeRow>();
private Set<Task> focusRequested = new HashSet<Task>();
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<ChildAndParent> group(Task origin, List<Task> parents) {
ArrayList<ChildAndParent> result = new ArrayList<ChildAndParent>();
Task child = origin;
Task parent;
ListIterator<Task> 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:<br />
* <code>if(parent != null &&
(!(parent instanceof Treeitem) || ((Treeitem)parent).isLoaded())){</code><br />
* This problem is present in zk 3.6.1 at least.
* @author Óscar González Fernández <ogonzalez@igalia.com>
* @see Tree#onTreeDataChange
*/
private class DeferredFiller {
private Set<Task> pendingToAddChildren = new HashSet<Task>();
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<Task> tasks;
private MutableTreeModel<Task> tasksTreeModel;
private Tree tasksTree;
private CommandContextualized<?> goingDownInLastArrowCommand;
private final IDisabilityConfiguration disabilityConfiguration;
private FilterAndParentExpandedPredicates predicate;
private final List<Task> visibleTasks = new ArrayList<Task>();
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<? extends Task> tasks, boolean firstTime) {
fillModel(this.tasksTreeModel.getRoot(), 0, tasks, firstTime);
}
private void fillModel(Task parent, Integer insertionPosition,
Collection<? extends Task> 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<Task> 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<Task> toAdd = Arrays.asList(task);
fillModel(position.getParent(), position.getInsertionPosition(),
toAdd, false);
}
}
public void addTasks(Position position, Collection<? extends Task> 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);
}
}