org.sigmah.client.page.project.reports.ProjectReportsView.java Source code

Java tutorial

Introduction

Here is the source code for org.sigmah.client.page.project.reports.ProjectReportsView.java

Source

/*
 * All Sigmah code is released under the GNU General Public License v3 See COPYRIGHT.txt and LICENSE.txt.
 */
package org.sigmah.client.page.project.reports;

import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.sigmah.client.EventBus;
import org.sigmah.client.dispatch.AsyncCallbacks;
import org.sigmah.client.dispatch.Dispatcher;
import org.sigmah.client.dispatch.remote.Authentication;
import org.sigmah.client.event.NavigationEvent;
import org.sigmah.client.i18n.I18N;
import org.sigmah.client.icon.IconImageBundle;
import org.sigmah.client.page.NavigationHandler;
import org.sigmah.client.page.project.ProjectState;
import org.sigmah.client.page.project.reports.images.ToolbarImages;
import org.sigmah.client.ui.FoldPanel;
import org.sigmah.client.util.DateUtils;
import org.sigmah.client.util.Notification;
import org.sigmah.shared.command.CreateEntity;
import org.sigmah.shared.command.GetProjectReport;
import org.sigmah.shared.command.PromoteProjectReportDraft;
import org.sigmah.shared.command.RemoveProjectReportDraft;
import org.sigmah.shared.command.UpdateEntity;
import org.sigmah.shared.command.result.CreateResult;
import org.sigmah.shared.command.result.VoidResult;
import org.sigmah.shared.domain.profile.GlobalPermissionEnum;
import org.sigmah.shared.dto.ExportUtils;
import org.sigmah.shared.dto.profile.ProfileUtils;
import org.sigmah.shared.dto.report.KeyQuestionDTO;
import org.sigmah.shared.dto.report.ProjectReportContent;
import org.sigmah.shared.dto.report.ProjectReportDTO;
import org.sigmah.shared.dto.report.ProjectReportSectionDTO;
import org.sigmah.shared.dto.report.ReportReference;
import org.sigmah.shared.dto.report.RichTextElementDTO;
import org.sigmah.shared.dto.value.FileUploadUtils;

import com.allen_sauer.gwt.log.client.Log;
import com.extjs.gxt.ui.client.Style.HorizontalAlignment;
import com.extjs.gxt.ui.client.Style.LayoutRegion;
import com.extjs.gxt.ui.client.Style.Scroll;
import com.extjs.gxt.ui.client.event.BaseEvent;
import com.extjs.gxt.ui.client.event.ButtonEvent;
import com.extjs.gxt.ui.client.event.Events;
import com.extjs.gxt.ui.client.event.FormEvent;
import com.extjs.gxt.ui.client.event.Listener;
import com.extjs.gxt.ui.client.event.SelectionListener;
import com.extjs.gxt.ui.client.store.ListStore;
import com.extjs.gxt.ui.client.util.Margins;
import com.extjs.gxt.ui.client.widget.ContentPanel;
import com.extjs.gxt.ui.client.widget.Dialog;
import com.extjs.gxt.ui.client.widget.Label;
import com.extjs.gxt.ui.client.widget.LayoutContainer;
import com.extjs.gxt.ui.client.widget.MessageBox;
import com.extjs.gxt.ui.client.widget.button.Button;
import com.extjs.gxt.ui.client.widget.button.ToolButton;
import com.extjs.gxt.ui.client.widget.form.FormPanel;
import com.extjs.gxt.ui.client.widget.form.FormPanel.Encoding;
import com.extjs.gxt.ui.client.widget.form.FormPanel.Method;
import com.extjs.gxt.ui.client.widget.form.HiddenField;
import com.extjs.gxt.ui.client.widget.form.TextField;
import com.extjs.gxt.ui.client.widget.grid.ColumnConfig;
import com.extjs.gxt.ui.client.widget.grid.ColumnData;
import com.extjs.gxt.ui.client.widget.grid.ColumnModel;
import com.extjs.gxt.ui.client.widget.grid.Grid;
import com.extjs.gxt.ui.client.widget.grid.GridCellRenderer;
import com.extjs.gxt.ui.client.widget.layout.BorderLayout;
import com.extjs.gxt.ui.client.widget.layout.BorderLayoutData;
import com.extjs.gxt.ui.client.widget.layout.FitLayout;
import com.extjs.gxt.ui.client.widget.layout.FormLayout;
import com.extjs.gxt.ui.client.widget.toolbar.SeparatorToolItem;
import com.extjs.gxt.ui.client.widget.toolbar.ToolBar;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.FormElement;
import com.google.gwt.dom.client.InputElement;
import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ChangeHandler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.FocusEvent;
import com.google.gwt.event.dom.client.FocusHandler;
import com.google.gwt.i18n.client.DateTimeFormat;
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.AbstractImagePrototype;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HasHorizontalAlignment;
import com.google.gwt.user.client.ui.HasVerticalAlignment;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.RichTextArea;
import com.google.gwt.user.client.ui.RootPanel;

