package org.sigmah.client.ui.presenter.project; /* * #%L * Sigmah * %% * Copyright (C) 2010 - 2016 URD * %% * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU 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 General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program. If not, see * <http://www.gnu.org/licenses/gpl-3.0.html>. * #L% */ import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import com.extjs.gxt.ui.client.widget.Layout; import org.sigmah.client.dispatch.CommandResultHandler; import org.sigmah.client.dispatch.DispatchQueue; import org.sigmah.client.dispatch.monitor.LoadingMask; import org.sigmah.client.event.UpdateEvent; import org.sigmah.client.i18n.I18N; import org.sigmah.client.inject.Injector; import org.sigmah.client.page.Page; import org.sigmah.client.page.PageRequest; import org.sigmah.client.ui.notif.N10N; import org.sigmah.client.ui.view.project.ProjectDetailsView; import org.sigmah.client.ui.widget.button.Button; import org.sigmah.client.ui.widget.form.Forms; import org.sigmah.client.ui.widget.form.IterableGroupPanel; import org.sigmah.client.ui.widget.form.IterableGroupPanel.IterableGroupItem; import org.sigmah.client.ui.widget.layout.Layouts; import org.sigmah.client.util.ClientUtils; import org.sigmah.shared.command.GetLayoutGroupIterations; import org.sigmah.shared.command.GetOrgUnit; import org.sigmah.shared.command.GetValue; import org.sigmah.shared.command.UpdateLayoutGroupIterations; import org.sigmah.shared.command.UpdateLayoutGroupIterations.IterationChange; import org.sigmah.shared.command.UpdateProject; import org.sigmah.shared.command.result.ListResult; import org.sigmah.shared.command.result.ValueResult; import org.sigmah.shared.command.result.VoidResult; import org.sigmah.shared.dto.ProjectDTO; import org.sigmah.shared.dto.ProjectDetailsDTO; import org.sigmah.shared.dto.ProjectFundingDTO; import org.sigmah.shared.dto.UserDTO; import org.sigmah.shared.dto.base.EntityDTO; import org.sigmah.shared.dto.country.CountryDTO; import org.sigmah.shared.dto.element.BudgetElementDTO; import org.sigmah.shared.dto.element.BudgetSubFieldDTO; import org.sigmah.shared.dto.element.DefaultFlexibleElementDTO; import org.sigmah.shared.dto.element.FlexibleElementContainer; import org.sigmah.shared.dto.element.FlexibleElementDTO; import org.sigmah.shared.dto.element.event.RequiredValueEvent; import org.sigmah.shared.dto.element.event.RequiredValueHandler; import org.sigmah.shared.dto.element.event.ValueEvent; import org.sigmah.shared.dto.element.event.ValueHandler; import org.sigmah.shared.dto.layout.LayoutConstraintDTO; import org.sigmah.shared.dto.layout.LayoutDTO; import org.sigmah.shared.dto.layout.LayoutGroupDTO; import org.sigmah.shared.dto.layout.LayoutGroupIterationDTO; import org.sigmah.shared.dto.orgunit.OrgUnitDTO; import org.sigmah.shared.dto.referential.GlobalPermissionEnum; import org.sigmah.shared.util.ProfileUtils; import org.sigmah.shared.util.ValueResultUtils; import com.allen_sauer.gwt.log.client.Log; import com.extjs.gxt.ui.client.event.ButtonEvent; import com.extjs.gxt.ui.client.event.SelectionListener; import com.extjs.gxt.ui.client.widget.Component; import com.extjs.gxt.ui.client.widget.Label; import com.extjs.gxt.ui.client.widget.LayoutContainer; import com.extjs.gxt.ui.client.widget.form.FieldSet; import com.extjs.gxt.ui.client.widget.layout.FormData; import com.google.gwt.user.client.ui.Grid; import com.google.gwt.user.client.ui.Widget; import com.google.inject.ImplementedBy; import com.google.inject.Inject; import com.google.inject.Singleton; import org.sigmah.client.computation.ComputationTriggerManager; import org.sigmah.client.util.profiler.Profiler; import org.sigmah.client.util.profiler.Scenario; /** * Project's details presenter which manages the {@link ProjectDetailsView}. * * @author Denis Colliot (dcolliot@ideia.fr) */ @Singleton public class ProjectDetailsPresenter extends AbstractProjectPresenter<ProjectDetailsPresenter.View> implements IterableGroupPanel.Delegate { // CSS style names. private static final String STYLE_PROJECT_LABEL_10 = "project-label-10"; /** * Description of the view managed by this presenter. */ @ImplementedBy(ProjectDetailsView.class) public static interface View extends AbstractProjectPresenter.View { LayoutContainer getMainPanel(); void setMainPanelWidget(Widget widget); Button getSaveButton(); } /** * List of values changes. */ private final ArrayList<ValueEvent> valueChanges = new ArrayList<ValueEvent>(); private final Map<Integer, IterationChange> iterationChanges = new HashMap<Integer, IterationChange>(); private final Map<Integer, IterableGroupItem> newIterationsTabItems = new HashMap<Integer, IterableGroupItem>(); /** * Listen to the values of flexible elements to update computated values. */ @Inject private ComputationTriggerManager computationTriggerManager; /** * Presenters's initialization. * * @param view * Presenter's view interface. * @param injector * Injected client injector. */ @Inject public ProjectDetailsPresenter(final View view, final Injector injector) { super(view, injector); } /** * {@inheritDoc} */ @Override public Page getPage() { return Page.PROJECT_DETAILS; } /** * {@inheritDoc} */ @Override public void onBind() { // Save action. view.getSaveButton().addSelectionListener(new SelectionListener<ButtonEvent>() { @Override public void componentSelected(final ButtonEvent be) { onSaveAction(); } }); } /** * {@inheritDoc} */ @Override public void onPageRequest(final PageRequest request) { load(getProject().getProjectModel().getProjectDetails()); valueChanges.clear(); } /** * {@inheritDoc} */ @Override public boolean hasValueChanged() { return !valueChanges.isEmpty() || !iterationChanges.isEmpty(); } // --------------------------------------------------------------------------------------------------------------- // // UTILITY METHODS. // // --------------------------------------------------------------------------------------------------------------- /** * Loads the presenter with the given project {@code details}. * * @param details * The project details. */ private void load(final ProjectDetailsDTO details) { // Prepare the manager of computation elements computationTriggerManager.prepareForProject(getProject()); // Clear panel. view.getMainPanel().removeAll(); // Layout. final LayoutDTO layout = details.getLayout(); // Counts elements. int count = 0; for (final LayoutGroupDTO groupDTO : layout.getGroups()) { count += groupDTO.getConstraints().size(); } if (count == 0) { // Default details page. final Label label = new Label(I18N.CONSTANTS.projectDetailsNoDetails()); label.addStyleName(STYLE_PROJECT_LABEL_10); view.setMainPanelWidget(label); return; } final Grid gridLayout = (Grid) layout.getWidget(); final DispatchQueue queue = new DispatchQueue(dispatch, true); for (final LayoutGroupDTO groupLayout : layout.getGroups()) { // simple group if(!groupLayout.getHasIterations()) { FieldSet fieldSet = createGroupLayoutFieldSet(getProject(), groupLayout, queue, null, null, null); fieldSet.setHeadingHtml(groupLayout.getTitle()); fieldSet.setCollapsible(true); fieldSet.setBorders(true); gridLayout.setWidget(groupLayout.getRow(), groupLayout.getColumn(), fieldSet); continue; } final FieldSet fieldSet = (FieldSet) groupLayout.getWidget(); gridLayout.setWidget(groupLayout.getRow(), groupLayout.getColumn(), fieldSet); // iterative group final IterableGroupPanel tabPanel = Forms.iterableGroupPanel(dispatch, groupLayout, getProject(), ProfileUtils.isGranted(auth(), GlobalPermissionEnum.CREATE_ITERATIONS) && getProject().getCurrentAmendment() == null); tabPanel.setDelegate(this); fieldSet.add(tabPanel); tabPanel.setAutoHeight(true); tabPanel.setAutoWidth(true); tabPanel.setTabScroll(true); tabPanel.addStyleName("white-tab-body"); tabPanel.setBorders(true); tabPanel.setBodyBorder(false); Integer amendmentId; if (getProject().getCurrentAmendment() != null) { amendmentId = getProject().getCurrentAmendment().getId(); } else { amendmentId = -1; } GetLayoutGroupIterations getIterations = new GetLayoutGroupIterations(groupLayout.getId(), getProject().getId(), amendmentId); queue.add(getIterations, new CommandResultHandler<ListResult<LayoutGroupIterationDTO>>() { @Override public void onCommandFailure(final Throwable throwable) { if (Log.isErrorEnabled()) { Log.error("Error, layout group iterations not loaded.", throwable); } throw new RuntimeException(throwable); } @Override protected void onCommandSuccess(ListResult<LayoutGroupIterationDTO> result) { DispatchQueue iterationsQueue = new DispatchQueue(dispatch, true); for(final LayoutGroupIterationDTO iteration : result.getList()) { final IterableGroupItem tab = new IterableGroupItem(tabPanel, iteration.getId(), iteration.getName()); tabPanel.addIterationTab(tab); Layout tabLayout = Layouts.fitLayout(); tab.setLayout(tabLayout); FieldSet tabSet = createGroupLayoutFieldSet(getProject(), groupLayout, iterationsQueue, iteration == null ? null : iteration.getId(), tabPanel, tab); tab.add(tabSet); } iterationsQueue.start(); if(tabPanel.getItemCount() > 0) { tabPanel.setSelection(tabPanel.getItem(0)); } } }, new LoadingMask(view.getMainPanel())); fieldSet.layout(); } queue.start(); view.setMainPanelWidget(gridLayout); } @Override public IterationChange getIterationChange(int iterationId) { return iterationChanges.get(iterationId); } @Override public void setIterationChange(IterationChange iterationChange) { iterationChanges.put(iterationChange.getIterationId(), iterationChange); if (!getParentPresenter().getCurrentDisplayedPhase().isEnded()) { view.getSaveButton().enable(); } } @Override public void addIterationTabItem(int iterationId, IterableGroupItem tab) { newIterationsTabItems.put(iterationId, tab); } @Override public FieldSet createGroupLayoutFieldSet(FlexibleElementContainer container, LayoutGroupDTO groupLayout, DispatchQueue queue, final Integer iterationId, final IterableGroupPanel tabPanel, final IterableGroupItem tabItem) { final ProjectDTO project = (ProjectDTO)container; // Creates the fieldset and positions it. final FieldSet fieldSet = (FieldSet) groupLayout.getWidget(); // For each constraint in the current layout group. if (ClientUtils.isEmpty(groupLayout.getConstraints())) { return fieldSet; } for (final LayoutConstraintDTO constraintDTO : groupLayout.getConstraints()) { // Gets the element managed by this constraint. final FlexibleElementDTO elementDTO = constraintDTO.getFlexibleElementDTO(); // -- // -- DISABLED ELEMENTS // -- if(elementDTO.isDisabled()) { continue; } // -- // -- ELEMENT VALUE // -- // Retrieving the current amendment id. final Integer amendmentId; if (project.getCurrentAmendment() != null) { amendmentId = project.getCurrentAmendment().getId(); } else { amendmentId = null; } // Remote call to ask for this element value. GetValue getValue; getValue = new GetValue(project.getId(), elementDTO.getId(), elementDTO.getEntityName(), amendmentId, iterationId); queue.add(getValue, new CommandResultHandler<ValueResult>() { @Override public void onCommandFailure(final Throwable throwable) { if (Log.isErrorEnabled()) { Log.error("Error, element value not loaded.", throwable); } throw new RuntimeException(throwable); } @Override public void onCommandSuccess(final ValueResult valueResult) { if (Log.isDebugEnabled()) { Log.debug("Element value(s) object : " + valueResult); } // -- // -- ELEMENT COMPONENT // -- // Configures the flexible element for the current application state before generating its component. elementDTO.setService(dispatch); elementDTO.setAuthenticationProvider(injector.getAuthenticationProvider()); elementDTO.setEventBus(eventBus); elementDTO.setCache(injector.getClientCache()); elementDTO.setCurrentContainerDTO(project); elementDTO.setTransfertManager(injector.getTransfertManager()); elementDTO.assignValue(valueResult); elementDTO.setTabPanel(tabPanel); final ProjectPresenter projectPresenter = injector.getProjectPresenter(); // Generates element component (with the value). elementDTO.init(); final Component elementComponent = elementDTO.getElementComponent(valueResult); if(elementDTO.getAmendable() && projectPresenter.projectIsLocked() && projectPresenter.canUnlockProject() && !ProfileUtils.isGranted(auth(), GlobalPermissionEnum.MODIFY_LOCKED_CONTENT)) { projectPresenter.addUnlockProjectPopup(elementDTO, elementComponent, new LoadingMask(view.getMainPanel())); } // Component width. final FormData formData; if (elementDTO.getPreferredWidth() == 0) { formData = new FormData("100%"); } else { formData = new FormData(elementDTO.getPreferredWidth(), -1); } if (elementComponent != null) { fieldSet.add(elementComponent, formData); } fieldSet.layout(); // -- // -- ELEMENT HANDLERS // -- // Adds a value change handler if this element is a dependency of a ComputationElementDTO. computationTriggerManager.listenToValueChangesOfElement(elementDTO, elementComponent, valueChanges); // Adds a value change handler to this element. elementDTO.addValueHandler(new ValueHandler() { @Override public void onValueChange(final ValueEvent event) { if(tabPanel != null) { event.setIterationId(tabPanel.getCurrentIterationId()); } // TODO: Find linked computation fields if any and recompute the value. // Stores the change to be saved later. valueChanges.add(event); if (!getParentPresenter().getCurrentDisplayedPhase().isEnded()) { // Enables the save action. view.getSaveButton().enable(); } } }); if(elementDTO.getValidates() && tabItem != null) { tabItem.setElementValidity(elementDTO, elementDTO.isCorrectRequiredValue(valueResult)); tabItem.refreshTitle(); elementDTO.addRequiredValueHandler(new RequiredValueHandlerImpl(elementDTO)); } } }, new LoadingMask(view.getMainPanel())); } fieldSet.setCollapsible(false); fieldSet.setAutoHeight(true); fieldSet.setBorders(false); fieldSet.setHeadingHtml(""); return fieldSet; } /** * Internal class handling the value changes of the required elements. */ private class RequiredValueHandlerImpl implements RequiredValueHandler { private final FlexibleElementDTO elementDTO; public RequiredValueHandlerImpl(FlexibleElementDTO elementDTO) { this.elementDTO = elementDTO; } @Override public void onRequiredValueChange(RequiredValueEvent event) { // Refresh the panel's header elementDTO.getTabPanel().setElementValidity(elementDTO, event.isValueOn()); elementDTO.getTabPanel().validateElements(); } } /** * Method executed on save button action. */ private void onSaveAction() { // Checks if there are any changes regarding layout group iterations dispatch.execute(new UpdateLayoutGroupIterations(new ArrayList<IterationChange>(iterationChanges.values()), getProject().getId()), new CommandResultHandler<ListResult<IterationChange>>() { @Override public void onCommandFailure(final Throwable caught) { N10N.error(I18N.CONSTANTS.save(), I18N.CONSTANTS.saveError()); } @Override protected void onCommandSuccess(ListResult<IterationChange> result) { for(IterationChange iterationChange : result.getList()) { if(iterationChange.isDeleted()) { // remove corresponding valueEvents Iterator<ValueEvent> valuesIterator = valueChanges.iterator(); while(valuesIterator.hasNext()) { ValueEvent valueEvent = valuesIterator.next(); if(valueEvent.getIterationId() == iterationChange.getIterationId()) { valuesIterator.remove(); } } } else if(iterationChange.isCreated()) { // change ids in valueEvents int oldId = iterationChange.getIterationId(); int newId = iterationChange.getNewIterationId(); // updating tabitem id newIterationsTabItems.get(oldId).setIterationId(newId); for(ValueEvent valueEvent : valueChanges) { if(valueEvent.getIterationId() == oldId) { valueEvent.setIterationId(newId); } } } } iterationChanges.clear(); newIterationsTabItems.clear(); dispatch.execute(new UpdateProject(getProject().getId(), valueChanges), new CommandResultHandler<VoidResult>() { @Override public void onCommandFailure(final Throwable caught) { N10N.error(I18N.CONSTANTS.save(), I18N.CONSTANTS.saveError()); } @Override public void onCommandSuccess(final VoidResult result) { N10N.infoNotif(I18N.CONSTANTS.infoConfirmation(), I18N.CONSTANTS.saveConfirm()); // Checks if there is any update needed to the local project instance. boolean refreshBanner = false; boolean coreVersionUpdated = false; ProjectDTO newProject = null; for (final ValueEvent event : valueChanges) { if (event.getSource() instanceof DefaultFlexibleElementDTO) { newProject = updateCurrentProject(((DefaultFlexibleElementDTO) event.getSource()), event.getSingleValue(), event.isProjectCountryChanged()); getParentPresenter().setCurrentProject(newProject); refreshBanner = true; } coreVersionUpdated |= event.getSourceElement().getAmendable(); } valueChanges.clear(); if (refreshBanner) { eventBus.fireEvent(new UpdateEvent(UpdateEvent.PROJECT_BANNER_UPDATE)); } if(coreVersionUpdated) { eventBus.fireEvent(new UpdateEvent(UpdateEvent.CORE_VERSION_UPDATED)); } // Avoid tight coupling with other project events. // FIXME (from v1.3) eventBus.fireEvent(new ProjectEvent(ProjectEvent.CHANGED, getProject().getId())); eventBus.fireEvent(new UpdateEvent(UpdateEvent.VALUE_UPDATE, getProject())); if (newProject != null) { load(newProject.getProjectModel().getProjectDetails()); } } }, view.getSaveButton(), new LoadingMask(view.getMainPanel())); } }, view.getSaveButton(), new LoadingMask(view.getMainPanel())); } /** * Updates locally the DTO to avoid a remote server call. * * @param element * The default flexible element. * @param value * The new value. * @param isProjectCountryChanged * If the the project country has changed. */ private ProjectDTO updateCurrentProject(final DefaultFlexibleElementDTO element, final String value, final boolean isProjectCountryChanged) { final ProjectDTO currentProjectDTO = getProject(); switch (element.getType()) { case CODE: currentProjectDTO.setName(value); break; case TITLE: currentProjectDTO.setFullName(value); break; case START_DATE: if ("".equals(value)) { currentProjectDTO.setStartDate(null); } else { try { final long timestamp = Long.parseLong(value); currentProjectDTO.setStartDate(new Date(timestamp)); } catch (NumberFormatException e) { // nothing, invalid date. } } break; case END_DATE: if ("".equals(value)) { currentProjectDTO.setEndDate(null); } else { try { final long timestamp = Long.parseLong(value); currentProjectDTO.setEndDate(new Date(timestamp)); } catch (NumberFormatException e) { // nothing, invalid date. } } break; case BUDGET: try { final BudgetElementDTO budgetElement = (BudgetElementDTO) element; final Map<Integer, String> values = ValueResultUtils.splitMapElements(value); double plannedBudget = 0.0; double spendBudget = 0.0; double receivedBudget = 0.0; for (final BudgetSubFieldDTO bf : budgetElement.getBudgetSubFields()) { if (bf.getType() != null) { switch (bf.getType()) { case PLANNED: plannedBudget = Double.parseDouble(values.get(bf.getId())); break; case RECEIVED: receivedBudget = Double.parseDouble(values.get(bf.getId())); break; case SPENT: spendBudget = Double.parseDouble(values.get(bf.getId())); break; default: break; } } } currentProjectDTO.setPlannedBudget(plannedBudget); currentProjectDTO.setSpendBudget(spendBudget); currentProjectDTO.setReceivedBudget(receivedBudget); /** * Update funding projects - Reflect to funded project in funding projects currentProjectDTO |-- getFunding() * → <ProjectFundingDTO> // list of funding projects |-- getPercentage() // no updates from here |-- * getFunded() → ProjectDTOLight // funded project details light |--getPlannedBudget() // update budget * details */ final List<ProjectFundingDTO> fundingProjects = currentProjectDTO.getFunding(); if (ClientUtils.isNotEmpty(fundingProjects)) { for (final ProjectFundingDTO projectFundingDTO : fundingProjects) { final ProjectDTO fundedProject = projectFundingDTO.getFunded(); if (fundedProject != null && fundedProject.getId().equals(currentProjectDTO.getId())) { fundedProject.setPlannedBudget(plannedBudget); fundedProject.setSpendBudget(spendBudget); fundedProject.setReceivedBudget(receivedBudget); } } } } catch (Exception e) { // nothing, invalid budget. } break; case COUNTRY: final CountryDTO country = element.getCountriesStore().findModel(EntityDTO.ID, Integer.parseInt(value)); if (country != null) { currentProjectDTO.setCountry(country); } else { // nothing, invalid country. } break; case OWNER: // The owner component doesn't fire any event for now. break; case MANAGER: final UserDTO manager = element.getManagersStore().findModel(EntityDTO.ID, Integer.parseInt(value)); if (manager != null) { currentProjectDTO.setManager(manager); } else { // nothing, invalid user. } break; case ORG_UNIT: currentProjectDTO.setOrgUnitId(Integer.parseInt(value)); if (isProjectCountryChanged) { dispatch.execute(new GetOrgUnit(currentProjectDTO.getOrgUnitId(), OrgUnitDTO.Mode.BASE), new CommandResultHandler<OrgUnitDTO>() { @Override public void onCommandSuccess(final OrgUnitDTO result) { if (result != null) { currentProjectDTO.setCountry(result.getCountry()); } } }); } break; default: // Nothing, unknown type. break; } return currentProjectDTO; } }