/* * 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.resourceload; import static org.zkoss.ganttz.i18n.I18nHelper._; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.zkoss.ganttz.IDatesMapper; import org.zkoss.ganttz.data.resourceload.LoadPeriod; import org.zkoss.ganttz.data.resourceload.LoadTimeLine; import org.zkoss.ganttz.timetracker.TimeTracker; import org.zkoss.ganttz.timetracker.zoom.IZoomLevelChangedListener; import org.zkoss.ganttz.timetracker.zoom.ZoomLevel; import org.zkoss.ganttz.util.MenuBuilder; import org.zkoss.ganttz.util.WeakReferencedListeners; import org.zkoss.zk.ui.sys.ContentRenderer; import org.zkoss.zul.Div; import org.zkoss.zul.Menupopup; import org.zkoss.zul.impl.XulElement; /** * This class wraps ResourceLoad data inside an specific HTML Div component. * * @author Lorenzo Tilve Álvaro <ltilve@igalia.com> * @author Vova Perebykivskyi <vova@libreplan-enterprise.com> */ public class ResourceLoadComponent extends XulElement { private final LoadTimeLine loadLine; private final TimeTracker timeTracker; private transient IZoomLevelChangedListener zoomChangedListener; private WeakReferencedListeners<ISeeScheduledOfListener> scheduleListeners = WeakReferencedListeners.create(); private Map<Div, Menupopup> contextMenus = new HashMap<>(); private ResourceLoadComponent(final TimeTracker timeTracker, final LoadTimeLine loadLine) { this.loadLine = loadLine; this.timeTracker = timeTracker; createChildren(loadLine, timeTracker.getMapper()); /* Do not replace it with lambda */ zoomChangedListener = new IZoomLevelChangedListener() { /** * In general it is working like, on every zoomChanged : * 1. Remove all LoadLines ( divs ). * 2. Create new ones ( for selected zoom mode ). * 3. Redraw insertionPointRightPanel component ( Div ). */ @Override public void zoomLevelChanged(ZoomLevel detailLevel) { getChildren().clear(); createChildren(loadLine, timeTracker.getMapper()); if ( !getFellows().isEmpty() ) { getFellow("insertionPointRightPanel").invalidate(); } invalidate(); } }; this.timeTracker.addZoomListener(zoomChangedListener); } public static ResourceLoadComponent create(TimeTracker timeTracker, LoadTimeLine loadLine) { return new ResourceLoadComponent(timeTracker, loadLine); } private void createChildren(final LoadTimeLine loadLine, IDatesMapper mapper) { List<Div> divs = createDivsForPeriods(mapper, loadLine.getLoadPeriods()); for (Div div : divs) { appendChild(div); } if (loadLine.getRole().isVisibleScheduled()) { for (Div div : divs) { addDoubleClickAction(div, loadLine); addContextMenu(divs, div, loadLine); } } } private void addDoubleClickAction(final Div div, final LoadTimeLine loadLine) { div.addEventListener("onDoubleClick", event -> schedule(loadLine)); } private void addContextMenu(final List<Div> divs, final Div div, final LoadTimeLine loadLine) { /* * This EventListener could be replaced with * div.setContext(getContextMenuFor(divs, div, loadLine)) but * in this case this is not valid as we'll got an exception. * * As this component (ResourceLoadComponent) hasn't be added to * a page yet, its getPage() method will return null and a * non-null page is required by MenuBuilder or a NullPointerException will be raised. */ div.addEventListener("onRightClick", event -> { try { getContextMenuFor(divs, div, loadLine).open(div); } catch (Exception e) { e.printStackTrace(); } }); } public void schedule(final LoadTimeLine taskLine) { scheduleListeners.fireEvent(listener -> listener.seeScheduleOf(taskLine)); } public void addSeeScheduledOfListener(ISeeScheduledOfListener seeScheduledOfListener) { scheduleListeners.addListener(seeScheduledOfListener); } private Menupopup getContextMenuFor(final List<Div> divs, final Div div, final LoadTimeLine loadLine) { if (contextMenus.get(div) == null) { MenuBuilder<Div> menuBuilder = MenuBuilder.on(getPage(), divs); menuBuilder.item( _("See resource allocation"), "/common/img/ico_allocation.png", (chosen, event) -> schedule(loadLine)); Menupopup result = menuBuilder.createWithoutSettingContext(); contextMenus.put(div, result); return result; } return contextMenus.get(div); } public String getResourceLoadName() { return loadLine.getConceptName(); } public String getResourceLoadType() { return loadLine.getType(); } LoadTimeLine getLoadLine() { return loadLine; } private static List<Div> createDivsForPeriods(IDatesMapper datesMapper, List<LoadPeriod> loadPeriods) { List<Div> result = new ArrayList<>(); for (LoadPeriod loadPeriod : loadPeriods) { result.add(createDivForPeriod(datesMapper, loadPeriod)); } return result; } private static Div createDivForPeriod(IDatesMapper datesMapper, LoadPeriod loadPeriod) { Div result = new Div(); result.setClass(String.format("taskassignmentinterval %s", loadPeriod.getLoadLevel().getCategory())); String load = _("Load: {0}%", loadPeriod.getLoadLevel().getPercentage()) + ", "; if (loadPeriod.getLoadLevel().getPercentage() == Integer.MAX_VALUE) { load = ""; } result.setTooltiptext( load + _("available effort: {0}, assigned effort: {1}", loadPeriod.getAvailableEffort(), loadPeriod.getAssignedEffort())); result.setLeft(forCSS(getStartPixels(datesMapper, loadPeriod))); result.setWidth(forCSS(getWidthPixels(datesMapper, loadPeriod))); return result; } private static int getWidthPixels(IDatesMapper datesMapper, LoadPeriod loadPeriod) { return Math.max(loadPeriod.getEnd().toPixels(datesMapper) - getStartPixels(datesMapper, loadPeriod), 0); } private static String forCSS(int pixels) { return String.format("%dpx", pixels); } private static int getStartPixels(IDatesMapper datesMapper, LoadPeriod loadPeriod) { return loadPeriod.getStart().toPixels(datesMapper); } /** * It is drawing chart on right pane for each ResourceLoad row. */ @Override protected void renderProperties(ContentRenderer renderer) throws IOException{ render(renderer, "_resourceLoadName", getResourceLoadName()); render(renderer, "_resourceLoadType", getResourceLoadType()); super.renderProperties(renderer); } }