/**
 * Displays the reports attached to a project.
 * 
 * @author Raphal Calabro (rcalabro@ideia.fr)
 */
@SuppressWarnings({ "unchecked", "rawtypes" })
public class ProjectReportsView extends LayoutContainer {

    /**
     * Time in milliseconds between each autosave.
     */
    public final static int AUTO_SAVE_PERIOD = 120000;

    private Authentication authentication;
    private EventBus eventBus;
    private Dispatcher dispatcher;

    private ProjectState currentState;
    private String phaseName;

    private ListStore<ReportReference> store;
    private LayoutContainer mainPanel;
    private RichTextArea.Formatter[] globalFormatterArray = new RichTextArea.Formatter[1];

    // private ProjectReportDTO currentReport;
    private int currentReportId = -1;
    private HashMap<Integer, RichTextArea> textAreas;
    private HashMap<Integer, String> oldContents;
    private KeyQuestionState keyQuestionState;

    private Button attachButton;
    private Button createReportButton;

    private Timer autoSaveTimer;

    public ProjectReportsView(Authentication authentication, EventBus eventBus, Dispatcher dispatcher,
            ListStore<ReportReference> store) {

        this.authentication = authentication;
        this.eventBus = eventBus;
        this.dispatcher = dispatcher;
        this.textAreas = new HashMap<Integer, RichTextArea>();
        this.oldContents = new HashMap<Integer, String>();

        this.store = store;

        this.keyQuestionState = new KeyQuestionState();

        final BorderLayout layout = new BorderLayout();
        // Adding a dark background between objects managed by this layout.
        layout.setContainerStyle("x-border-layout-ct main-background");

        setLayout(layout);

        // Layout data object used to define constraints for every widget.
        BorderLayoutData layoutData;

        final ContentPanel documentList = createDocumentList(store);
        layoutData = new BorderLayoutData(LayoutRegion.WEST, 400);
        layoutData.setCollapsible(true);
        add(documentList, layoutData);

        mainPanel = createMainPanel();
        layoutData = new BorderLayoutData(LayoutRegion.CENTER);
        layoutData.setMargins(new Margins(0, 0, 0, 8));
        add(mainPanel, layoutData);
    }

