/* * 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); } }