package org.sigmah.client.ui.presenter.project.dashboard; /* * #%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.Collections; import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; 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.ui.notif.ConfirmCallback; import org.sigmah.client.ui.notif.N10N; import org.sigmah.client.ui.presenter.base.AbstractPresenter; import org.sigmah.client.ui.res.icon.IconImageBundle; import org.sigmah.client.ui.view.base.ViewInterface; import org.sigmah.client.ui.view.project.dashboard.PhasesView; 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.ChangePhase; import org.sigmah.shared.command.GetLayoutGroupIterations; 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.PhaseDTO; import org.sigmah.shared.dto.PhaseModelDTO; import org.sigmah.shared.dto.ProjectDTO; 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.LayoutGroupDTO; import org.sigmah.shared.dto.layout.LayoutGroupIterationDTO; 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.BaseEvent; import com.extjs.gxt.ui.client.event.BoxComponentEvent; import com.extjs.gxt.ui.client.event.ButtonEvent; import com.extjs.gxt.ui.client.event.ComponentEvent; import com.extjs.gxt.ui.client.event.Events; import com.extjs.gxt.ui.client.event.Listener; import com.extjs.gxt.ui.client.event.MenuEvent; import com.extjs.gxt.ui.client.event.SelectionListener; import com.extjs.gxt.ui.client.store.ListStore; import com.extjs.gxt.ui.client.widget.Component; import com.extjs.gxt.ui.client.widget.ContentPanel; import com.extjs.gxt.ui.client.widget.LayoutContainer; import com.extjs.gxt.ui.client.widget.TabItem; import com.extjs.gxt.ui.client.widget.TabPanel; import com.extjs.gxt.ui.client.widget.form.FieldSet; import com.extjs.gxt.ui.client.widget.layout.FitLayout; import com.extjs.gxt.ui.client.widget.layout.FormData; import com.extjs.gxt.ui.client.widget.menu.Menu; import com.extjs.gxt.ui.client.widget.menu.MenuItem; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.ui.Grid; import com.google.inject.ImplementedBy; import com.google.inject.Inject; import org.sigmah.client.computation.ComputationTriggerManager; import org.sigmah.client.ui.presenter.project.ProjectPresenter; import org.sigmah.client.ui.widget.Loadable; import org.sigmah.client.util.profiler.Profiler; import org.sigmah.client.util.profiler.Scenario; import org.sigmah.shared.dispatch.FunctionalException; /** * Phases presenter. * * @author Denis Colliot (dcolliot@ideia.fr) */ public class PhasesPresenter extends AbstractPresenter<PhasesPresenter.View> implements IterableGroupPanel.Delegate { /** * Description of the view managed by this presenter. */ @ImplementedBy(PhasesView.class) public static interface View extends ViewInterface, Loadable { /** * Mask the main panel and set the mask counter. * * @param count * The mask counter. */ void mask(int count); /** * Decrements the mask counter and unmasks the main panel if the counter reaches <code>0</code>. * * @return {@code true} if the mask counter's value is {@code 0}. */ boolean unmask(); Button getButtonActivatePhase(); Button getButtonPhaseGuide(); Button getButtonSavePhase(); com.extjs.gxt.ui.client.widget.grid.Grid<FlexibleElementDTO> getGridRequiredElements(); LayoutContainer getPanelProjectModel(); LayoutContainer getPanelSelectedPhase(); TabPanel getTabPanelPhases(); void flushToolbar(); void fillToolbar(boolean changePhaseAuthorized); ContentPanel getRequiredElementContentPanel(); void layout(); } /** * List of values changes. */ private List<ValueEvent> valueChanges; /** * Counter used to keep track of loading values : * refreshActionsToolbar must be called after the loading of the last value. */ private int loadCurrentPhaseCounter; private void initCurrentPhaseCounter() { loadCurrentPhaseCounter = 0; } private void addPhaseCounter() { loadCurrentPhaseCounter++; } private void removePhaseCounter() { loadCurrentPhaseCounter--; if (loadCurrentPhaseCounter == 0) { refreshActionsToolbar(); } } private final Map<Integer, IterationChange> iterationChanges = new HashMap<Integer, IterationChange>(); private final Map<Integer, IterableGroupItem> newIterationsTabItems = new HashMap<Integer, IterableGroupItem>(); private final Set<FlexibleElementDTO> requiredElementsSet = new HashSet<FlexibleElementDTO>(); /** * Mapping between phases models ids and tabs items (to quickly get a tab). */ private Map<Integer, TabItem> tabItemsMap; /** * A map to maintain the current active phase required elements states. */ private RequiredValueStateList activePhaseRequiredElements; /** * A map to maintain the current displayed phase required elements states. */ private RequiredValueStateList currentPhaseRequiredElements; /** * Listen to the values of flexible elements to update computated values. */ @Inject private ComputationTriggerManager computationTriggerManager; /** * Presenters initialization. * * @param view * The presenter's view. * @param injector * The application injector. */ @Inject protected PhasesPresenter(final View view, final Injector injector) { super(view, injector); } /** * {@inheritDoc} */ @Override public void onBind() { valueChanges = new ArrayList<ValueEvent>(); tabItemsMap = new HashMap<Integer, TabItem>(); activePhaseRequiredElements = new RequiredValueStateList(); currentPhaseRequiredElements = new RequiredValueStateList(); } /** * Clear everything by removing all tabs and all required elements. */ public void clear() { clearChangedValues(); // Clears the required elements maps. activePhaseRequiredElements.clear(); currentPhaseRequiredElements.clear(); // Removes old tabs configuration (from the previous displayed project). view.getTabPanelPhases().removeAll(); view.getTabPanelPhases().removeAllListeners(); tabItemsMap.clear(); } /** * <p> * Refreshes the presenter with the given {@code project}. * </p> * <p> * The project's following attributes must be loaded: * <ul> * <li>Base attributes (id, name, etc.)</li> * <li>{@link ProjectDTO#PHASES}</li> * </ul> * </p> * * @param project * The loaded project DTO. */ public void refresh(final ProjectDTO project) { clearChangedValues(); view.getButtonSavePhase().disable(); setCurrentDisplayedPhase(project.getCurrentPhase()); loadProjectDashboard(project); } private ProjectDTO getCurrentProject() { return injector.getProjectPresenter().getCurrentProject(); } private void setCurrentProject(final ProjectDTO project) { injector.getProjectPresenter().setCurrentProject(project); } private PhaseDTO getCurrentDisplayedPhase() { return injector.getProjectPresenter().getCurrentDisplayedPhase(); } private void setCurrentDisplayedPhase(final PhaseDTO phase) { injector.getProjectPresenter().setCurrentDisplayedPhase(phase); } /** * Simply fires a {@link UpdateEvent#PROJECT_BANNER_UPDATE} event on the application event bus. */ private void refreshProjectBanner() { eventBus.fireEvent(new UpdateEvent(UpdateEvent.PROJECT_BANNER_UPDATE)); } public boolean hasValueChanged() { return !valueChanges.isEmpty() || !iterationChanges.isEmpty(); } public void clearChangedValues() { valueChanges.clear(); view.getButtonSavePhase().setEnabled(false); } /** * Loads the given {@code project} phases. * * @param projectDTO * The project. */ private void loadProjectDashboard(final ProjectDTO projectDTO) { // Clears the required elements maps. activePhaseRequiredElements.clear(); currentPhaseRequiredElements.clear(); // Sorts phases to be displayed in the correct order. Collections.sort(projectDTO.getPhases()); // -- // -- TABS CREATION. // -- // Removes old tabs configuration (from the previous displayed project). view.getTabPanelPhases().removeAll(); view.getTabPanelPhases().removeAllListeners(); tabItemsMap.clear(); TabItem currentPhase = null; // Creates tabs for each phase. for (final PhaseDTO phaseDTO : projectDTO.getPhases()) { // Creates the default tab. final TabItem tabItem = new TabItem(phaseDTO.getPhaseModel().getName()); tabItem.setLayout(new FitLayout()); tabItem.setEnabled(false); tabItem.setAutoHeight(true); // Map the tab item with the phase id. tabItemsMap.put(phaseDTO.getPhaseModel().getId(), tabItem); // Adds the tab to the view. view.getTabPanelPhases().add(tabItem); view.getTabPanelPhases().addListener(Events.Resize, new Listener<BoxComponentEvent>() { @Override public void handleEvent(BoxComponentEvent event) { // 25 is the default height of the tab bar. tabItem.setSize(event.getWidth(), event.getHeight() - 25); } }); // If the phase is the active one. if (isActivePhase(phaseDTO)) { // Enables it, apply the correct style and selects it. tabItem.setEnabled(true); tabItem.getHeader().addStyleName(PhasesView.PROJECT_PHASE_ACTIVE); currentPhase = tabItem; } // If the phase is ended. if (isEndedPhase(phaseDTO)) { // Enables it and apply the correct style. tabItem.setEnabled(true); tabItem.getHeader().addStyleName(PhasesView.PROJECT_PHASE_CLOSED); } } // Enables successors tabs of the current phase. enableSuccessorsTabs(); // -- // -- TABS LISTENERS. // -- // Adds tabs listeners for selection changes (must be added after tabs creation or event fired for each tab). for (final PhaseDTO phaseDTO : projectDTO.getPhases()) { final TabItem tabItem = tabItemsMap.get(phaseDTO.getPhaseModel().getId()); tabItem.addListener(Events.Select, new Listener<ComponentEvent>() { /** * Id of the phase to display.<br> * Important: it's better to manipulate the id instead of the phases instances to keep coherence after a project * update. */ private final Integer phaseDTOId = phaseDTO.getId(); private PhaseDTO retrievePhaseDTO() { // Loads the phase of the selected tab (loaded from the current project instance). for (final PhaseDTO p : getCurrentProject().getPhases()) { if (p.getId().equals(phaseDTOId)) { return p; } } return null; } @Override public void handleEvent(ComponentEvent tpe) { final PhaseDTO toDisplayPhase = retrievePhaseDTO(); if (!view.getButtonSavePhase().isEnabled() || isEndedPhase(getCurrentDisplayedPhase())) { // Load the selected phase without asking a question // if the current phase has not been modified or if it is ended. loadPhaseOnTab(toDisplayPhase); return; } // Asks the client to save the unsaved elements before switching phases. N10N.confirmation(I18N.CONSTANTS.projectPhaseChangeAlert(), I18N.CONSTANTS.projectPhaseChangeAlertDetails(), new ConfirmCallback() { @Override public void onAction() { // -- // YES CALLBACK. // -- view.getButtonSavePhase().fireEvent(Events.OnClick); if (isActivePhase(getCurrentDisplayedPhase())) { activePhaseRequiredElements.saveState(); } loadPhaseOnTab(toDisplayPhase); } }, new ConfirmCallback() { @Override public void onAction() { // -- // NO CALLBACK. // -- // If the last displayed phase was the active one, modifications are discarded then the required // elements map is cleared (to prevent inconsistent successor activation). if (isActivePhase(getCurrentDisplayedPhase())) { activePhaseRequiredElements.clearState(); } loadPhaseOnTab(toDisplayPhase); } }); } }); } view.getTabPanelPhases().setSelection(currentPhase); } /** * Returns if a phase is the current displayed phase. * * @param phaseDTO * The phase to test. * @return If the phase is currently displayed. */ private boolean isCurrentPhase(PhaseDTO phaseDTO) { final PhaseDTO currentPhaseDTO = getCurrentDisplayedPhase(); return currentPhaseDTO != null && phaseDTO != null && currentPhaseDTO.getId().equals(phaseDTO.getId()); } /** * Returns if a phase is the active phase of the current project. * * @param phaseDTO * The phase to test. * @return If the phase is active. */ private boolean isActivePhase(PhaseDTO phaseDTO) { final ProjectDTO currentProjectDTO = getCurrentProject(); return currentProjectDTO != null && currentProjectDTO.getCurrentPhase() != null && phaseDTO != null && currentProjectDTO.getCurrentPhase().getId().equals(phaseDTO.getId()); } /** * Returns if a phase is ended. * * @param phaseDTO * The phase to test. * @return If the phase is ended. */ private boolean isEndedPhase(PhaseDTO phaseDTO) { return phaseDTO != null && phaseDTO.isEnded(); } /** * Returns if the active phase of the current project is filled in. * * @return If the active phase of the current project is filled in. */ private boolean isActivePhaseFilledIn() { // Checks id the map contains only true booleans. return activePhaseRequiredElements.isTrue(); } /** * Enables the successors tabs of the current displayed phase. */ private void enableSuccessorsTabs() { for (final PhaseModelDTO successor : getCurrentProject().getCurrentPhase().getPhaseModel().getSuccessors()) { final TabItem successorTabItem = tabItemsMap.get(successor.getId()); if (successorTabItem != null) { successorTabItem.setEnabled(true); } } } /** * Loads a project phase into the selected tab panel. * * @param phaseDTO * The phase to display. */ private void loadPhaseOnTab(final PhaseDTO phaseDTO) { initCurrentPhaseCounter(); // If the element are read only. final boolean phaseIsEnded = isEndedPhase(phaseDTO); // Sets current project status. setCurrentDisplayedPhase(phaseDTO); // Clears the required elements map for the current displayed phase. currentPhaseRequiredElements.clear(); valueChanges.clear(); // -- // -- CLEARS PANELS // -- // Clears all tabs. for (final TabItem tab : view.getTabPanelPhases().getItems()) { tab.removeAll(); } // Clears panels. view.getPanelSelectedPhase().removeAll(); view.getGridRequiredElements().getStore().removeAll(); view.getTabPanelPhases().getSelectedItem().add(view.getPanelProjectModel()); // Store required elements requiredElementsSet.clear(); // -- // -- PHASE LAYOUT // -- final Grid layoutGrid = (Grid) phaseDTO.getPhaseModel().getWidget(); layoutGrid.setStyleName("flexibility-layout"); view.getPanelSelectedPhase().add(layoutGrid); // Dispatch queue ensuring results handling order. final DispatchQueue queue = new DispatchQueue(dispatch, true) { @Override protected void onComplete() { // View layouts update. // FIXME (v1.3) This should be done by Ext, not be the developer! injector.getProjectDashboardPresenter().getView().layoutView(); view.layout(); Profiler.INSTANCE.endScenario(Scenario.OPEN_PROJECT); } }; // Current project final ProjectDTO project = getCurrentProject(); // Prepare the manager of computation elements computationTriggerManager.prepareForProject(project); // For each layout group. for (final LayoutGroupDTO groupDTO : phaseDTO.getPhaseModel().getLayout().getGroups()) { // simple group if(!groupDTO.getHasIterations()) { FieldSet fieldSet = createGroupLayoutFieldSet(getCurrentProject(), groupDTO, queue, null, null, null); fieldSet.setHeadingHtml(groupDTO.getTitle()); fieldSet.setCollapsible(true); fieldSet.setBorders(true); layoutGrid.setWidget(groupDTO.getRow(), groupDTO.getColumn(), fieldSet); continue; } final FieldSet fieldSet = (FieldSet) groupDTO.getWidget(); layoutGrid.setWidget(groupDTO.getRow(), groupDTO.getColumn(), fieldSet); // iterative group final IterableGroupPanel tabPanel = Forms.iterableGroupPanel(dispatch, groupDTO, getCurrentProject(), ProfileUtils.isGranted(auth(), GlobalPermissionEnum.CREATE_ITERATIONS) && getCurrentProject().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 (getCurrentProject().getCurrentAmendment() != null) { amendmentId = getCurrentProject().getCurrentAmendment().getId(); } else { amendmentId = -1; } GetLayoutGroupIterations getIterations = new GetLayoutGroupIterations(groupDTO.getId(), getCurrentProject().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(getCurrentProject(), groupDTO, 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.getTabPanelPhases())); fieldSet.layout(); } queue.start(); } @Override public IterationChange getIterationChange(int iterationId) { return iterationChanges.get(iterationId); } @Override public void setIterationChange(IterationChange iterationChange) { iterationChanges.put(iterationChange.getIterationId(), iterationChange); if (!getCurrentDisplayedPhase().isEnded()) { view.getButtonSavePhase().enable(); } refreshSaveButtonState(); } @Override public void addIterationTabItem(int iterationId, IterableGroupItem tab) { newIterationsTabItems.put(iterationId, tab); } @Override public FieldSet createGroupLayoutFieldSet(FlexibleElementContainer container, final 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); addPhaseCounter(); queue.add(getValue, new CommandResultHandler<ValueResult>() { @Override public void onCommandFailure(final Throwable throwable) { removePhaseCounter(); 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.getTabPanelPhases())); } // 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 (!getCurrentDisplayedPhase().isEnded() || ProfileUtils.isGranted(auth(), GlobalPermissionEnum.MODIFY_LOCKED_CONTENT)) { // Enables the save action. refreshSaveButtonState(); } } }); // If this element id a required one. if (elementDTO.getValidates()) { // Adds a specific handler. //elementDTO.addRequiredValueHandler(new RequiredValueHandlerImpl(elementDTO, iterationId)); elementDTO.addRequiredValueHandler(new RequiredValueHandler() { @Override public void onRequiredValueChange(RequiredValueEvent event) { final Integer iterationId = tabPanel != null ? tabPanel.getCurrentIterationId() : null; // Map the required element for the current displayed phase. currentPhaseRequiredElements.putActual(iterationId, elementDTO.getId(), event.isValueOn()); // If the current displayed phase is the active one, // map the required element for the active phase. if (isCurrentPhase(getCurrentProject().getCurrentPhase())) { activePhaseRequiredElements.putActual(iterationId, elementDTO.getId(), event.isValueOn()); } // The element is in charge of the saving of its values. The state // of the current project must be refreshed here. if (event.isImmediate()) { view.getButtonSavePhase().fireEvent(Events.OnClick); } // Updates the element state for the new value. elementDTO.setFilledIn(currentPhaseRequiredElements.isActuallyTrue(elementDTO.getId())); view.getGridRequiredElements().getStore().update(elementDTO); // Refresh the panel's header if (iterationId != null) { elementDTO.getTabPanel().setElementValidity(elementDTO, event.isValueOn()); } refreshRequiredElementContentPanelHeader(); } }); if(tabItem != null) { tabItem.setElementValidity(elementDTO, elementDTO.isCorrectRequiredValue(valueResult)); tabItem.refreshTitle(); } // Set the groupDTO into the element elementDTO.setGroup(groupLayout); elementDTO.setConstraint(constraintDTO); // Adds the element to the tmp list for sorting requiredElementsSet.add(elementDTO); // Clear the store view.getGridRequiredElements().getStore().removeAll(); // Sorting and add the list to the view view.getGridRequiredElements().getStore().add(sortRequiredElements(new ArrayList<FlexibleElementDTO>(requiredElementsSet))); // Refresh header refreshRequiredElementContentPanelHeader(); // Map the required element for the current // displayed phase. currentPhaseRequiredElements.putSaved(iterationId, elementDTO.getId(), elementDTO.isFilledIn()); // If the current displayed phase is the active one, // map the required element for the active phase. if (isCurrentPhase(getCurrentProject().getCurrentPhase())) { activePhaseRequiredElements.putSaved(iterationId, elementDTO.getId(), elementDTO.isFilledIn()); } } removePhaseCounter(); } }, new LoadingMask(view.getTabPanelPhases())); } fieldSet.setCollapsible(false); fieldSet.setAutoHeight(true); fieldSet.setBorders(false); fieldSet.setHeadingHtml(""); return fieldSet; } /** * Refreshes the actions toolbar for the current displayed phase. */ private void refreshActionsToolbar() { // If the current displayed phase is ended, the toolbar is hidden. if (isEndedPhase(getCurrentDisplayedPhase()) && // An exception is made if the user is authorized to edit ended phases. !ProfileUtils.isGranted(auth(), GlobalPermissionEnum.MODIFY_LOCKED_CONTENT)) { // Hide the toolbar. view.flushToolbar(); return; } view.fillToolbar(ProfileUtils.isGranted(auth(), GlobalPermissionEnum.CHANGE_PHASE) && // Do not add the change phase button if the phase is already closed. !isEndedPhase(getCurrentDisplayedPhase())); // -- // -- ACTION: ACTIVATE OR CLOSE PHASE // -- final boolean enabled = activePhaseRequiredElements.isTrue(); view.getButtonActivatePhase().setEnabled(enabled); view.getButtonActivatePhase().removeAllListeners(); // If the current displayed phase is the active one or it is ended, the close action is displayed. if (isCurrentPhase(getCurrentProject().getCurrentPhase())) { view.getButtonActivatePhase().setText(I18N.CONSTANTS.projectClosePhaseButton()); view.getButtonActivatePhase().setIcon(IconImageBundle.ICONS.close()); view.getButtonActivatePhase().setTitle(enabled ? "" : I18N.CONSTANTS.projectCannotClose()); view.getButtonActivatePhase().addSelectionListener(new SelectionListener<ButtonEvent>() { @Override public void componentSelected(final ButtonEvent be) { view.getButtonActivatePhase().showMenu(); } }); // Builds the button menu to select the next phase after closing the current displayed one. final Menu successorsMenu = new Menu(); final List<PhaseDTO> successors = getCurrentProject().getSuccessors(getCurrentDisplayedPhase()); // If the current displayed phase hasn't successor, the close action ends the project. if (successors == null || successors.isEmpty()) { final MenuItem endItem = new MenuItem(I18N.CONSTANTS.projectEnd(), IconImageBundle.ICONS.activate()); endItem.addSelectionListener(new SelectionListener<MenuEvent>() { @Override public void componentSelected(MenuEvent me) { activatePhase(null, true); } }); successorsMenu.add(endItem); } // Each successor is added to the list of choices. else { for (final PhaseDTO successor : successors) { final MenuItem successorItem = new MenuItem(I18N.MESSAGES.projectActivate(successor.getPhaseModel().getName()), IconImageBundle.ICONS.activate()); successorItem.addSelectionListener(new SelectionListener<MenuEvent>() { @Override public void componentSelected(MenuEvent me) { activatePhase(successor, true); } }); successorsMenu.add(successorItem); } } view.getButtonActivatePhase().setMenu(successorsMenu); } // Else the active action is displayed. else { view.getButtonActivatePhase().setTitle(enabled ? "" : I18N.CONSTANTS.projectCannotActivate()); view.getButtonActivatePhase().setMenu(null); view.getButtonActivatePhase().setText(I18N.CONSTANTS.projectActivatePhaseButton()); view.getButtonActivatePhase().setIcon(IconImageBundle.ICONS.activate()); view.getButtonActivatePhase().addSelectionListener(new SelectionListener<ButtonEvent>() { @Override public void componentSelected(final ButtonEvent be) { activatePhase(getCurrentDisplayedPhase(), false); } }); } // -- // -- ACTION: SAVE MODIFICATIONS // -- refreshSaveButtonState(); // -- // -- ACTION: PHASE GUIDE // -- // Check guide availability. view.getButtonPhaseGuide().removeAllListeners(); if (getCurrentDisplayedPhase().getPhaseModel().isGuideAvailable()) { final String guide = getCurrentDisplayedPhase().getPhaseModel().getGuide(); view.getButtonPhaseGuide().setEnabled(true); view.getButtonPhaseGuide().setTitle(guide); view.getButtonPhaseGuide().addListener(Events.OnClick, new Listener<BaseEvent>() { @Override public void handleEvent(BaseEvent be) { Window.open(guide, "_blank", null); } }); } else { view.getButtonPhaseGuide().setEnabled(false); view.getButtonPhaseGuide().setTitle(I18N.CONSTANTS.projectPhaseGuideUnavailable()); } } private void refreshSaveButtonState() { view.getButtonSavePhase().removeAllListeners(); // Disabled unless a field is modified. if (hasValueChanged()) { view.getButtonSavePhase().setEnabled(true); view.getButtonSavePhase().addListener(Events.OnClick, new SaveListener()); } else { view.getButtonSavePhase().setEnabled(false); } } /** * This method is to update the herder of requiredElementContentPanel's header text. It computes the numbers of all * filled elements and then updates. * * @author HUZHE (zhe.hu32@gmail.com) */ private void refreshRequiredElementContentPanelHeader() { // The local sotre of all elements final ListStore<FlexibleElementDTO> listStore = view.getGridRequiredElements().getStore(); // The number of all element in the store final int requiredElementsCount = listStore.getCount(); // The number of all element that are filled in the store int filledRequiredElements = 0; for (final FlexibleElementDTO elementDTO : listStore.getModels()) { if (elementDTO.isFilledIn()) { filledRequiredElements++; } } view.getRequiredElementContentPanel().setHeadingText( I18N.CONSTANTS.projectRequiredElements() + " (" + filledRequiredElements + "/" + requiredElementsCount + ")"); } /** * Method to sort the list of all required elements list * * @param list * List to be sorted * @return List List sorted * @author HUZHE (zhe.hu32@gmail.com) */ private List<FlexibleElementDTO> sortRequiredElements(List<FlexibleElementDTO> list) { if (list.size() < 2) { return list; } Collections.sort(list, new Comparator<FlexibleElementDTO>() { @Override public int compare(FlexibleElementDTO arg0, FlexibleElementDTO arg1) { return comparePosition(arg0, arg1); } }); return list; } /** * Method to compare the exact position of the two flexible elements. * * @param o1 * The first flexible element. * @param o2 * The second flexible element * @return Respectively {@code 1}, {@code -1} or {@code 0} if {@code o1}'s position is greater than, lower than or * equal to {@code o2}'s position. * @author HUZHE (zhe.hu32@gmail.com) */ private int comparePosition(FlexibleElementDTO o1, FlexibleElementDTO o2) { int groupRow1 = o1.getGroup().getRow(); int groupColumn1 = o1.getGroup().getColumn(); int groupRow2 = o2.getGroup().getRow(); int groupColumn2 = o2.getGroup().getColumn(); // First,compare the row of group of the element if (groupRow1 > groupRow2) { return 1; } else if (groupRow1 < groupRow2) { return -1; } else {// The row of group is the same,compare the column of group if (groupColumn1 > groupColumn2) { return 1; } else if (groupColumn1 < groupColumn2) { return -1; } } // If goes this far,m2 and m2 in the same group, compare the their // positions in the group int elementPosition1 = o1.getConstraint().getSortOrder(); int elementPosition2 = o2.getConstraint().getSortOrder(); if (elementPosition1 > elementPosition2) { return 1; } else if (elementPosition1 < elementPosition2) { return -1; } else { return 0; } } /** * Activates a phase. * * @param phase * The phase to activate. * @param reload * If the current displayed phase must be reloaded. */ private void activatePhase(final PhaseDTO phase, final boolean reload) { // If the active phase required elements aren't filled, shows an alert and returns. if (!isActivePhaseFilledIn()) { N10N.warn(I18N.CONSTANTS.projectPhaseActivationError(), I18N.CONSTANTS.projectPhaseActivationErrorDetails()); return; } // If the phase to activate is null, the active phase will only be closed. if (phase == null) { // Confirms that the user wants to end the project. N10N.confirmation(I18N.CONSTANTS.projectEnd(), I18N.MESSAGES.projectEnd(getCurrentProject().getCurrentPhase().getPhaseModel().getName()), new ConfirmCallback() { @Override public void onAction() { // Activates the current displayed phase. dispatch.execute(new ChangePhase(getCurrentProject().getId(), null), new CommandResultHandler<ProjectDTO>() { @Override public void onCommandFailure(final Throwable e) { if (Log.isErrorEnabled()) { Log.error("The project hasn't be ended.", e); } N10N.warn(I18N.CONSTANTS.projectEndError(), I18N.CONSTANTS.projectEndErrorDetails()); } @Override public void onCommandSuccess(final ProjectDTO result) { if (Log.isDebugEnabled()) { Log.debug("Project successfully ended."); } // Sets the new current project (after update). setCurrentProject(result); // Sets the new current displayed phase (not necessary the active one). for (final PhaseDTO phase : getCurrentProject().getPhases()) { if (phase.getId().equals(getCurrentDisplayedPhase().getId())) { setCurrentDisplayedPhase(phase); } } refreshDashboardAfterUpdate(reload); } }); } }); } // Else the active will be closed and the new phase will be activated. else { // Confirms that the user wants to close the active phase and activate the given one. N10N.confirmation(I18N.CONSTANTS.projectCloseAndActivate(), I18N.MESSAGES.projectCloseAndActivate(getCurrentProject().getCurrentPhase().getPhaseModel().getName(), phase.getPhaseModel().getName()), new ConfirmCallback() { @Override public void onAction() { // Activates the current displayed phase. dispatch.execute(new ChangePhase(getCurrentProject().getId(), phase.getId()), new CommandResultHandler<ProjectDTO>() { @Override public void onCommandFailure(final Throwable e) { if (Log.isErrorEnabled()) { Log.error("The phase #" + phase.getId() + " hasn't be activated.", e); } N10N.warn(I18N.CONSTANTS.projectActivatePhaseError(), I18N.CONSTANTS.projectActivatePhaseErrorDetails()); } @Override public void onCommandSuccess(final ProjectDTO result) { if (Log.isDebugEnabled()) { Log.debug("Phase #" + phase.getId() + " successfully activated."); } // Sets the new current project (after update). setCurrentProject(result); // Sets the new current displayed phase (not necessary the active one). for (final PhaseDTO phase : getCurrentProject().getPhases()) { if (phase.getId().equals(getCurrentDisplayedPhase().getId())) { setCurrentDisplayedPhase(phase); } } refreshDashboardAfterUpdate(reload); } }); } }); } } /** * Refreshes the dashboard after an update of the project instance. * * @param reload * If the current displayed phase must be reloaded. */ private void refreshDashboardAfterUpdate(boolean reload) { if (Log.isDebugEnabled()) { Log.debug("Refreshes the project dashboard."); } // Map the required element for the active phase from the current displayed phase map. activePhaseRequiredElements.clear(); activePhaseRequiredElements.putAll(currentPhaseRequiredElements); // -- // -- BANNER // -- refreshProjectBanner(); // -- // -- TOOLBAR // -- refreshActionsToolbar(); // -- // -- UPDATES TABS // -- // Updates closed phases styles. for (final PhaseDTO phase : getCurrentProject().getPhases()) { final TabItem successorTabItem = tabItemsMap.get(phase.getPhaseModel().getId()); if (phase.isEnded()) { successorTabItem.getHeader().addStyleName(PhasesView.PROJECT_PHASE_CLOSED); } } // Updates active phase styles. for (final TabItem item : view.getTabPanelPhases().getItems()) { item.getHeader().removeStyleName(PhasesView.PROJECT_PHASE_ACTIVE); } final PhaseDTO phase; if ((phase = getCurrentProject().getCurrentPhase()) != null) { // Updates active phase styles. tabItemsMap.get(phase.getPhaseModel().getId()).getHeader().addStyleName(PhasesView.PROJECT_PHASE_ACTIVE); // Enables successors tabs of the current phase. enableSuccessorsTabs(); } if (reload) { loadPhaseOnTab(getCurrentDisplayedPhase()); } } /** * Internal class handling the modifications saving. */ private class SaveListener implements Listener<ButtonEvent> { @Override public void handleEvent(final ButtonEvent be) { view.getButtonSavePhase().disable(); // Checks if there are any changes regarding layout group iterations dispatch.execute(new UpdateLayoutGroupIterations(new ArrayList<IterationChange>(iterationChanges.values()), getCurrentProject().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(getCurrentProject().getId(), valueChanges), new CommandResultHandler<VoidResult>() { @Override public void onCommandFailure(final Throwable caught) { N10N.warn(I18N.CONSTANTS.save(), I18N.CONSTANTS.saveError()); currentPhaseRequiredElements.clearState(); if (isActivePhase(getCurrentDisplayedPhase())) { activePhaseRequiredElements.clearState(); } } @Override protected void onFunctionalException(FunctionalException exception) { super.onFunctionalException(exception); view.getButtonSavePhase().setEnabled(true); } @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; for (final ValueEvent event : valueChanges) { if (event.getSource() instanceof DefaultFlexibleElementDTO) { updateCurrentProject(((DefaultFlexibleElementDTO) event.getSource()), event.getSingleValue()); refreshBanner = true; } coreVersionUpdated |= event.getSourceElement().getAmendable(); } clearChangedValues(); currentPhaseRequiredElements.saveState(); if (isActivePhase(getCurrentDisplayedPhase())) { activePhaseRequiredElements.saveState(); } refreshActionsToolbar(); if (refreshBanner) { refreshProjectBanner(); } if(coreVersionUpdated) { eventBus.fireEvent(new UpdateEvent(UpdateEvent.CORE_VERSION_UPDATED)); } } }, new LoadingMask(view.getTabPanelPhases())); } }, view.getButtonSavePhase(), new LoadingMask(view.getPanelSelectedPhase())); } } /** * Updates locally the DTO to avoid a remote server call. * * @param element * The default flexible element. * @param value * The new value. */ private void updateCurrentProject(final DefaultFlexibleElementDTO element, final String value) { final ProjectDTO currentProjectDTO = getCurrentProject(); 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 (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); } 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)); break; default: // Nothing, unknown type. break; } } }