    /**
     * Creates left panel of the view. It displays the document list.
     * 
     * @param store
     *            The document store to use.
     * @return The document list panel.
     */
    private ContentPanel createDocumentList(final ListStore<ReportReference> store) {
        final ContentPanel panel = new ContentPanel(new FitLayout());
        panel.setHeading(I18N.CONSTANTS.projectTabReports());

        // Toolbar
        final ToolBar toolBar = new ToolBar();
        toolBar.setAlignment(HorizontalAlignment.RIGHT);
        final IconImageBundle icons = GWT.create(IconImageBundle.class);

        createReportButton = new Button(I18N.CONSTANTS.reportCreateReport(), icons.add());

        attachButton = new Button(I18N.CONSTANTS.flexibleElementFilesListAddDocument(), icons.attach());

        if (ProfileUtils.isGranted(authentication, GlobalPermissionEnum.EDIT_PROJECT)) {
            toolBar.add(attachButton);
            toolBar.add(new SeparatorToolItem());
            toolBar.add(createReportButton);
        }

        panel.setTopComponent(toolBar);

        // Report list
        final ColumnConfig editDate = new ColumnConfig("lastEditDate", I18N.CONSTANTS.reportLastEditDate(), 200);
        editDate.setDateTimeFormat(DateUtils.DATE_SHORT);
        final ColumnConfig editorName = new ColumnConfig("editorName", I18N.CONSTANTS.reportEditor(), 200);
        final ColumnConfig iconColumn = new ColumnConfig("icon", "", 20);
        final ColumnConfig reportName = new ColumnConfig("name", I18N.CONSTANTS.reportName(), 200);
        final ColumnConfig typeColumn = new ColumnConfig("flexibleElementLabel", I18N.CONSTANTS.reportType(), 200);
        final ColumnConfig phaseNameColumn = new ColumnConfig("phaseName", I18N.CONSTANTS.reportPhase(), 200);
        final ColumnModel reportColumnModel = new ColumnModel(
                Arrays.asList(editDate, editorName, iconColumn, reportName, typeColumn, phaseNameColumn));

        iconColumn.setRenderer(new GridCellRenderer<ReportReference>() {

            @Override
            public Object render(ReportReference model, String property, ColumnData config, int rowIndex,
                    int colIndex, ListStore<ReportReference> store, Grid<ReportReference> grid) {

                if (model.isDocument()) {
                    return IconImageBundle.ICONS.attach().createImage();
                } else {
                    return IconImageBundle.ICONS.report().createImage();
                }
            }
        });

        reportName.setRenderer(new GridCellRenderer<ReportReference>() {

            @Override
            public Object render(final ReportReference model, String property, ColumnData config, int rowIndex,
                    int colIndex, ListStore store, Grid grid) {

                if (model.isDocument()) {

                    // Uses a "hidden form" to manages the downloads to be able
                    // to catch server errors.
                    final FormPanel downloadFormPanel = new FormPanel();
                    downloadFormPanel.setBodyBorder(false);
                    downloadFormPanel.setHeaderVisible(false);
                    downloadFormPanel.setPadding(0);
                    downloadFormPanel.setEncoding(Encoding.URLENCODED);
                    downloadFormPanel.setMethod(Method.GET);
                    downloadFormPanel.setAction(GWT.getModuleBaseURL() + "download");

                    final com.google.gwt.user.client.ui.Label downloadButton = new com.google.gwt.user.client.ui.Label(
                            (String) model.get(property));
                    downloadButton.addStyleName("flexibility-action");

                    // File's id.
                    final HiddenField<String> fileIdHidden = new HiddenField<String>();
                    fileIdHidden.setName(FileUploadUtils.DOCUMENT_ID);
                    fileIdHidden.setValue(String.valueOf(model.getId()));

                    downloadFormPanel.add(downloadButton);
                    downloadFormPanel.add(fileIdHidden);

                    // Submit complete handler to catch server errors.
                    downloadFormPanel.addListener(Events.Submit, new Listener<FormEvent>() {

                        @Override
                        public void handleEvent(FormEvent be) {

                            if (!"".equals(be.getResultHtml())) {
                                MessageBox.info(I18N.CONSTANTS.flexibleElementFilesListDownloadError(),
                                        I18N.CONSTANTS.flexibleElementFilesListDownloadErrorDetails(), null);
                            }
                        }
                    });

                    // Buttons listeners.
                    downloadButton.addClickHandler(new ClickHandler() {

                        @Override
                        public void onClick(ClickEvent e) {

                            // Submits the form.
                            downloadFormPanel.submit();
                        }
                    });

                    return downloadFormPanel;

                } else {

                    final Anchor link = new Anchor((String) model.get(property));
                    link.addStyleName("flexibility-action");

                    link.addClickHandler(new ClickHandler() {

                        @Override
                        public void onClick(ClickEvent event) {
                            // Opening a report

                            if (model.getId() != currentReportId) {
                                setReport(null); // Closing the current report
                                mainPanel.mask(I18N.CONSTANTS.loading());

                                final ProjectState state = new ProjectState(currentState.getProjectId());
                                state.setCurrentSection(currentState.getCurrentSection());
                                state.setArgument(model.getId().toString());

                                eventBus.fireEvent(
                                        new NavigationEvent(NavigationHandler.NavigationRequested, state, null));

                            } else {
                                Notification.show(I18N.CONSTANTS.projectTabReports(),
                                        I18N.CONSTANTS.reportAlreadyOpened());
                            }
                        }
                    });

                    return link;
                }
            }
        });

        final Grid<ReportReference> documentGrid = new Grid<ReportReference>(store, reportColumnModel);
        documentGrid.setAutoExpandColumn("name");
        documentGrid.getView().setForceFit(true);

        panel.add(documentGrid);

        return panel;
    }

    private LayoutContainer createMainPanel() {
        final LayoutContainer panel = new LayoutContainer(new BorderLayout());
        return panel;
    }

    public void setCurrentState(ProjectState currentState) {
        this.currentState = currentState;
    }

