/*
* 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.libreplan.web.tree;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.libreplan.business.trees.ITreeNode;
import org.libreplan.business.trees.ITreeParentNode;
import org.zkoss.ganttz.util.MutableTreeModel;
import org.zkoss.ganttz.util.MutableTreeModel.IChildrenExtractor;
import org.zkoss.zul.TreeModel;
/**
* @author Óscar González Fernández <ogonzalez@igalia.com>
*/
public abstract class EntitiesTree<T extends ITreeNode<T>> {
private MutableTreeModel<T> tree;
protected EntitiesTree(Class<T> type, T root) {
tree = createTreeFrom(type, root);
}
protected EntitiesTree(Class<T> type, T root, List<T> elements) {
tree = createFilteredTreeFrom(type, root, elements);
}
protected static <T extends ITreeNode<T>> MutableTreeModel<T> createTreeFrom(Class<T> type, T tree) {
List<T> children = tree.getChildren();
return createTreeFrom(type, tree, children);
}
protected static <T extends ITreeNode<T>> MutableTreeModel<T> createTreeFrom(Class<T> type,
T tree,
List<T> children) {
MutableTreeModel<T> treeModel = MutableTreeModel.create(type, tree);
T parent = treeModel.getRoot();
treeModel.add(parent, children, EntitiesTree.createChildrenExtractor());
return treeModel;
}
private static <T extends ITreeNode<T>> IChildrenExtractor<T> createChildrenExtractor() {
return parent -> parent.getChildren();
}
private static <T extends ITreeNode<T>> MutableTreeModel<T> createFilteredTreeFrom(
Class<T> type, T tree, List<T> elements) {
MutableTreeModel<T> treeModel = MutableTreeModel.create(type, tree);
T parent = treeModel.getRoot();
addFilteredChildren(treeModel, parent, elements);
return treeModel;
}
private static <T extends ITreeNode<T>> void addFilteredChildren(
MutableTreeModel<T> treeModel, T parent, List<T> children) {
for (T each : children) {
if ((each.getParent() != null) && (each.getParent().equals(parent))) {
treeModel.add(parent, each);
addFilteredChildren(treeModel, each, children);
}
}
}
public TreeModel asTree() {
return tree;
}
public T getRoot() {
return tree.getRoot();
}
public void addElement() {
addElementAtImpl(tree.getRoot());
}
public void addElement(String name, int hours) {
addElementAtImpl(tree.getRoot(), name, hours);
}
public void addElementAt(T node) {
addElementAtImpl(node);
}
public T addElementAt(T node, String name, int hours) {
return addElementAtImpl(node, name, hours);
}
protected abstract T createNewElement();
protected abstract T createNewElement(String name, int hours);
private void addElementAtImpl(T parent) {
addOrderElementAt(parent, createNewElement());
}
private T addElementAtImpl(T parent, String name, int hours) {
T newElement = createNewElement(name, hours);
addOrderElementAt(parent, newElement);
return newElement;
}
private void addToTree(ITreeNode<T> parentNode, ITreeNode<T> elementToAdd) {
tree.add(parentNode.getThis(), Collections.singletonList(elementToAdd.getThis()), childrenExtractor());
}
private void addToTree(ITreeNode<T> parentNode,
int position,
ITreeNode<T> elementToAdd) {
List<T> children = Collections.singletonList(elementToAdd.getThis());
tree.add(parentNode.getThis(), position, children, childrenExtractor());
}
private void addOrderElementAt(ITreeNode<T> parent, ITreeNode<T> element) {
ITreeParentNode<T> container = turnIntoContainerIfNeeded(parent);
container.add(element.getThis());
addToTree(container.getThis(), element);
}
private void addOrderElementAt(ITreeNode<T> destinationNode,
ITreeNode<T> elementToAdd,
int position) {
ITreeParentNode<T> container = turnIntoContainerIfNeeded(destinationNode);
container.add(position, elementToAdd.getThis());
addToTree(container, position, elementToAdd);
if (!tree.isRoot(container.getThis())) {
// The destination node might have data that depends on its children, so it should be redrawn
tree.sendContentsChangedEventFor(container.getThis());
}
}
private ITreeParentNode<T> turnIntoContainerIfNeeded(ITreeNode<T> selectedForTurningIntoContainer) {
if (selectedForTurningIntoContainer instanceof ITreeParentNode) {
return (ITreeParentNode<T>) selectedForTurningIntoContainer;
}
ITreeParentNode<T> parentContainer = getParent(selectedForTurningIntoContainer);
ITreeParentNode<T> asContainer = selectedForTurningIntoContainer.toContainer();
parentContainer.replace(selectedForTurningIntoContainer.getThis(), asContainer.getThis());
if (!selectedForTurningIntoContainer.isEmptyLeaf()) {
asContainer.add(selectedForTurningIntoContainer.getThis());
}
tree.replace(selectedForTurningIntoContainer.getThis(), asContainer.getThis(), childrenExtractor());
return asContainer;
}
private ITreeParentNode<T> getParent(ITreeNode<T> node) {
return (ITreeParentNode<T>) tree.getParent(node.getThis());
}
public List<T> getParents(T node) {
return tree.getParents(node);
}
public void indent(T nodeToIndent) {
T parentOfSelected = tree.getParent(nodeToIndent);
int position = getChildren(parentOfSelected).indexOf(nodeToIndent);
if (position == 0) {
return;
}
T destination = getChildren(parentOfSelected).get(position - 1);
move(nodeToIndent, destination, getChildren(destination).size());
}
private List<T> getChildren(T node) {
List<T> result = new ArrayList<>();
final int childCount = tree.getChildCount(node);
for (int i = 0; i < childCount; i++) {
result.add(tree.getChild(node, i));
}
return result;
}
public void unindent(T nodeToUnindent) {
T parent = tree.getParent(nodeToUnindent);
if (tree.isRoot(parent)) {
return;
}
// If the last child of parent in unindented parent is replaced by
// its representation as leaf so no longer would be found.
// Keeping track of the position
int[] parentNodePath = tree.getPath(getRoot(), parent);
T destination = tree.getParent(parent);
move(nodeToUnindent, destination, getChildren(destination).indexOf(parent) + 1);
if (!tree.contains(parent)) {
parent = tree.findObjectAt(parentNodePath);
}
if (!tree.isRoot(parent)) {
tree.sendContentsChangedEventFor(parent);
}
}
private class WithPosition {
int position;
T element;
private WithPosition(int position, T element) {
this.position = position;
this.element = element;
}
}
public void addNewlyAddedChildrenOf(ITreeParentNode<T> parent) {
List<T> treeChildren = getTreeChildren(parent);
List<T> currentChildren = parent.getChildren();
if (!currentChildren.containsAll(treeChildren)) {
throw new IllegalStateException("some children were removed. Can't add new tree children");
}
int i = 0;
List<WithPosition> addings = new ArrayList<>();
for (T each : currentChildren) {
if (!treeChildren.contains(each)) {
addings.add(new WithPosition(i, each));
}
i++;
}
for (WithPosition each : addings) {
tree.add(parent.getThis(), each.position, Collections.singletonList(each.element), childrenExtractor());
}
}
private IChildrenExtractor<T> childrenExtractor() {
return EntitiesTree.createChildrenExtractor();
}
private List<T> getTreeChildren(ITreeParentNode<T> parent) {
List<T> result = new ArrayList<>();
int childCount = tree.getChildCount(parent);
for (int i = 0; i < childCount; i++) {
result.add(tree.getChild(parent, i));
}
return result;
}
public void move(T toBeMoved, T destination) {
move(toBeMoved, destination, getChildren(destination).size());
}
public void moveToRoot(T toBeMoved) {
move(toBeMoved, tree.getRoot(), 0);
}
private void move(T toBeMoved, T destination, int position) {
if (getChildren(destination).contains(toBeMoved)) {
return; // It's already moved
}
if (isGreatInHierarchy(toBeMoved, destination)) {
return;
}
removeNode(toBeMoved);
addOrderElementAt(destination, toBeMoved, position);
}
private boolean isGreatInHierarchy(T parent, T child) {
return find(child, getChildren(parent));
}
private boolean find(T child, List<T> children) {
if (children.indexOf(child) >= 0) {
return true;
}
for (T each : children) {
if (find(child, getChildren(each))) {
return true;
}
}
return false;
}
public void up(T node) {
ITreeParentNode<T> parent = getParent(node);
parent.up(node);
tree.up(node);
}
public void down(T node) {
ITreeParentNode<T> parent = getParent(node);
parent.down(node);
tree.down(node);
}
public void removeNode(T element) {
if (element == tree.getRoot()) {
return;
}
ITreeParentNode<T> parent = getParent(element);
parent.remove(element);
tree.remove(element);
// If removed node was the last one and its parent is not the root node
if (!tree.isRoot(parent.getThis()) && tree.getChildCount(parent) == 0) {
T asLeaf = parent.toLeaf();
ITreeParentNode<T> parentContainer = getParent(parent.getThis());
parentContainer.replace(parent.getThis(), asLeaf);
tree.replace(parent.getThis(), asLeaf);
}
}
public int[] getPath(T element) {
return tree.getPath(tree.getRoot(), element);
}
}