/*
* 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 static org.zkoss.ganttz.i18n.I18nHelper._;
import org.apache.commons.lang3.math.Fraction;
import org.joda.time.LocalDate;
import org.zkoss.ganttz.adapters.IDisabilityConfiguration;
import org.zkoss.ganttz.data.Dependency;
import org.zkoss.ganttz.data.DependencyType;
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.timetracker.TimeTracker;
import org.zkoss.ganttz.timetracker.TimeTrackerComponent;
import org.zkoss.ganttz.timetracker.zoom.IZoomLevelChangedListener;
import org.zkoss.ganttz.timetracker.zoom.ZoomLevel;
import org.zkoss.ganttz.util.Interval;
import org.zkoss.ganttz.util.MenuBuilder;
import org.zkoss.zk.ui.ext.AfterCompose;
import org.zkoss.zul.Menupopup;
import org.zkoss.zul.impl.XulElement;
import java.util.HashSet;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.HashMap;
import java.util.Set;
import java.util.Collection;
import java.util.Collections;
/**
* Component to show the list of task in the planner.
*
* @author Javier Moran Rua <jmoran@igalia.com>
*/
public class TaskList extends XulElement implements AfterCompose {
private transient IZoomLevelChangedListener zoomLevelChangedListener;
private List<Task> currentTotalTasks;
private final CommandOnTaskContextualized<?> doubleClickCommand;
private final List<? extends CommandOnTaskContextualized<?>> commandsOnTasksContextualized;
private final FunctionalityExposedForExtensions<?> context;
private final IDisabilityConfiguration disabilityConfiguration;
private FilterAndParentExpandedPredicates predicate;
private Set<Task> visibleTasks = new HashSet<>();
private Map<Task, TaskComponent> taskComponentByTask;
private Map<TaskContainer, IExpandListener> autoRemovedListers = new WeakHashMap<>();
private Map<TaskComponent, Menupopup> contextMenus = new HashMap<>();
public TaskList(
FunctionalityExposedForExtensions<?> context,
CommandOnTaskContextualized<?> doubleClickCommand,
List<Task> tasks,
List<? extends CommandOnTaskContextualized<?>> commandsOnTasksContextualized,
IDisabilityConfiguration disabilityConfiguration,
FilterAndParentExpandedPredicates predicate) {
this.context = context;
this.doubleClickCommand = doubleClickCommand;
this.currentTotalTasks = new ArrayList<>(tasks);
this.commandsOnTasksContextualized = commandsOnTasksContextualized;
this.disabilityConfiguration = disabilityConfiguration;
this.predicate = predicate;
}
public void updateCompletion(String progressType) {
for (TaskComponent task: getTaskComponents()) {
task.updateCompletion(progressType);
task.updateCompletionReportedHours();
task.updateTooltipText(progressType);
}
}
public List<Task> getAllTasks() {
return new ArrayList<>(currentTotalTasks);
}
public static TaskList createFor(
FunctionalityExposedForExtensions<?> context,
CommandOnTaskContextualized<?> doubleClickCommand,
List<? extends CommandOnTaskContextualized<?>> commandsOnTasksContextualized,
IDisabilityConfiguration disabilityConfiguration,
FilterAndParentExpandedPredicates predicate) {
return new TaskList(
context,
doubleClickCommand,
context.getDiagramGraph().getTopLevelTasks(),
commandsOnTasksContextualized,
disabilityConfiguration,
predicate);
}
public List<DependencyComponent> asDependencyComponents(Collection<? extends Dependency> dependencies) {
List<DependencyComponent> result = new ArrayList<>();
for (Dependency dependency : dependencies) {
result.add(new DependencyComponent(
taskComponentByTask.get(dependency.getSource()),
taskComponentByTask.get(dependency.getDestination()), dependency));
}
return result;
}
public DependencyComponent asDependencyComponent(Dependency dependency) {
return asDependencyComponents(Collections.singletonList(dependency)).get(0);
}
private synchronized void addTaskComponent(
TaskRow beforeThis, final TaskComponent taskComponent, boolean relocate) {
insertBefore(taskComponent.getRow(), beforeThis);
addContextMenu(taskComponent);
addListenerForTaskComponentEditForm(taskComponent);
taskComponent.afterCompose();
if ( relocate ) {
getGanttPanel().adjustZoomColumnsHeight();
}
}
public void addTasks(Position position, Collection<? extends Task> newTasks) {
createAndPublishComponentsIfNeeded(newTasks);
if ( position.isAppendToTop() ) {
currentTotalTasks.addAll(newTasks);
} else if ( position.isAtTop() ) {
final int insertionPosition = position.getInsertionPosition();
currentTotalTasks.addAll(insertionPosition, newTasks);
}
// If the position is children of some already existent task when
// reloading it will be added if the predicate tells so
reload(true);
}
public TaskComponent find(Task task) {
List<TaskComponent> taskComponents = getTaskComponents();
for (TaskComponent taskComponent : taskComponents) {
if ( taskComponent.getTask().equals(task) ) {
return taskComponent;
}
}
return null;
}
private void addListenerForTaskComponentEditForm(final TaskComponent taskComponent) {
if ( doubleClickCommand == null ) {
return;
}
taskComponent.addEventListener("onDoubleClick", event -> doubleClickCommand.doAction(taskComponent));
}
private void addContextMenu(final TaskComponent taskComponent) {
taskComponent.setContext(getContextMenuFor(taskComponent));
}
private TimeTrackerComponent getTimeTrackerComponent() {
return getGanttPanel().getTimeTrackerComponent();
}
IDatesMapper getMapper() {
return getTimeTracker().getMapper();
}
private TimeTracker getTimeTracker() {
return getTimeTrackerComponent().getTimeTracker();
}
protected List<TaskComponent> getTaskComponents() {
ArrayList<TaskComponent> result = new ArrayList<>();
for (Object child : getChildren()) {
if ( child instanceof TaskRow ) {
TaskRow row = (TaskRow) child;
result.add(row.getChild());
}
}
return result;
}
public int getTasksNumber() {
return getTaskComponents().size();
}
@Override
public void afterCompose() {
publishOriginalTasksAsComponents();
registerZoomLevelChangedListener();
reload(false);
}
private void publishOriginalTasksAsComponents() {
taskComponentByTask = new HashMap<>();
createAndPublishComponentsIfNeeded(currentTotalTasks);
}
private List<TaskComponent> createAndPublishComponentsIfNeeded(Collection<? extends Task> newTasks) {
if ( predicate.isFilterContainers() ) {
List<Task> taskLeafs = new ArrayList<>();
for (Task task : newTasks) {
taskLeafs.addAll(task.getAllTaskLeafs());
}
newTasks = taskLeafs;
}
List<TaskComponent> result = new ArrayList<>();
for (Task task : newTasks) {
TaskComponent taskComponent = taskComponentByTask.get(task);
if ( taskComponent == null ) {
taskComponent = TaskComponent.asTaskComponent(task, disabilityConfiguration);
taskComponent.publishTaskComponents(taskComponentByTask);
}
if ( task.isContainer() ) {
addExpandListenerTo((TaskContainer) task);
}
result.add(taskComponent);
}
return result;
}
private void addExpandListenerTo(TaskContainer container) {
if ( autoRemovedListers.containsKey(container) ) {
return;
}
IExpandListener expandListener = (isNowExpanded -> reload(true));
container.addExpandListener(expandListener);
autoRemovedListers.put(container, expandListener);
}
private void registerZoomLevelChangedListener() {
if ( zoomLevelChangedListener == null ) {
/* Do not replace it with lambda */
zoomLevelChangedListener = new IZoomLevelChangedListener() {
@Override
public void zoomLevelChanged(ZoomLevel detailLevel) {
for (TaskComponent taskComponent : getTaskComponents()) {
taskComponent.zoomChanged();
}
adjustZoomPositionScroll();
}
};
getTimeTracker().addZoomListener(zoomLevelChangedListener);
}
}
public LocalDate toDate(int pixels, Fraction pixelsPerDay, Interval interval) {
int daysInto = Fraction.getFraction(pixels, 1).divideBy(pixelsPerDay).intValue();
return interval.getStart().plusDays(daysInto);
}
private Menupopup getContextMenuFor(TaskComponent taskComponent) {
if ( contextMenus.get(taskComponent) == null ) {
MenuBuilder<TaskComponent> menuBuilder = MenuBuilder.on(getPage(), getTaskComponents());
if ( disabilityConfiguration.isAddingDependenciesEnabled() ) {
menuBuilder.item(
_("Add Dependency"), "/common/img/ico_dependency.png",
(chosen, event) -> chosen.addDependency());
}
for (CommandOnTaskContextualized<?> command : commandsOnTasksContextualized) {
if ( command.accepts(taskComponent) ) {
menuBuilder.item(command.getName(), command.getIcon(), command.toItemAction());
}
}
Menupopup result = menuBuilder.createWithoutSettingContext();
contextMenus.put(taskComponent, result);
return result;
}
return contextMenus.get(taskComponent);
}
GanttPanel getGanttPanel() {
return (GanttPanel) getParent();
}
private void adjustZoomPositionScroll() {
getTimeTrackerComponent().movePositionScroll();
}
public void redrawDependencies() {
getGanttPanel().getDependencyList().redrawDependencies();
}
public void remove(Task task) {
currentTotalTasks.remove(task);
for (TaskComponent taskComponent : getTaskComponents()) {
if ( taskComponent.getTask().equals(task) ) {
taskComponent.remove();
return;
}
}
}
public void addDependency(TaskComponent source, TaskComponent destination) {
context.addDependency(new Dependency(source.getTask(), destination.getTask(), DependencyType.END_START));
}
public IDisabilityConfiguration getDisabilityConfiguration() {
return disabilityConfiguration;
}
private void reload(boolean relocate) {
ArrayList<Task> tasksPendingToAdd = new ArrayList<>();
reload(currentTotalTasks, tasksPendingToAdd, relocate);
addPendingTasks(tasksPendingToAdd, null, relocate);
getGanttPanel().getDependencyList().redrawDependencies();
}
private void reload(List<Task> tasks, List<Task> tasksPendingToAdd, boolean relocate) {
for (Task task : tasks) {
if ( visibleTasks.contains(task) ) {
addPendingTasks(tasksPendingToAdd, rowFor(task), relocate);
}
final boolean isShown = visibleTasks.contains(task);
if ( predicate.accepts(task) != isShown ) {
if ( isShown ) {
makeDisappear(task);
} else {
tasksPendingToAdd.add(task);
}
}
if ( task instanceof TaskContainer ) {
reload(task.getTasks(), tasksPendingToAdd, relocate);
}
}
}
private void makeDisappear(Task task) {
TaskComponent taskComponent = find(task);
removeChild(taskComponent.getRow());
visibleTasks.remove(task);
task.setVisible(false);
}
private TaskRow rowFor(Task task) {
TaskComponent taskComponent = find(task);
return taskComponent == null ? null : taskComponent.getRow();
}
private void addPendingTasks(List<Task> tasksPendingToAdd, TaskRow insertBefore, boolean relocate) {
if ( tasksPendingToAdd.isEmpty() ) {
return;
}
for (TaskComponent each : createAndPublishComponentsIfNeeded(tasksPendingToAdd)) {
addTaskComponent(insertBefore, each, relocate);
visibleTasks.add(each.getTask());
each.getTask().setVisible(true);
}
tasksPendingToAdd.clear();
}
public void setPredicate(FilterAndParentExpandedPredicates predicate) {
this.predicate = predicate;
reload(false);
}
}