2009-04-14 17:51:03 +02:00
|
|
|
package org.zkoss.ganttz;
|
|
|
|
|
|
2009-07-24 17:48:46 +02:00
|
|
|
import java.lang.reflect.Method;
|
2009-07-13 14:01:42 +02:00
|
|
|
import java.util.ArrayList;
|
2009-07-24 17:48:46 +02:00
|
|
|
import java.util.Arrays;
|
2009-07-28 20:17:49 +02:00
|
|
|
import java.util.Collection;
|
2009-07-05 17:15:27 +02:00
|
|
|
import java.util.HashMap;
|
2009-07-10 20:40:20 +02:00
|
|
|
import java.util.HashSet;
|
2009-04-29 17:40:02 +02:00
|
|
|
import java.util.List;
|
2009-07-13 14:01:42 +02:00
|
|
|
import java.util.ListIterator;
|
2009-07-05 17:15:27 +02:00
|
|
|
import java.util.Map;
|
2009-07-10 20:40:20 +02:00
|
|
|
import java.util.Set;
|
2009-04-14 17:51:03 +02:00
|
|
|
|
|
|
|
|
import org.apache.commons.logging.Log;
|
|
|
|
|
import org.apache.commons.logging.LogFactory;
|
2009-07-20 15:37:39 +02:00
|
|
|
import org.zkoss.ganttz.LeftTasksTreeRow.ILeftTasksTreeNavigator;
|
2009-07-28 20:17:49 +02:00
|
|
|
import org.zkoss.ganttz.data.Position;
|
2009-07-20 18:00:11 +02:00
|
|
|
import org.zkoss.ganttz.data.Task;
|
|
|
|
|
import org.zkoss.ganttz.data.TaskContainer;
|
2009-07-10 20:40:19 +02:00
|
|
|
import org.zkoss.ganttz.util.MutableTreeModel;
|
2009-07-05 17:15:31 +02:00
|
|
|
import org.zkoss.zk.ui.Component;
|
|
|
|
|
import org.zkoss.zk.ui.Executions;
|
2009-04-14 17:51:03 +02:00
|
|
|
import org.zkoss.zk.ui.HtmlMacroComponent;
|
2009-07-05 17:15:30 +02:00
|
|
|
import org.zkoss.zk.ui.event.Event;
|
|
|
|
|
import org.zkoss.zk.ui.event.EventListener;
|
|
|
|
|
import org.zkoss.zk.ui.event.OpenEvent;
|
2009-07-05 17:15:26 +02:00
|
|
|
import org.zkoss.zul.Tree;
|
2009-07-05 17:15:27 +02:00
|
|
|
import org.zkoss.zul.TreeModel;
|
2009-07-08 13:03:43 +02:00
|
|
|
import org.zkoss.zul.Treecell;
|
2009-07-05 17:15:26 +02:00
|
|
|
import org.zkoss.zul.Treeitem;
|
|
|
|
|
import org.zkoss.zul.TreeitemRenderer;
|
2009-04-14 17:51:03 +02:00
|
|
|
|
2009-07-20 15:37:39 +02:00
|
|
|
public class LeftTasksTree extends HtmlMacroComponent {
|
2009-04-14 17:51:03 +02:00
|
|
|
|
2009-07-05 17:15:26 +02:00
|
|
|
private final class TaskBeanRenderer implements TreeitemRenderer {
|
|
|
|
|
public void render(Treeitem item, Object data) throws Exception {
|
2009-07-20 18:00:11 +02:00
|
|
|
Task task = (Task) data;
|
|
|
|
|
item.setOpen(isOpened(task));
|
2009-07-05 17:15:27 +02:00
|
|
|
final int[] path = tasksTreeModel.getPath(tasksTreeModel.getRoot(),
|
2009-07-20 18:00:11 +02:00
|
|
|
task);
|
2009-07-10 20:40:19 +02:00
|
|
|
String cssClass = "depth_" + path.length;
|
2009-07-20 18:00:11 +02:00
|
|
|
LeftTasksTreeRow leftTasksTreeRow = LeftTasksTreeRow.create(task,
|
|
|
|
|
new TreeNavigator(tasksTreeModel, task));
|
|
|
|
|
if (task.isContainer()) {
|
|
|
|
|
expandWhenOpened((TaskContainer) task, item);
|
2009-07-05 17:15:30 +02:00
|
|
|
}
|
2009-07-05 17:15:31 +02:00
|
|
|
Component row = Executions.getCurrent().createComponents(
|
2009-07-20 15:37:39 +02:00
|
|
|
"~./ganttz/zul/leftTasksTreeRow.zul", item, null);
|
|
|
|
|
leftTasksTreeRow.doAfterCompose(row);
|
2009-07-10 20:40:19 +02:00
|
|
|
List<Object> rowChildren = row.getChildren();
|
|
|
|
|
List<Treecell> treeCells = Planner.findComponentsOfType(
|
|
|
|
|
Treecell.class, rowChildren);
|
|
|
|
|
for (Treecell cell : treeCells) {
|
2009-07-08 13:03:43 +02:00
|
|
|
cell.setSclass(cssClass);
|
|
|
|
|
}
|
2009-07-20 18:00:11 +02:00
|
|
|
detailsForBeans.put(task, leftTasksTreeRow);
|
2009-07-24 17:48:46 +02:00
|
|
|
deferredFiller.isBeingRendered(task, item);
|
2009-07-05 17:15:26 +02:00
|
|
|
}
|
2009-07-05 17:15:30 +02:00
|
|
|
|
2009-07-20 18:00:11 +02:00
|
|
|
private void expandWhenOpened(final TaskContainer taskBean,
|
2009-07-05 17:15:30 +02:00
|
|
|
Treeitem item) {
|
|
|
|
|
item.addEventListener("onOpen", new EventListener() {
|
|
|
|
|
@Override
|
|
|
|
|
public void onEvent(Event event) throws Exception {
|
|
|
|
|
OpenEvent openEvent = (OpenEvent) event;
|
|
|
|
|
taskBean.setExpanded(openEvent.isOpen());
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2009-07-20 18:00:11 +02:00
|
|
|
public boolean isOpened(Task task) {
|
|
|
|
|
return task.isLeaf() || task.isExpanded();
|
2009-07-05 17:15:26 +02:00
|
|
|
}
|
|
|
|
|
|
2009-07-10 20:40:20 +02:00
|
|
|
private final class DetailsForBeans {
|
2009-07-20 18:00:11 +02:00
|
|
|
private Map<Task, LeftTasksTreeRow> map = new HashMap<Task, LeftTasksTreeRow>();
|
2009-07-10 20:40:20 +02:00
|
|
|
|
2009-07-20 18:00:11 +02:00
|
|
|
private Set<Task> focusRequested = new HashSet<Task>();
|
2009-07-10 20:40:20 +02:00
|
|
|
|
2009-07-20 18:00:11 +02:00
|
|
|
public void put(Task task, LeftTasksTreeRow leftTasksTreeRow) {
|
|
|
|
|
map.put(task, leftTasksTreeRow);
|
|
|
|
|
if (focusRequested.contains(task)) {
|
|
|
|
|
focusRequested.remove(task);
|
2009-07-20 15:37:39 +02:00
|
|
|
leftTasksTreeRow.receiveFocus();
|
2009-07-10 20:40:20 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2009-07-20 18:00:11 +02:00
|
|
|
public void requestFocusFor(Task task) {
|
|
|
|
|
focusRequested.add(task);
|
2009-07-10 20:40:20 +02:00
|
|
|
}
|
|
|
|
|
|
2009-07-20 18:00:11 +02:00
|
|
|
public LeftTasksTreeRow get(Task taskbean) {
|
2009-07-10 20:40:20 +02:00
|
|
|
return map.get(taskbean);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private DetailsForBeans detailsForBeans = new DetailsForBeans();
|
2009-07-05 17:15:27 +02:00
|
|
|
|
2009-07-20 15:37:39 +02:00
|
|
|
private final class TreeNavigator implements ILeftTasksTreeNavigator {
|
2009-07-05 17:15:27 +02:00
|
|
|
private final int[] pathToNode;
|
2009-07-20 18:00:11 +02:00
|
|
|
private final Task task;
|
2009-07-05 17:15:27 +02:00
|
|
|
|
2009-07-20 18:00:11 +02:00
|
|
|
private TreeNavigator(TreeModel treemodel, Task task) {
|
2009-07-13 14:01:40 +02:00
|
|
|
this.task = task;
|
|
|
|
|
this.pathToNode = tasksTreeModel.getPath(tasksTreeModel.getRoot(),
|
|
|
|
|
task);
|
2009-07-05 17:15:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
2009-07-20 15:37:39 +02:00
|
|
|
public LeftTasksTreeRow getAboveRow() {
|
2009-07-20 18:00:11 +02:00
|
|
|
Task parent = getParent(pathToNode);
|
2009-07-05 17:15:27 +02:00
|
|
|
int lastPosition = pathToNode[pathToNode.length - 1];
|
|
|
|
|
if (lastPosition != 0) {
|
|
|
|
|
return getChild(parent, lastPosition - 1);
|
2009-07-10 20:40:19 +02:00
|
|
|
} else if (tasksTreeModel.getRoot() != parent) {
|
2009-07-13 14:01:42 +02:00
|
|
|
return getDetailFor(parent);
|
2009-07-05 17:15:27 +02:00
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2009-07-20 18:00:11 +02:00
|
|
|
private LeftTasksTreeRow getChild(Task parent, int position) {
|
|
|
|
|
Task child = tasksTreeModel.getChild(parent, position);
|
2009-07-13 14:01:42 +02:00
|
|
|
return getDetailFor(child);
|
|
|
|
|
}
|
|
|
|
|
|
2009-07-20 18:00:11 +02:00
|
|
|
private LeftTasksTreeRow getDetailFor(Task child) {
|
2009-07-10 20:40:19 +02:00
|
|
|
return detailsForBeans.get(child);
|
2009-07-05 17:15:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
2009-07-20 15:37:39 +02:00
|
|
|
public LeftTasksTreeRow getBelowRow() {
|
2009-07-13 14:01:40 +02:00
|
|
|
if (isExpanded() && hasChildren()) {
|
|
|
|
|
return getChild(task, 0);
|
2009-07-05 17:15:27 +02:00
|
|
|
}
|
2009-07-13 14:01:42 +02:00
|
|
|
for (ChildAndParent childAndParent : group(task, tasksTreeModel
|
|
|
|
|
.getParents(task))) {
|
|
|
|
|
if (childAndParent.childIsNotLast()) {
|
|
|
|
|
return getDetailFor(childAndParent.getNextToChild());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// it's the last one, it has none below
|
2009-07-05 17:15:27 +02:00
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2009-07-24 17:48:46 +02:00
|
|
|
public List<ChildAndParent> group(Task origin, List<Task> parents) {
|
2009-07-13 14:01:42 +02:00
|
|
|
ArrayList<ChildAndParent> result = new ArrayList<ChildAndParent>();
|
2009-07-20 18:00:11 +02:00
|
|
|
Task child = origin;
|
|
|
|
|
Task parent;
|
|
|
|
|
ListIterator<Task> listIterator = parents.listIterator();
|
2009-07-13 14:01:42 +02:00
|
|
|
while (listIterator.hasNext()) {
|
|
|
|
|
parent = listIterator.next();
|
2009-07-16 10:40:55 +02:00
|
|
|
result.add(new ChildAndParent(child, parent));
|
2009-07-13 14:01:42 +02:00
|
|
|
child = parent;
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private class ChildAndParent {
|
2009-07-20 18:00:11 +02:00
|
|
|
private final Task parent;
|
2009-07-13 14:01:42 +02:00
|
|
|
|
2009-07-20 18:00:11 +02:00
|
|
|
private final Task child;
|
2009-07-13 14:01:42 +02:00
|
|
|
|
|
|
|
|
private Integer positionOfChildCached;
|
|
|
|
|
|
2009-07-20 18:00:11 +02:00
|
|
|
private ChildAndParent(Task child, Task parent) {
|
2009-07-13 14:01:42 +02:00
|
|
|
this.parent = parent;
|
|
|
|
|
this.child = child;
|
|
|
|
|
}
|
|
|
|
|
|
2009-07-20 18:00:11 +02:00
|
|
|
public Task getNextToChild() {
|
2009-07-13 14:01:42 +02:00
|
|
|
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];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2009-07-13 14:01:40 +02:00
|
|
|
private boolean hasChildren() {
|
2009-07-16 10:40:55 +02:00
|
|
|
return task.isContainer() && task.getTasks().size() > 0;
|
2009-07-13 14:01:40 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private boolean isExpanded() {
|
2009-07-16 10:40:55 +02:00
|
|
|
return task.isContainer() && task.isExpanded();
|
2009-07-13 14:01:40 +02:00
|
|
|
}
|
|
|
|
|
|
2009-07-20 18:00:11 +02:00
|
|
|
private Task getParent(int[] path) {
|
|
|
|
|
Task current = tasksTreeModel.getRoot();
|
2009-07-05 17:15:27 +02:00
|
|
|
for (int i = 0; i < path.length - 1; i++) {
|
2009-07-10 20:40:19 +02:00
|
|
|
current = tasksTreeModel.getChild(current, path[i]);
|
2009-07-05 17:15:27 +02:00
|
|
|
}
|
2009-07-10 20:40:19 +02:00
|
|
|
return current;
|
2009-07-05 17:15:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2009-07-24 17:48:46 +02:00
|
|
|
/**
|
|
|
|
|
* This class is a workaround for an issue with zk {@link Tree}. Once the
|
|
|
|
|
* tree is created, a node with more children can't be added. 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);
|
2009-07-28 20:17:49 +02:00
|
|
|
fillModel(parent, 0, parent.getTasks(), false);
|
2009-07-24 17:48:46 +02:00
|
|
|
pendingToAddChildren.remove(parent);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void markLoaded(Treeitem item) {
|
|
|
|
|
try {
|
|
|
|
|
Method method = getSetLoadedMethod();
|
|
|
|
|
method.invoke(item, true);
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
throw new RuntimeException(e);
|
|
|
|
|
}
|
|
|
|
|
}
|
2009-07-28 20:17:49 +02:00
|
|
|
|
2009-07-24 17:48:46 +02:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2009-07-20 15:37:39 +02:00
|
|
|
private static Log LOG = LogFactory.getLog(LeftTasksTree.class);
|
2009-06-15 21:48:54 +02:00
|
|
|
|
2009-07-24 17:48:46 +02:00
|
|
|
private final DeferredFiller deferredFiller = new DeferredFiller();
|
|
|
|
|
|
2009-07-20 18:00:11 +02:00
|
|
|
private final List<Task> tasks;
|
2009-06-15 21:48:54 +02:00
|
|
|
|
2009-07-20 18:00:11 +02:00
|
|
|
private MutableTreeModel<Task> tasksTreeModel;
|
2009-07-05 17:15:26 +02:00
|
|
|
|
|
|
|
|
private Tree tasksTree;
|
|
|
|
|
|
2009-07-20 15:37:42 +02:00
|
|
|
private CommandContextualized<?> goingDownInLastArrowCommand;
|
|
|
|
|
|
2009-07-20 18:00:11 +02:00
|
|
|
public LeftTasksTree(List<Task> tasks) {
|
|
|
|
|
this.tasks = tasks;
|
2009-04-14 17:51:03 +02:00
|
|
|
}
|
|
|
|
|
|
2009-07-28 20:17:49 +02:00
|
|
|
private void fillModel(Collection<? extends Task> tasks, boolean firstTime) {
|
|
|
|
|
fillModel(this.tasksTreeModel.getRoot(), 0, tasks, firstTime);
|
2009-04-29 17:40:02 +02:00
|
|
|
}
|
|
|
|
|
|
2009-07-28 20:17:49 +02:00
|
|
|
private void fillModel(Task parent, Integer insertionPosition,
|
|
|
|
|
Collection<? extends Task> children, final boolean firstTime) {
|
|
|
|
|
if (firstTime) {
|
|
|
|
|
this.tasksTreeModel.add(parent, insertionPosition, children);
|
|
|
|
|
for (Task node : children) {
|
2009-07-24 17:48:46 +02:00
|
|
|
if (node.isContainer()) {
|
2009-07-28 20:17:49 +02:00
|
|
|
fillModel(node, 0, node.getTasks(), firstTime);
|
2009-07-24 17:48:46 +02:00
|
|
|
}
|
2009-07-28 20:17:49 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
for (Task node : children) {
|
2009-07-24 17:48:46 +02:00
|
|
|
if (node.isContainer()) {
|
|
|
|
|
this.deferredFiller.addParentOfPendingToAdd(node);
|
|
|
|
|
}
|
2009-07-10 20:40:19 +02:00
|
|
|
}
|
2009-07-28 20:17:49 +02:00
|
|
|
// the node must be added after, so the multistepTreeFiller is
|
|
|
|
|
// ready
|
|
|
|
|
this.tasksTreeModel.add(parent, insertionPosition, children);
|
2009-07-10 20:40:19 +02:00
|
|
|
}
|
2009-07-05 17:15:26 +02:00
|
|
|
}
|
|
|
|
|
|
2009-07-20 18:00:11 +02:00
|
|
|
public void taskRemoved(Task taskRemoved) {
|
2009-07-12 21:31:27 +02:00
|
|
|
tasksTreeModel.remove(taskRemoved);
|
2009-04-29 17:40:02 +02:00
|
|
|
}
|
|
|
|
|
|
2009-06-15 21:48:54 +02:00
|
|
|
@Override
|
|
|
|
|
public void afterCompose() {
|
2009-06-29 11:43:23 +02:00
|
|
|
setClass("listdetails");
|
2009-06-15 21:48:54 +02:00
|
|
|
super.afterCompose();
|
2009-07-05 17:15:26 +02:00
|
|
|
tasksTree = (Tree) getFellow("tasksTree");
|
2009-07-20 18:00:11 +02:00
|
|
|
tasksTreeModel = MutableTreeModel.create(Task.class);
|
2009-07-24 17:48:46 +02:00
|
|
|
fillModel(tasks, true);
|
2009-07-05 17:15:26 +02:00
|
|
|
tasksTree.setModel(tasksTreeModel);
|
|
|
|
|
tasksTree.setTreeitemRenderer(new TaskBeanRenderer());
|
2009-04-14 17:51:03 +02:00
|
|
|
}
|
|
|
|
|
|
2009-07-28 20:17:49 +02:00
|
|
|
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) {
|
2009-07-28 20:17:49 +02:00
|
|
|
Task root = tasksTreeModel.getRoot();
|
2009-07-28 20:17:49 +02:00
|
|
|
if (position.isAppendToTop()) {
|
2009-07-28 20:17:49 +02:00
|
|
|
fillModel(root, tasksTreeModel.getChildCount(root), newTasks, false);
|
2009-07-28 20:17:49 +02:00
|
|
|
} else if (position.isAtTop()) {
|
2009-07-28 20:17:49 +02:00
|
|
|
fillModel(root,
|
2009-07-28 20:17:49 +02:00
|
|
|
position.getInsertionPosition(), newTasks, false);
|
|
|
|
|
} else {
|
|
|
|
|
fillModel(position.getParent(), position.getInsertionPosition(),
|
|
|
|
|
newTasks, false);
|
|
|
|
|
}
|
2009-06-30 20:31:08 +02:00
|
|
|
}
|
|
|
|
|
|
2009-07-20 15:37:42 +02:00
|
|
|
public CommandContextualized<?> getGoingDownInLastArrowCommand() {
|
|
|
|
|
return goingDownInLastArrowCommand;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void setGoingDownInLastArrowCommand(
|
|
|
|
|
CommandContextualized<?> goingDownInLastArrowCommand) {
|
|
|
|
|
this.goingDownInLastArrowCommand = goingDownInLastArrowCommand;
|
|
|
|
|
}
|
|
|
|
|
|
2009-07-01 17:42:47 +02:00
|
|
|
}
|