    private void displaySection(final ProjectReportSectionDTO section, final FoldPanel parent,
            final StringBuilder prefix, int level, final boolean draftMode) {

        final FoldPanel sectionPanel = new FoldPanel();
        sectionPanel.setHeading(prefix.toString() + ' ' + section.getName());
        sectionPanel.addStyleName("project-report-level-" + level);

        int index = 1;
        int prefixLength = prefix.length();
        for (final ProjectReportContent object : section.getChildren()) {
            if (object.getClass() == ProjectReportSectionDTO.class) {
                prefix.append(index).append('.');

                displaySection((ProjectReportSectionDTO) object, sectionPanel, prefix, level + 1, draftMode);
                index++;

                prefix.setLength(prefixLength);

            } else if (object.getClass() == RichTextElementDTO.class) {

                if (draftMode) {
                    final RichTextArea textArea = new RichTextArea();
                    textArea.setHTML(((RichTextElementDTO) object).getText());

                    textArea.addFocusHandler(new FocusHandler() {

                        @Override
                        public void onFocus(FocusEvent event) {
                            globalFormatterArray[0] = textArea.getFormatter();
                        }
                    });

                    sectionPanel.add(textArea);
                    textAreas.put(((RichTextElementDTO) object).getId(), textArea);
                    oldContents.put(((RichTextElementDTO) object).getId(), textArea.getText());

                } else {
                    final HTML html = new HTML();

                    final String value = ((RichTextElementDTO) object).getText();
                    if (value == null || "".equals(value)) {
                        html.setText(I18N.CONSTANTS.reportEmptySection());
                        html.addStyleName("project-report-field-empty");

                    } else {
                        html.setHTML(value);
                        html.addStyleName("project-report-field");
                    }

                    sectionPanel.add(html);
                }

            } else if (object.getClass() == KeyQuestionDTO.class) {
                final KeyQuestionDTO keyQuestion = (KeyQuestionDTO) object;
                keyQuestionState.increaseCount();

                keyQuestion.setNumber(keyQuestionState.getCount());

                // Rich text field
                final RichTextArea textArea = new RichTextArea();
                final RichTextElementDTO richTextElementDTO = keyQuestion.getRichTextElementDTO();
                if (richTextElementDTO != null) {
                    textArea.setHTML(richTextElementDTO.getText());
                    textAreas.put(richTextElementDTO.getId(), textArea);
                    oldContents.put(richTextElementDTO.getId(), textArea.getText());

                } else {
                    Log.error("No text area is attached to the key question #" + keyQuestion.getId());
                }

                // Compas icon
                final ToolbarImages images = GWT.create(ToolbarImages.class);

                final ImageResource icon;
                if ("".equals(textArea.getText()))
                    icon = images.compasRed();
                else {
                    icon = images.compasGreen();
                    keyQuestionState.increaseValids();
                }

                final int toolButtonIndex = sectionPanel.getToolButtonCount();

                sectionPanel.addToolButton(icon, new ClickHandler() {

                    @Override
                    public void onClick(ClickEvent event) {
                        KeyQuestionDialog.getDialog(keyQuestion, textArea, sectionPanel, toolButtonIndex,
                                keyQuestionState, draftMode).show();
                    }
                });

            } else {
                Log.warn("Report : object type unknown (" + object.getClass() + ")");
            }
        }

        parent.add(sectionPanel);
    }

