/* * 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.planner.allocation.stretches; import static org.libreplan.web.I18nHelper._; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.Date; import java.util.List; import org.apache.commons.lang3.Validate; import org.libreplan.business.common.exceptions.ValidationException; import org.libreplan.business.planner.entities.AssignmentFunction; import org.libreplan.business.planner.entities.ResourceAllocation; import org.libreplan.business.planner.entities.Stretch; import org.libreplan.business.planner.entities.StretchesFunction; import org.libreplan.business.planner.entities.StretchesFunctionTypeEnum; import org.libreplan.web.common.Util; import org.zkoss.zk.ui.Component; import org.zkoss.zk.ui.SuspendNotAllowedException; import org.zkoss.zk.ui.WrongValueException; import org.zkoss.zk.ui.event.Event; import org.zkoss.zk.ui.event.Events; import org.zkoss.zk.ui.util.GenericForwardComposer; import org.zkoss.zkplus.spring.SpringUtil; import org.zkoss.zul.Button; import org.zkoss.zul.Datebox; import org.zkoss.zul.Decimalbox; import org.zkoss.zul.Listcell; import org.zkoss.zul.Listitem; import org.zkoss.zul.ListitemRenderer; import org.zkoss.zul.Messagebox; import org.zkoss.zul.XYModel; import org.zkoss.zul.Window; public class StretchesFunctionController extends GenericForwardComposer { private static final String EXIT_STATUS = "EXIT_STATUS"; interface IGraphicGenerator { boolean areChartsEnabled(IStretchesFunctionModel model); XYModel getDedicationChart(IStretchesFunctionModel stretchesFunctionModel); XYModel getAccumulatedHoursChartData(IStretchesFunctionModel stretchesFunctionModel); } private Window window; private IStretchesFunctionModel stretchesFunctionModel; private StretchesRenderer stretchesRenderer = new StretchesRenderer(); private String title; private final IGraphicGenerator graphicGenerator; public StretchesFunctionController(IGraphicGenerator graphicGenerator) { Validate.notNull(graphicGenerator); this.graphicGenerator = graphicGenerator; } @Override public void doAfterCompose(Component comp) throws Exception { super.doAfterCompose(comp); injectsObjects(); window = (Window) comp; } private void injectsObjects(){ if (stretchesFunctionModel == null) { stretchesFunctionModel = (IStretchesFunctionModel) SpringUtil.getBean("stretchesFunctionModel"); } } AssignmentFunction getAssignmentFunction() { return stretchesFunctionModel.getStretchesFunction(); } void setResourceAllocation(ResourceAllocation<?> resourceAllocation, StretchesFunctionTypeEnum type) { AssignmentFunction assignmentFunction = resourceAllocation.getAssignmentFunction(); stretchesFunctionModel.init((StretchesFunction) assignmentFunction, resourceAllocation, type); reloadStretchesListAndCharts(); } public int showWindow() { try { window.doModal(); return (Integer) window.getAttribute("EXIT_STATUS", true); } catch (SuspendNotAllowedException e) { throw new RuntimeException(e); } } public void confirm() throws InterruptedException { try { stretchesFunctionModel.confirm(); exit(); } catch (ValidationException e) { Messagebox.show(e.getMessage(), _("Error"), Messagebox.OK, Messagebox.ERROR); } } public void cancel() throws InterruptedException { int status = Messagebox.show(_("All changes will be lost. Are you sure?"), _("Confirm cancel"), Messagebox.YES | Messagebox.NO, Messagebox.QUESTION); if ( Messagebox.YES == status ) { stretchesFunctionModel.cancel(); close(); } } private void close() { window.setAttribute(EXIT_STATUS, Messagebox.CANCEL, true); window.setVisible(false); } private void exit() { window.setAttribute(EXIT_STATUS, Messagebox.OK, true); window.setVisible(false); } public void onClose(Event event) { close(); } public List<Stretch> getStretches() { return stretchesFunctionModel.getAllStretches(); } public StretchesRenderer getStretchesRenderer() { return stretchesRenderer; } private interface IFocusApplycability { boolean focusIfApplycableOnLength(Stretch stretch, Decimalbox lengthPercentage); boolean focusIfApplycableOnAmountWork(Stretch stretch, Decimalbox amountWork); } private static class FocusState implements IFocusApplycability { private static final NoFocus NO_FOCUS = new NoFocus(); private IFocusApplycability currentFocus; private FocusState(IFocusApplycability currentFocus) { this.currentFocus = currentFocus; } public static FocusState noFocus() { return new FocusState(NO_FOCUS); } @Override public boolean focusIfApplycableOnAmountWork(Stretch stretch, Decimalbox amountWork) { boolean result = currentFocus.focusIfApplycableOnAmountWork(stretch, amountWork); if ( result ) { currentFocus = NO_FOCUS; } return result; } @Override public boolean focusIfApplycableOnLength(Stretch stretch, Decimalbox lengthPercentage) { boolean result = currentFocus.focusIfApplycableOnLength(stretch, lengthPercentage); if ( result ) { currentFocus = NO_FOCUS; } return result; } void focusOn(Stretch stretch, Field field) { this.currentFocus = new FocusOnStretch(stretch, field); } } private static class NoFocus implements IFocusApplycability { @Override public boolean focusIfApplycableOnAmountWork(Stretch stretch, Decimalbox amountWork) { return false; } @Override public boolean focusIfApplycableOnLength(Stretch stretch, Decimalbox lengthPercentage) { return false; } } public enum Field { AMOUNT_WORK, LENGTH } private static class FocusOnStretch implements IFocusApplycability { private final Stretch stretch; private final Field field; FocusOnStretch(Stretch stretch, Field field) { this.stretch = stretch; this.field = field; } @Override public boolean focusIfApplycableOnAmountWork(Stretch stretch, Decimalbox amountWork) { if ( field == Field.AMOUNT_WORK && this.stretch.equals(stretch) ) { amountWork.focus(); return true; } return false; } @Override public boolean focusIfApplycableOnLength(Stretch stretch, Decimalbox lengthPercentage) { if ( field == Field.LENGTH && this.stretch.equals(stretch) ) { lengthPercentage.focus(); return true; } return false; } } private FocusState focusState = FocusState.noFocus(); /** * Renders a {@link Stretch}. * * @author Manuel Rego Casasnovas <mrego@igalia.com> */ private class StretchesRenderer implements ListitemRenderer { private final BigDecimal ONE_HUNDRED = BigDecimal.valueOf(100); @Override public void render(Listitem listitem, Object o, int i) throws Exception { Stretch stretch = (Stretch) o; listitem.setValue(stretch); listitem.setDisabled(stretch.isReadOnly()); appendDate(listitem, stretch); appendLengthPercentage(listitem, stretch); appendAmountWorkPercentage(listitem, stretch); appendOperations(listitem, stretch); } private void appendChild(Listitem item, Component component) { Listcell listcell = new Listcell(); listcell.appendChild(component); item.appendChild(listcell); } private void appendDate(Listitem item, final Stretch stretch) { final Datebox tempDatebox = new Datebox(); Datebox datebox = Util.bind(tempDatebox, () -> { return stretchesFunctionModel.getStretchDate(stretch); }, value -> { try { if (value == null) { value = new Date(); } stretchesFunctionModel.setStretchDate(stretch, value); reloadStretchesListAndCharts(); } catch (IllegalArgumentException e) { throw new WrongValueException(tempDatebox, e .getMessage()); } }); appendChild(item, datebox); } private void appendLengthPercentage(Listitem item, final Stretch stretch) { final Decimalbox tempDecimalbox = new Decimalbox(); final Decimalbox decimalbox = Util.bind(tempDecimalbox, () -> { return fromValueToPercent(scaleBy(stretch.getLengthPercentage(), 4)); }, new Util.Setter<BigDecimal>() { @Override public void set(BigDecimal percent) { if (percent == null) { percent = BigDecimal.ZERO; } checkBetweenZeroAndOneHundred(percent); BigDecimal value = fromPercentToValue(scaleBy(percent, 2)); stretchesFunctionModel.setStretchLengthPercentage(stretch, value); focusState.focusOn(stretch, Field.LENGTH); reloadStretchesListAndCharts(); } private void checkBetweenZeroAndOneHundred(BigDecimal percent) { if (percent.toBigInteger().intValue() > 100 || percent.toBigInteger().intValue() < 0) { throw new WrongValueException(tempDecimalbox, _("Length percentage should be between 0 and 100")); } } }); appendChild(item, decimalbox); focusState.focusIfApplycableOnLength(stretch, decimalbox); } private BigDecimal scaleBy(BigDecimal value, int scale) { return value.setScale(scale, RoundingMode.HALF_UP); } private BigDecimal fromValueToPercent(BigDecimal value) { return value.multiply(ONE_HUNDRED); } private BigDecimal fromPercentToValue(BigDecimal percent) { return BigDecimal.ZERO.equals(percent) ? BigDecimal.ZERO : percent.divide(ONE_HUNDRED, RoundingMode.DOWN); } private void appendAmountWorkPercentage(Listitem item, final Stretch stretch) { final Decimalbox decimalBox = new Decimalbox(); Util.bind(decimalBox, () -> { return fromValueToPercent(scaleBy(stretch.getAmountWorkPercentage(), 4)); }, percent -> { if (percent == null) { percent = BigDecimal.ZERO; } BigDecimal value = fromPercentToValue(scaleBy(percent, 2)); try { stretch.setAmountWorkPercentage(value); focusState.focusOn(stretch, Field.AMOUNT_WORK); reloadStretchesListAndCharts(); } catch (IllegalArgumentException e) { throw new WrongValueException( decimalBox, _("Amount work percentage should be between 0 and 100")); } }); appendChild(item, decimalBox); focusState.focusIfApplycableOnAmountWork(stretch, decimalBox); } private void appendOperations(Listitem item, final Stretch stretch) { Button button; if (item.isDisabled()) { button = new Button("", "/common/img/ico_borrar_out.png"); button.setHoverImage("/common/img/ico_borrar_out.png"); button.setSclass("icono"); button.setDisabled(true); } else { button = new Button("", "/common/img/ico_borrar1.png"); button.setHoverImage("/common/img/ico_borrar.png"); button.setSclass("icono"); button.setTooltiptext(_("Delete")); button.addEventListener(Events.ON_CLICK, event -> { stretchesFunctionModel.removeStretch(stretch); reloadStretchesListAndCharts(); }); } appendChild(item, button); } } public void addStretch() { stretchesFunctionModel.addStretch(); reloadStretchesListAndCharts(); } private void reloadStretchesListAndCharts() { Util.reloadBindings(window.getFellow("stretchesList")); Util.reloadBindings(window.getFellow("charts")); } public XYModel getDedicationChartData() { return graphicGenerator.getDedicationChart(stretchesFunctionModel); } public XYModel getAccumulatedHoursChartData() { return graphicGenerator.getAccumulatedHoursChartData(stretchesFunctionModel); } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } private boolean isChartsEnabled() { return graphicGenerator.areChartsEnabled(stretchesFunctionModel); } public boolean isChartsDisabled() { return !isChartsEnabled(); } }