/* * 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.materials; import org.libreplan.business.common.exceptions.ValidationException; import org.libreplan.business.common.exceptions.ValidationException.InvalidValue; import org.libreplan.business.materials.entities.Material; import org.libreplan.business.materials.entities.MaterialCategory; import org.libreplan.business.materials.entities.UnitType; import org.libreplan.web.common.ConstraintChecker; import org.libreplan.web.common.IMessagesForUser; import org.libreplan.web.common.Level; import org.libreplan.web.common.MessagesForUser; import org.libreplan.web.common.Util; import org.zkoss.zk.ui.Component; import org.zkoss.zk.ui.WrongValueException; import org.zkoss.zk.ui.event.CheckEvent; import org.zkoss.zk.ui.event.Events; import org.zkoss.zk.ui.event.InputEvent; import org.zkoss.zk.ui.util.GenericForwardComposer; import org.zkoss.zul.Button; import org.zkoss.zul.Caption; import org.zkoss.zul.Checkbox; import org.zkoss.zul.Grid; import org.zkoss.zul.Listbox; import org.zkoss.zul.Listcell; import org.zkoss.zul.Listitem; import org.zkoss.zul.ListitemRenderer; import org.zkoss.zul.Messagebox; import org.zkoss.zul.Row; import org.zkoss.zul.Rows; import org.zkoss.zul.SimpleListModel; import org.zkoss.zul.Textbox; import org.zkoss.zul.Tree; import org.zkoss.zul.TreeModel; import org.zkoss.zul.Treecell; import org.zkoss.zul.Treechildren; import org.zkoss.zul.Treeitem; import org.zkoss.zul.TreeitemRenderer; import org.zkoss.zul.Treerow; import org.zkoss.zkplus.spring.SpringUtil; import java.util.ConcurrentModificationException; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.ArrayList; import static org.libreplan.web.I18nHelper._; /** * Controller for {@link Material} materials * * @author Diego Pino García <dpino@igalia.com> * */ public class MaterialsController extends GenericForwardComposer { private IMaterialsModel materialsModel; private Tree categoriesTree; private Grid gridMaterials; private Textbox txtCategory; private Button btnAddMaterial; private IMessagesForUser messagesForUser; private Component messagesContainer; private Caption materialsCaption; private UnitTypeListRenderer unitTypeListRenderer = new UnitTypeListRenderer(); public MaterialsController(){ materialsModel = (IMaterialsModel) SpringUtil.getBean("materialsModel"); } @Override public void doAfterCompose(Component comp) throws Exception { super.doAfterCompose(comp); comp.setAttribute("materialsController", this, true); messagesForUser = new MessagesForUser(messagesContainer); // load the unit types loadUnitTypes(); // Renders grid and enables delete button is material is new gridMaterials.addEventListener("onInitRender", event -> { gridMaterials.renderAll(); final Rows rows = gridMaterials.getRows(); for (Iterator i = rows.getChildren().iterator(); i.hasNext(); ) { final Row row = (Row) i.next(); final Material material = row.getValue(); Button btnDelete = (Button) row.getChildren().get(6); if (!materialsModel.canRemoveMaterial(material)) { btnDelete.setDisabled(true); btnDelete.setImage("/common/img/ico_borrar_out.png"); btnDelete.setHoverImage("/common/img/ico_borrar_out.png"); } } }); } private void loadUnitTypes() { materialsModel.loadUnitTypes(); } public List<UnitType> getUnitTypes() { return materialsModel.getUnitTypes(); } public void selectUnitType(Component self) { Listitem selectedItem = ((Listbox) self).getSelectedItem(); UnitType unitType = selectedItem.getValue(); Material material = ((Row) self.getParent()).getValue(); material.setUnitType(unitType); } public TreeModel getMaterialCategories() { return materialsModel.getMaterialCategories(); } public MaterialCategoryRenderer getMaterialCategoryRenderer() { return new MaterialCategoryRenderer(); } /** * Render for criterionsTree. * * I had to implement a renderer for the Tree, for setting open to tree for each treeitem while being rendered. * * I tried to do this by iterating through the list of items after setting * model in doAfterCompose, but I got a ConcurrentModificationException. * It seems that at that point some other component was using the list of item, so it was not possible to modify it. * There's not other point where to initialize components but doAfterCompose. * * @author Diego Pino Garcia <dpino@igalia.com> * */ private class MaterialCategoryRenderer implements TreeitemRenderer<MaterialCategory> { /** * Copied verbatim from org.zkoss.zul.Tree */ @Override public void render(Treeitem treeitem, MaterialCategory node, int i) throws Exception { final MaterialCategory materialCategory = node; final Textbox tb = new Textbox(materialCategory.getName()); tb.setWidth("90%"); tb.addEventListener("onChange", event -> { final InputEvent ie = (InputEvent) event; materialCategory.setName(ie.getValue()); }); tb.addEventListener("onFocus", event -> { ((Treeitem)tb.getParent().getParent().getParent()).setSelected(true); refreshMaterials(); }); Treecell tc = new Treecell(); Treerow tr; treeitem.setValue(node); if (treeitem.getTreerow() == null) { tr = new Treerow(); tr.setParent(treeitem); treeitem.setOpen(true); // Expand node } else { tr = treeitem.getTreerow(); tr.getChildren().clear(); } tb.setParent(tc); tc.setParent(tr); final Textbox codeTb = new Textbox(materialCategory.getCode()); codeTb.setWidth("95%"); codeTb.setDisabled(materialCategory.isCodeAutogenerated()); codeTb.addEventListener("onChange", event -> { final InputEvent ie = (InputEvent) event; materialCategory.setCode(ie.getValue()); }); codeTb.addEventListener("onFocus", event -> { ((Treeitem)codeTb.getParent().getParent().getParent()).setSelected(true); refreshMaterials(); }); Treecell codeTc = new Treecell(); codeTb.setParent(codeTc); codeTc.setParent(tr); final Checkbox cb = new Checkbox(); cb.setChecked(materialCategory.isCodeAutogenerated()); cb.addEventListener("onCheck", event -> { final CheckEvent ce = (CheckEvent) event; materialCategory.setCodeAutogenerated(ce.isChecked()); if (ce.isChecked()) { try { materialsModel.setCodeAutogenerated(ce.isChecked(), materialCategory); } catch (ConcurrentModificationException err) { messagesForUser.showMessage(Level.ERROR, err.getMessage()); } } codeTb.setValue(materialCategory.getCode()); codeTb.setDisabled(ce.isChecked()); Util.reloadBindings(codeTb); Util.reloadBindings(gridMaterials); }); Treecell generateCodeTc = new Treecell(); cb.setParent(generateCodeTc); generateCodeTc.setParent(tr); appendDeleteButton(treeitem); } } private void appendDeleteButton(final Treeitem ti) { final MaterialCategory materialCategory = ti.getValue(); Button btnDelete = new Button("", "/common/img/ico_borrar1.png"); btnDelete.setHoverImage("/common/img/ico_borrar.png"); btnDelete.setSclass("icono"); btnDelete.setTooltiptext(_("Delete")); btnDelete.addEventListener(Events.ON_CLICK, event -> confirmRemove(materialCategory)); btnDelete.setDisabled(hasSubcategoriesOrMaterials(materialCategory)); Treecell tc = new Treecell(); tc.setParent(ti.getTreerow()); btnDelete.setParent(tc); } private boolean hasSubcategoriesOrMaterials(MaterialCategory materialCategory) { return !materialCategory.getSubcategories().isEmpty() || !materialCategory.getMaterials().isEmpty(); } public void confirmRemove(MaterialCategory materialCategory) { int status = Messagebox.show(_("Confirm deleting {0}. Are you sure?", materialCategory.getName()), _("Delete"), Messagebox.OK | Messagebox.CANCEL, Messagebox.QUESTION); if (Messagebox.OK == status) { removeMaterialCategory(materialCategory); } } private void removeMaterialCategory(MaterialCategory materialCategory) { materialsModel.confirmRemoveMaterialCategory(materialCategory); reloadCategoriesTree(categoriesTree.getSelectedItem()); } public void addMaterialCategory() { String categoryName = txtCategory.getValue(); if (categoryName == null || categoryName.isEmpty()) { throw new WrongValueException(txtCategory, _("cannot be empty")); } MaterialCategory parent = null; final Treeitem treeitem = categoriesTree.getSelectedItem(); if (treeitem != null) { parent = treeitem.getValue(); } try { materialsModel.addMaterialCategory(parent, categoryName); txtCategory.setValue(""); reloadCategoriesTree(treeitem); } catch (ValidationException e) { for (InvalidValue invalidValue : e.getInvalidValues()) { Object value = invalidValue.getRootBean(); if (value instanceof MaterialCategory) { MaterialCategory materialCategory = (MaterialCategory) value; Component comp = findInMaterialCategoryTree(materialCategory); if (comp != null) { throw new WrongValueException(comp, _(invalidValue.getMessage())); } } } } } /** * Finds which element in categoryTree has the same name as {@link MaterialCategory}, * and returns name {@link Textbox} component. * * @param materialCategory * @return {@link Component} */ private Component findInMaterialCategoryTree(MaterialCategory materialCategory) { final Treechildren children = categoriesTree.getTreechildren(); for(Treeitem each: children.getItems()) { final MaterialCategory _materialCategory = each.getValue(); final Textbox textbox = getMaterialCategoryTextbox(each); // Clear previous errors textbox.clearErrorMessage(); if (_materialCategory.equals(materialCategory)) { return textbox; } } return null; } private Textbox getMaterialCategoryTextbox(Treeitem treeitem) { final Treerow tr = treeitem.getTreerow(); final Treecell tc = (Treecell) tr.getChildren().get(0); return (Textbox) tc.getChildren().get(0); } public void addMaterialToMaterialCategory(Treeitem treeitem) { if (treeitem == null) { throw new WrongValueException(btnAddMaterial, _("Cannot insert material in general view. Please, select a category")); } final MaterialCategory materialCategory = treeitem.getValue(); materialsModel.addMaterialToMaterialCategory(materialCategory); Util.reloadBindings(gridMaterials); } public void saveAndContinue() { if (save()) { messagesForUser.showMessage(Level.INFO, _("Materials saved")); // Reload materials and categories, keep track of category currently being selected final Treeitem treeitem = categoriesTree.getSelectedItem(); materialsModel.reloadMaterialCategories(); categoriesTree.setSelectedItem(treeitem); reloadCategoriesTree(categoriesTree.getSelectedItem()); Util.reloadBindings(gridMaterials); } } private void reloadCategoriesTree(Treeitem treeitem) { Util.reloadBindings(categoriesTree); if (treeitem != null) { final MaterialCategory materialCategory = treeitem.getValue(); categoriesTree.invalidate(); locateAndSelectMaterialCategory(materialCategory); } else { categoriesTree.invalidate(); } } private boolean save() { try { materialsModel.confirmSave(); return true; } catch (ValidationException e) { showInvalidValues(e); } return false; } private void showInvalidValues(ValidationException validationException) { final Set<? extends InvalidValue> invalidValues = validationException.getInvalidValues(); for (InvalidValue each: invalidValues) { final Object bean = each.getRootBean(); // Errors related with constraints in Material (not null, etc) if (bean instanceof Material) { final Material material = (Material) bean; showConstraintErrorsFor(material.getCategory()); } // Unique material in materialCategory if (bean instanceof MaterialCategory) { final MaterialCategory materialCategory = (MaterialCategory) bean; final Treeitem treeitem = findTreeItemByMaterialCategory(categoriesTree, materialCategory); if (treeitem != null) { if (each.getPropertyPath().equals("name")) { throw new WrongValueException(getCategoryTextbox(treeitem), _(each.getMessage())); } if (each.getPropertyPath().equals("code")) { throw new WrongValueException(getCategoryCodeTextbox(treeitem), _(each.getMessage())); } } } } messagesForUser.showInvalidValues(validationException); } private Textbox getCategoryTextbox(Treeitem treeitem) { final Treerow treerow = (Treerow) treeitem.getChildren().get(0); final Treecell treecell = (Treecell) treerow.getChildren().get(0); return (Textbox) treecell.getChildren().get(0); } private Textbox getCategoryCodeTextbox(Treeitem treeitem) { final Treerow treerow = (Treerow) treeitem.getChildren().get(0); final Treecell treecell = (Treecell) treerow.getChildren().get(1); return (Textbox) treecell.getChildren().get(0); } private boolean locateAndSelectMaterialCategory(MaterialCategory materialCategory) { Treeitem treeitem = findTreeItemByMaterialCategory(categoriesTree, materialCategory); if (treeitem != null) { treeitem.setSelected(true); return true; } return false; } private Treeitem findTreeItemByMaterialCategory(Tree tree, MaterialCategory materialCategory) { for (final Treeitem treeitem : tree.getItems()) { final MaterialCategory _materialCategory = treeitem.getValue(); if (_materialCategory.getId() != null && _materialCategory.getId().equals(materialCategory.getId())) { return treeitem; } } return null; } private void showConstraintErrorsFor(MaterialCategory materialCategory) { if (locateAndSelectMaterialCategory(materialCategory)) { // Load materials for category final List<Material> materials = getMaterials(materialCategory); gridMaterials.setModel(new SimpleListModel<>(materials)); gridMaterials.renderAll(); // Show errors ConstraintChecker.isValid(gridMaterials); } } public void refreshMaterials() { final List<Material> materials = getMaterials(); gridMaterials.setModel(new SimpleListModel<>(materials)); refreshMaterialsListTitle(); Util.reloadBindings(gridMaterials); } private void refreshMaterialsListTitle() { Treeitem treeitem = categoriesTree.getSelectedItem(); if (treeitem != null) { materialsCaption.setLabel( _("List of materials for category: {0}", ((MaterialCategory) treeitem.getValue()).getName())); } else { materialsCaption.setLabel(_("List of materials for all categories (select one to filter)")); } } public List<Material> getMaterials() { return getMaterials(categoriesTree.getSelectedItem()); } private List<Material> getMaterials(Treeitem treeitem) { final List<Material> result = new ArrayList<>(); if (treeitem != null) { result.addAll(getMaterials((MaterialCategory) treeitem.getValue())); } else { result.addAll(materialsModel.getMaterials()); } return result; } private List<Material> getMaterials(MaterialCategory materialCategory) { return materialsModel.getMaterials(materialCategory); } public void remove(Material material) { if(materialsModel.canRemoveMaterial(material)) { materialsModel.removeMaterial(material); Util.reloadBindings(gridMaterials); } else { messagesForUser.showMessage(Level.ERROR, _("Cannot delete that material because it is assigned to a project.")); } } public void clearSelectionCategoriesTree() { categoriesTree.clearSelection(); this.refreshMaterialsListTitle(); Util.reloadBindings(gridMaterials); } public UnitTypeListRenderer getRenderer() { return unitTypeListRenderer; } /** * RowRenderer for a @{UnitType} element. * * @author Susana Montes Pedreira <smontes@wirelessgalicia.com> */ public static class UnitTypeListRenderer implements ListitemRenderer { @Override public void render(Listitem listItem, Object data, int i) { final UnitType unitType = (UnitType) data; listItem.setValue(unitType); Listcell listCell = new Listcell(unitType.getMeasure()); listItem.appendChild(listCell); Material material = ((Row) listItem.getListbox().getParent()).getValue(); if ((material.getUnitType() != null) && (unitType.getId().equals(material.getUnitType().getId()))) { listItem.getListbox().setSelectedItem(listItem); } } } public String getMoneyFormat() { return Util.getMoneyFormat(); } }