    public void setReport(final ProjectReportDTO report) {
        mainPanel.removeAll();
        // currentReport = report;

        if (autoSaveTimer != null) {
            autoSaveTimer.cancel();
            autoSaveTimer = null;
        }

        if (report == null) {
            currentReportId = -1;
            return;
        }

        currentReportId = report.getId();

        // Preparing the view for the new report
        textAreas.clear();
        oldContents.clear();
        keyQuestionState.clear();

        // Title bar
        final ContentPanel reportPanel = new ContentPanel(new FitLayout());
        reportPanel.setScrollMode(Scroll.AUTOY);
        reportPanel.setHeading(report.getName());

        final ToolButton closeButton = new ToolButton("x-tool-close");
        closeButton.addListener(Events.Select, new Listener<BaseEvent>() {

            @Override
            public void handleEvent(BaseEvent be) {
                final ProjectState state = new ProjectState(currentState.getProjectId());
                state.setCurrentSection(currentState.getCurrentSection());
                state.setArgument("-1");

                eventBus.fireEvent(new NavigationEvent(NavigationHandler.NavigationRequested, state, null));
            }
        });
        reportPanel.getHeader().addTool(closeButton);

        // Report container
        final FlowPanel flowPanel = new FlowPanel();

        // Report
        final FoldPanel root = new FoldPanel();
        root.addStyleName("project-report");

        final List<ProjectReportSectionDTO> sections = report.getSections();

        final StringBuilder prefix = new StringBuilder();

        for (int index = 0; index < sections.size(); index++) {
            final ProjectReportSectionDTO section = sections.get(index);

            prefix.append(index + 1).append('.');
            displaySection(section, root, prefix, 1, report.isDraft());

            prefix.setLength(0);
        }

        // Toolbar
        final ToolBar toolBar = new ToolBar();

        final IconImageBundle icons = GWT.create(IconImageBundle.class);

        if (report.isDraft()) {
            // Draft banner
            final HorizontalPanel header = new HorizontalPanel();
            header.addStyleName("project-report-draft");

            // The "Personal Draft"
            final Label personalDraft = new Label(I18N.MESSAGES.personalDraft());
            personalDraft.addStyleName("project-report-personalDraft");

            final DateTimeFormat dateFormat = DateTimeFormat.getMediumDateFormat();
            final DateTimeFormat timeFormat = DateTimeFormat.getMediumTimeFormat();

            // The label showing the last changed time
            final Label draftLastChangedTime = new Label(I18N.MESSAGES.reportDraftLastChanged(
                    dateFormat.format(report.getLastEditDate()), timeFormat.format(report.getLastEditDate())));

            // Add the two labels
            header.add(personalDraft);
            header.add(draftLastChangedTime);

            final Button cancelButton = new Button(I18N.CONSTANTS.delete());
            final Button sendButton = new Button(I18N.CONSTANTS.sendReportDraft());

            cancelButton.addSelectionListener(new SelectionListener<ButtonEvent>() {

                @Override
                public void componentSelected(ButtonEvent ce) {
                    final RemoveProjectReportDraft removeDraft = new RemoveProjectReportDraft(
                            report.getVersionId());
                    final GetProjectReport getReport = new GetProjectReport(report.getId());

                    final AsyncCallback<VoidResult> callback = AsyncCallbacks.emptyCallback();
                    dispatcher.execute(removeDraft, null, callback);
                    dispatcher.execute(getReport, null, new AsyncCallback<ProjectReportDTO>() {

                        @Override
                        public void onFailure(Throwable caught) {
                            MessageBox.alert(I18N.CONSTANTS.projectTabReports(), I18N.CONSTANTS.reportEditError(),
                                    null);
                        }

                        @Override
                        public void onSuccess(ProjectReportDTO result) {
                            Notification.show(I18N.CONSTANTS.projectTabReports(),
                                    I18N.CONSTANTS.reportEditCancelSuccess());
                            setReport(result);
                        }

                    });
                }
            });

            sendButton.addSelectionListener(new SelectionListener<ButtonEvent>() {

                @Override
                public void componentSelected(ButtonEvent ce) {
                    final HashMap<String, Object> changes = new HashMap<String, Object>();
                    changes.put("currentPhase", phaseName);

                    for (final Map.Entry<Integer, RichTextArea> entry : textAreas.entrySet())
                        changes.put(entry.getKey().toString(), entry.getValue().getHTML());

                    final UpdateEntity updateEntity = new UpdateEntity("ProjectReport", report.getVersionId(),
                            changes);
                    final PromoteProjectReportDraft promoteDraft = new PromoteProjectReportDraft(report.getId(),
                            report.getVersionId());

                    final AsyncCallback<VoidResult> callback = AsyncCallbacks.emptyCallback();
                    dispatcher.execute(updateEntity, null, callback);
                    dispatcher.execute(promoteDraft, null, new AsyncCallback<ProjectReportDTO>() {

                        @Override
                        public void onFailure(Throwable caught) {
                            MessageBox.alert(I18N.CONSTANTS.projectTabReports(), I18N.CONSTANTS.reportSaveError(),
                                    null);
                        }

                        @Override
                        public void onSuccess(ProjectReportDTO result) {
                            Notification.show(I18N.CONSTANTS.projectTabReports(),
                                    I18N.CONSTANTS.reportSaveSuccess());
                            setReport(result);
                        }

                    });
                }
            });

            final HorizontalPanel buttons = new HorizontalPanel();
            buttons.setSpacing(5);
            buttons.setVerticalAlignment(HasVerticalAlignment.ALIGN_MIDDLE);
            buttons.addStyleName("project-report-draft-button");
            buttons.add(cancelButton);
            buttons.add(sendButton);

            header.add(buttons);
            header.setCellHorizontalAlignment(buttons, HasHorizontalAlignment.ALIGN_RIGHT);

            flowPanel.add(header);

            // Save action
            final Listener<BaseEvent> saveListener = new Listener<BaseEvent>() {

                @Override
                public void handleEvent(BaseEvent be) {
                    final HashMap<String, String> changes = new HashMap<String, String>();

                    changes.put("currentPhase", phaseName);

                    for (final Map.Entry<Integer, RichTextArea> entry : textAreas.entrySet())
                        changes.put(entry.getKey().toString(), entry.getValue().getHTML());

                    final UpdateEntity updateEntity = new UpdateEntity("ProjectReport", report.getVersionId(),
                            (Map<String, Object>) (Map<String, ?>) changes);
                    dispatcher.execute(updateEntity, null, new AsyncCallback<VoidResult>() {

                        @Override
                        public void onFailure(Throwable caught) {
                            MessageBox.alert(I18N.CONSTANTS.projectTabReports(), I18N.CONSTANTS.reportSaveError(),
                                    null);
                        }

                        @Override
                        public void onSuccess(VoidResult result) {
                            Notification.show(I18N.CONSTANTS.projectTabReports(),
                                    I18N.CONSTANTS.reportSaveSuccess());

                            final Date now = new Date();
                            header.clear();
                            draftLastChangedTime.setText(I18N.MESSAGES
                                    .reportDraftLastChanged(dateFormat.format(now), timeFormat.format(now)));
                            personalDraft.setText(I18N.MESSAGES.personalDraft());
                            header.add(personalDraft);
                            header.add(draftLastChangedTime);
                            header.add(buttons);

                            boolean found = false;
                            for (int index = 0; !found && index < store.getCount(); index++) {
                                final ReportReference reference = store.getAt(index);

                                if (reference.getId().equals(report.getId())) {
                                    store.remove(reference);

                                    reference.setEditorName(authentication.getUserShortName());
                                    reference.setPhaseName(phaseName);
                                    reference.setLastEditDate(new Date());

                                    store.add(reference);

                                    found = true;
                                }
                            }

                            updateChanges();

                            autoSaveTimer.cancel();
                            autoSaveTimer.schedule(AUTO_SAVE_PERIOD);
                        }
                    });
                }
            };

            // Save button
            final Button saveButton = new Button(I18N.CONSTANTS.save(), icons.save());
            saveButton.addListener(Events.Select, saveListener);

            toolBar.add(saveButton);
            toolBar.add(new SeparatorToolItem());

            // Auto save timer
            autoSaveTimer = new Timer() {

                @Override
                public void run() {
                    saveListener.handleEvent(null);
                }
            };
            autoSaveTimer.schedule(AUTO_SAVE_PERIOD);

        } else {
            final Button editReportButton = new Button(I18N.CONSTANTS.edit(), icons.editPage());

            if (ProfileUtils.isGranted(authentication, GlobalPermissionEnum.EDIT_PROJECT)) {
                toolBar.add(editReportButton);
            }

            editReportButton.addSelectionListener(new SelectionListener<ButtonEvent>() {

                @Override
                public void componentSelected(ButtonEvent ce) {
                    // Draft creation
                    final HashMap<String, Object> properties = new HashMap<String, Object>();
                    properties.put("reportId", report.getId());
                    properties.put("phaseName", phaseName);
                    final CreateEntity createDraft = new CreateEntity("ProjectReportDraft", properties);

                    // Retrieving the new draft
                    final GetProjectReport getReportDraft = new GetProjectReport(report.getId());

                    final AsyncCallback<CreateResult> callback = AsyncCallbacks.emptyCallback();
                    dispatcher.execute(createDraft, null, callback);
                    dispatcher.execute(getReportDraft, null, new AsyncCallback<ProjectReportDTO>() {

                        @Override
                        public void onFailure(Throwable caught) {
                            MessageBox.alert(I18N.CONSTANTS.projectTabReports(), I18N.CONSTANTS.reportEditError(),
                                    null);
                        }

                        @Override
                        public void onSuccess(ProjectReportDTO result) {
                            setReport(result);
                        }

                    });
                }
            });

            final Button exportReportButton = new Button(I18N.CONSTANTS.exportToWord(), icons.msword());
            toolBar.add(exportReportButton);

            exportReportButton.addSelectionListener(new SelectionListener<ButtonEvent>() {

                @Override
                public void componentSelected(ButtonEvent ce) {
                    final FormElement form = FormElement.as(DOM.createForm());
                    form.setAction(GWT.getModuleBaseURL() + "export");
                    form.setTarget("_downloadFrame");
                    form.setMethod("POST");

                    final InputElement typeField = InputElement.as(DOM.createInputText());
                    typeField.setAttribute("type", "hidden");
                    typeField.setName(ExportUtils.PARAM_EXPORT_TYPE);
                    typeField.setValue(ExportUtils.ExportType.PROJECT_REPORT.toString());
                    form.appendChild(typeField);

                    final InputElement formatField = InputElement.as(DOM.createInputText());
                    formatField.setAttribute("type", "hidden");
                    formatField.setName(ExportUtils.PARAM_EXPORT_FORMAT);
                    formatField.setValue(ExportUtils.ExportFormat.MS_WORD.name());
                    form.appendChild(formatField);

                    final InputElement idField = InputElement.as(DOM.createInputText());
                    idField.setAttribute("type", "hidden");
                    idField.setName(ExportUtils.PARAM_EXPORT_PROJECT_ID);
                    idField.setAttribute("value", report.getId().toString());
                    form.appendChild(idField);

                    RootPanel.getBodyElement().appendChild(form);

                    form.submit();
                    form.removeFromParent();
                }
            });

            toolBar.add(new SeparatorToolItem());
        }

        // Key question info
        final Label keyQuestionLabel = keyQuestionState.getLabel();
        toolBar.add(keyQuestionLabel);
        toolBar.add(new SeparatorToolItem());

        // Overview mode
        final Button foldButton = new Button(I18N.CONSTANTS.reportOverviewMode());
        foldButton.addListener(Events.Select, new Listener<BaseEvent>() {

            @Override
            public void handleEvent(BaseEvent be) {
                root.expand(true);
                root.fold(true);
            }
        });
        // Expanded mode
        final Button expandButton = new Button(I18N.CONSTANTS.reportFullMode());
        expandButton.addListener(Events.Select, new Listener<BaseEvent>() {

            @Override
            public void handleEvent(BaseEvent be) {
                root.expand(true);
            }
        });

        toolBar.add(foldButton);
        toolBar.add(expandButton);

        if (report.isDraft()) {
            toolBar.add(new SeparatorToolItem());
            createRichTextToolbar(toolBar);
        }

        flowPanel.add(root);
        reportPanel.add(flowPanel);
        reportPanel.setTopComponent(toolBar);

        // Display
        mainPanel.add(reportPanel, new BorderLayoutData(LayoutRegion.CENTER));
        mainPanel.layout();

        mainPanel.unmask();
    }

    private void createRichTextToolbar(final ToolBar toolbar) {
        createRichTextToolbar(toolbar, globalFormatterArray);
    }

    public static void createRichTextToolbar(final ToolBar toolbar, final RichTextArea.Formatter[] formatter) {
        final ToolbarImages images = GWT.create(ToolbarImages.class);

        // Fonts
        final ListBox fontListBox = new ListBox();
        fontListBox.addItem(I18N.CONSTANTS.font());
        fontListBox.addItem("Arial");
        fontListBox.addItem("Times New Roman");
        fontListBox.addItem("Courier New");
        fontListBox.addItem("Georgia");
        fontListBox.addItem("Trebuchet");
        fontListBox.addItem("Verdana");
        fontListBox.addChangeHandler(new ChangeHandler() {

            @Override
            public void onChange(ChangeEvent event) {
                formatter[0].setFontName(fontListBox.getValue(fontListBox.getSelectedIndex()));
            }
        });
        final LayoutContainer fontListBoxWrapper = new LayoutContainer(new FitLayout());
        fontListBoxWrapper.add(fontListBox);
        toolbar.add(fontListBoxWrapper);

        // Bold
        final Button boldButton = new Button();
        boldButton.setIcon(AbstractImagePrototype.create(images.textBold()));
        boldButton.addListener(Events.Select, new Listener<BaseEvent>() {

            @Override
            public void handleEvent(BaseEvent be) {
                formatter[0].toggleBold();
            }
        });
        toolbar.add(boldButton);

        // Italic
        final Button italicButton = new Button();
        italicButton.setIcon(AbstractImagePrototype.create(images.textItalic()));
        italicButton.addListener(Events.Select, new Listener<BaseEvent>() {

            @Override
            public void handleEvent(BaseEvent be) {
                formatter[0].toggleItalic();
            }
        });
        toolbar.add(italicButton);

        // Underline
        final Button underlineButton = new Button();
        underlineButton.setIcon(AbstractImagePrototype.create(images.textUnderline()));
        underlineButton.addListener(Events.Select, new Listener<BaseEvent>() {

            @Override
            public void handleEvent(BaseEvent be) {
                formatter[0].toggleUnderline();
            }
        });
        toolbar.add(underlineButton);

        // Strike
        final Button strikeButton = new Button();
        strikeButton.setIcon(AbstractImagePrototype.create(images.textStrike()));
        strikeButton.addListener(Events.Select, new Listener<BaseEvent>() {

            @Override
            public void handleEvent(BaseEvent be) {
                formatter[0].toggleStrikethrough();
            }
        });
        toolbar.add(strikeButton);

        // Align left
        final Button alignLeftButton = new Button();
        alignLeftButton.setIcon(AbstractImagePrototype.create(images.textAlignLeft()));
        alignLeftButton.addListener(Events.Select, new Listener<BaseEvent>() {

            @Override
            public void handleEvent(BaseEvent be) {
                formatter[0].setJustification(RichTextArea.Justification.LEFT);
            }
        });
        toolbar.add(alignLeftButton);

        // Align center
        final Button alignCenterButton = new Button();
        alignCenterButton.setIcon(AbstractImagePrototype.create(images.textAlignCenter()));
        alignCenterButton.addListener(Events.Select, new Listener<BaseEvent>() {

            @Override
            public void handleEvent(BaseEvent be) {
                formatter[0].setJustification(RichTextArea.Justification.CENTER);
            }
        });
        toolbar.add(alignCenterButton);

        // Align right
        final Button alignRightButton = new Button();
        alignRightButton.setIcon(AbstractImagePrototype.create(images.textAlignRight()));
        alignRightButton.addListener(Events.Select, new Listener<BaseEvent>() {

            @Override
            public void handleEvent(BaseEvent be) {
                formatter[0].setJustification(RichTextArea.Justification.RIGHT);
            }
        });
        toolbar.add(alignRightButton);

        // Justify
        final Button alignJustifyButton = new Button();
        alignJustifyButton.setIcon(AbstractImagePrototype.create(images.textAlignJustify()));
        alignJustifyButton.addListener(Events.Select, new Listener<BaseEvent>() {

            @Override
            public void handleEvent(BaseEvent be) {
                formatter[0].setJustification(RichTextArea.Justification.FULL);
            }
        });
        toolbar.add(alignJustifyButton);

        // List with numbers
        final Button listNumbersButton = new Button();
        listNumbersButton.setIcon(AbstractImagePrototype.create(images.textListNumbers()));
        listNumbersButton.addListener(Events.Select, new Listener<BaseEvent>() {

            @Override
            public void handleEvent(BaseEvent be) {
                formatter[0].insertOrderedList();
            }
        });
        toolbar.add(listNumbersButton);

        // List with bullets
        final Button listBulletsButton = new Button();
        listBulletsButton.setIcon(AbstractImagePrototype.create(images.textListBullets()));
        listBulletsButton.addListener(Events.Select, new Listener<BaseEvent>() {

            @Override
            public void handleEvent(BaseEvent be) {
                formatter[0].insertUnorderedList();
            }
        });
        toolbar.add(listBulletsButton);

        // Images
        final Button imageAddButton = new Button();
        imageAddButton.setIcon(AbstractImagePrototype.create(images.imageAdd()));
        imageAddButton.addListener(Events.Select, new Listener<BaseEvent>() {

            private Dialog imageAddDialog;
            private TextField<String> imageURLField;

            @Override
            public void handleEvent(BaseEvent be) {
                if (imageAddDialog == null) {
                    imageAddDialog = new Dialog();

                    imageAddDialog.setButtons(Dialog.OKCANCEL);
                    imageAddDialog.setHeading(I18N.CONSTANTS.reportAddImageDialogTitle());
                    imageAddDialog.setModal(true);

                    imageAddDialog.setResizable(false);
                    imageAddDialog.setWidth("340px");

                    imageAddDialog.setLayout(new FormLayout());

                    // Report name
                    imageURLField = new TextField<String>();
                    imageURLField.setFieldLabel(I18N.CONSTANTS.reportImageURL());
                    imageURLField.setAllowBlank(false);
                    imageURLField.setName("url");
                    imageAddDialog.add(imageURLField);

                    // OK button
                    imageAddDialog.getButtonById(Dialog.OK)
                            .addSelectionListener(new SelectionListener<ButtonEvent>() {

                                @Override
                                public void componentSelected(ButtonEvent ce) {
                                    formatter[0].insertImage(imageURLField.getValue());
                                    imageAddDialog.hide();
                                }
                            });

                    // Cancel button
                    imageAddDialog.getButtonById(Dialog.CANCEL)
                            .addSelectionListener(new SelectionListener<ButtonEvent>() {

                                @Override
                                public void componentSelected(ButtonEvent ce) {
                                    imageAddDialog.hide();
                                }
                            });
                }

                imageURLField.setValue(null);
                imageAddDialog.show();
            }
        });
        toolbar.add(imageAddButton);
    }

    public String getPhaseName() {
        return phaseName;
    }

    public void setPhaseName(String phaseName) {
        this.phaseName = phaseName;
    }

    public Button getAttachButton() {
        return attachButton;
    }

    public Button getCreateReportButton() {
        return createReportButton;
    }

    public boolean isTextAreaChanged() {
        boolean changed = false;
        if (textAreas != null) {
            for (Map.Entry<Integer, String> entry : oldContents.entrySet()) {
                RichTextArea textArea = textAreas.get(entry.getKey());
                if (!textArea.getText().equals(entry.getValue()))
                    changed = true;
            }
        }
        return changed;
    }

    public void updateChanges() {
        if (textAreas != null) {
            for (Map.Entry<Integer, String> entry : oldContents.entrySet()) {
                RichTextArea textArea = textAreas.get(entry.getKey());
                oldContents.put(entry.getKey(), textArea.getText());
            }
        }
    }

    public void eraseChanges() {
        oldContents.clear();
    }
}