com.tasktop.c2c.server.tasks.client.widgets.TaskViewImpl.java Source code

Java tutorial

Introduction

Here is the source code for com.tasktop.c2c.server.tasks.client.widgets.TaskViewImpl.java

Source

/*******************************************************************************
 * Copyright (c) 2010, 2012 Tasktop Technologies
 * Copyright (c) 2010, 2011 SpringSource, a division of VMware
 * 
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * Contributors:
 *     Tasktop Technologies - initial API and implementation
 ******************************************************************************/
package com.tasktop.c2c.server.tasks.client.widgets;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;

import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.dom.client.DivElement;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.FieldSetElement;
import com.google.gwt.dom.client.SpanElement;
import com.google.gwt.editor.client.Editor;
import com.google.gwt.editor.client.SimpleBeanEditorDriver;
import com.google.gwt.editor.client.adapters.EditorSource;
import com.google.gwt.editor.client.adapters.ListEditor;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.HasClickHandlers;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.safehtml.client.SafeHtmlTemplates;
import com.google.gwt.safehtml.shared.SafeHtml;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.safehtml.shared.SafeHtmlUtils;
import com.google.gwt.safehtml.shared.SafeUri;
import com.google.gwt.text.shared.AbstractRenderer;
import com.google.gwt.text.shared.testing.PassthroughRenderer;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.client.TakesValue;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.FocusWidget;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.Panel;
import com.google.gwt.user.client.ui.SuggestOracle;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.UIObject;
import com.google.gwt.user.client.ui.ValueLabel;
import com.google.gwt.user.client.ui.ValueListBox;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.user.datepicker.client.DateBox;
import com.google.gwt.user.datepicker.client.DatePicker;
import com.tasktop.c2c.server.common.profile.web.client.AuthenticationHelper;
import com.tasktop.c2c.server.common.profile.web.client.CommonProfileMessages;
import com.tasktop.c2c.server.common.profile.web.client.ProfileGinjector;
import com.tasktop.c2c.server.common.profile.web.client.presenter.person.ProjectPersonService;
import com.tasktop.c2c.server.common.web.client.view.AbstractComposite;
import com.tasktop.c2c.server.common.web.client.view.Avatar;
import com.tasktop.c2c.server.common.web.client.widgets.Format;
import com.tasktop.c2c.server.common.web.client.widgets.chooser.MultiValueChooser;
import com.tasktop.c2c.server.common.web.client.widgets.chooser.SingleValueChooser;
import com.tasktop.c2c.server.common.web.client.widgets.chooser.StringSuggestion;
import com.tasktop.c2c.server.common.web.client.widgets.chooser.StringValueCompositeFactory;
import com.tasktop.c2c.server.common.web.client.widgets.chooser.person.Person;
import com.tasktop.c2c.server.common.web.client.widgets.chooser.person.PersonSuggestOracle;
import com.tasktop.c2c.server.common.web.client.widgets.time.TimePeriodBox;
import com.tasktop.c2c.server.common.web.client.widgets.time.TimePeriodRenderer;
import com.tasktop.c2c.server.scm.domain.Commit;
import com.tasktop.c2c.server.tasks.client.TaskResources;
import com.tasktop.c2c.server.tasks.client.TasksMessages;
import com.tasktop.c2c.server.tasks.client.place.ProjectEditTaskPlace;
import com.tasktop.c2c.server.tasks.client.place.ProjectNewTaskPlace;
import com.tasktop.c2c.server.tasks.client.place.ProjectTaskHistoryPlace;
import com.tasktop.c2c.server.tasks.client.place.ProjectTasksPlace;
import com.tasktop.c2c.server.tasks.client.presenters.TaskPresenter;
import com.tasktop.c2c.server.tasks.client.widgets.chooser.keywords.KeywordCompositeFactory;
import com.tasktop.c2c.server.tasks.client.widgets.chooser.keywords.KeywordSuggestOracle;
import com.tasktop.c2c.server.tasks.client.widgets.chooser.task.ParentTaskChooser;
import com.tasktop.c2c.server.tasks.client.widgets.chooser.task.TaskCompositeFactory;
import com.tasktop.c2c.server.tasks.client.widgets.chooser.task.TaskSuggestOracle;
import com.tasktop.c2c.server.tasks.client.widgets.presenter.person.KeywordSuggestService;
import com.tasktop.c2c.server.tasks.client.widgets.wiki.EditWikiPanel;
import com.tasktop.c2c.server.tasks.client.widgets.wiki.WikiHTMLPanel;
import com.tasktop.c2c.server.tasks.domain.Attachment;
import com.tasktop.c2c.server.tasks.domain.Component;
import com.tasktop.c2c.server.tasks.domain.ExternalTaskRelation;
import com.tasktop.c2c.server.tasks.domain.FieldDescriptor;
import com.tasktop.c2c.server.tasks.domain.FieldType;
import com.tasktop.c2c.server.tasks.domain.Iteration;
import com.tasktop.c2c.server.tasks.domain.Keyword;
import com.tasktop.c2c.server.tasks.domain.Milestone;
import com.tasktop.c2c.server.tasks.domain.Priority;
import com.tasktop.c2c.server.tasks.domain.Product;
import com.tasktop.c2c.server.tasks.domain.RepositoryConfiguration;
import com.tasktop.c2c.server.tasks.domain.Task;
import com.tasktop.c2c.server.tasks.domain.TaskSeverity;
import com.tasktop.c2c.server.tasks.domain.TaskUserProfile;
import com.tasktop.c2c.server.tasks.domain.WorkLog;

public class TaskViewImpl extends AbstractComposite implements TaskView, Editor<Task> {

    private static final String NOT_SET = ((TasksMessages) GWT.create(TasksMessages.class)).notSet();
    private static TaskViewImpl instance = null;

    public static TaskViewImpl getInstance() {
        if (instance == null) {
            instance = new TaskViewImpl();
        }
        return instance;
    }

    interface Binder extends UiBinder<Widget, TaskViewImpl> {
    }

    private static Binder uiBinder = GWT.create(Binder.class);
    private CommonProfileMessages commonProfileMessages = GWT.create(CommonProfileMessages.class);
    private TasksMessages tasksMessages = GWT.create(TasksMessages.class);

    abstract class InlineEditableField<T> {
        private final HTML readOnlyField; // for anon users
        private final Anchor editAnchor;
        private final TakesValue<T> editableField;

        public InlineEditableField(HTML readOnlyField, Anchor editAnchor, TakesValue<T> editableField) {
            this(readOnlyField, editAnchor, editableField, editAnchor);
        }

        public InlineEditableField(HTML readOnlyField, Anchor editAnchor, TakesValue<T> editableField,
                HasClickHandlers editTrigger) {
            this.readOnlyField = readOnlyField;
            this.editableField = editableField;
            this.editAnchor = editAnchor;
            if (editTrigger != null) {
                editTrigger.addClickHandler(new ClickHandler() {

                    @Override
                    public void onClick(ClickEvent event) {
                        startInlineEdit(InlineEditableField.this);
                    }
                });
            }
        }

        protected Element getEditableElement() {
            return ((Widget) editableField).getElement();
        }

        public T getEditableFieldValue() {
            if (editableField instanceof TakesValue<?>) {
                return ((TakesValue<T>) editableField).getValue();
            }
            return null;
        }

        public void setEditableFieldValue(T value) {
            if (editableField instanceof TakesValue<?>) {
                ((TakesValue<T>) editableField).setValue(value);
            }
        }

        public void renderTask(Task t) {
            setTask(t);

            SafeHtml html = getHtml(t);
            UIObject.setVisible(getEditableElement().getParentElement(), false);
            boolean anon = AuthenticationHelper.isAnonymous();
            if (readOnlyField != null) {
                readOnlyField.setVisible(anon);
            }
            if (getHideOnEditElement() != null) {
                UIObject.setVisible(getHideOnEditElement(), !anon);
            }

            if (html != null && anon && readOnlyField != null) {
                readOnlyField.setHTML(html);
            }

            if (!anon && editAnchor != null) {

                SafeHtml editHtml;

                if (html == null) {
                    editHtml = visibleTriggerHtml();
                    getEditTrigerElement(true).removeClassName(TaskResources.resources.style().inlineEditTrigger());
                    getEditTrigerElement(false)
                            .removeClassName(TaskResources.resources.style().inlineEditTrigger());
                    getEditTrigerElement(true).addClassName(TaskResources.resources.style().visibleTrigger());
                } else {
                    editHtml = editTriggerHtml(html);
                    getEditTrigerElement(true).removeClassName(TaskResources.resources.style().visibleTrigger());
                    getEditTrigerElement(false).removeClassName(TaskResources.resources.style().visibleTrigger());
                    getEditTrigerElement(false).addClassName(TaskResources.resources.style().inlineEditTrigger());
                }

                editAnchor.setHTML(editHtml);
            }

        }

        protected SafeHtml editTriggerHtml(SafeHtml html) {
            return template.editTriggerHtml(html, TaskResources.resources.pencilIcon().getSafeUri());
        }

        protected SafeHtml visibleTriggerHtml() {
            return template.imageHtml(TaskResources.resources.pencilIcon().getSafeUri());
        }

        protected Element getEditTrigerElement(boolean triggerVisible) {
            if (editAnchor == null) {
                return null;
            }
            if (triggerVisible) {
                return editAnchor.getElement();
            }
            return editAnchor.getElement().getParentElement();
        }

        protected Element getHideOnEditElement() {
            return getEditTrigerElement(false);
        }

        protected SafeHtml getHtml(Task t) {
            return SafeHtmlUtils.EMPTY_SAFE_HTML;
        }

        protected abstract void flushTask();

        protected void setTask(Task t) {
            // Empty if the editor does it all
        }

        public void cancelEdit() {
            renderTask(task);
        }

        public void startEdit() {
            if (getHideOnEditElement() != null) {
                UIObject.setVisible(getHideOnEditElement(), false);
            }
            if (editableField != null) {
                UIObject.setVisible(getEditableElement().getParentElement(), true);

                ProfileGinjector.get.instance().getScheduler().scheduleDeferred(new ScheduledCommand() {

                    @Override
                    public void execute() {
                        deferedStartEdit();
                    }
                });

            }

        }

        protected void deferedStartEdit() {
            getEditableElement().focus();
        }

        public void saveEdit() {
            flushTask();
            renderTask(task);
        }

    }

    interface Driver extends SimpleBeanEditorDriver<Task, TaskViewImpl> {

    }

    private Driver driver = GWT.create(Driver.class);

    @UiField(provided = true)
    ValueLabel<Integer> id = new ValueLabel<Integer>(new AbstractRenderer<Integer>() {

        @Override
        public String render(Integer object) {
            return object + commonProfileMessages.colon() + " ";
        }

    });
    @UiField
    Anchor editTaskTypeAnchor;
    @UiField(provided = true)
    ValueListBox<String> taskType = new ValueListBox<String>(PassthroughRenderer.instance());
    @Ignore
    @UiField
    HTML readOnlyTaskType;

    @UiField
    Anchor editSeverityAnchor;
    @UiField
    @Ignore
    HTML readOnlySeverity;
    @UiField(provided = true)
    ValueListBox<TaskSeverity> severity = new ValueListBox<TaskSeverity>(new AbstractRenderer<TaskSeverity>() {

        @Override
        public String render(TaskSeverity s) {
            return s.getValue();
        }

    });
    @UiField
    Anchor editPriorityAnchor;
    @UiField
    @Ignore
    HTML readOnlyPriority;
    @UiField(provided = true)
    ValueListBox<Priority> priority = new ValueListBox<Priority>(new AbstractRenderer<Priority>() {

        @Override
        public String render(Priority p) {
            return p.getValue();
        }

    });

    @UiField
    Anchor editEstimateAnchor;
    @UiField
    @Ignore
    HTML readOnlyEstimate;
    @UiField(provided = true)
    TimePeriodBox estimatedTime = TimePeriodBox.getHourBoxWithDefaultsEntryToDays();
    @UiField
    Anchor editTimeSpentAnchor;
    @UiField
    @Ignore
    HTML readOnlyTimeSpent;
    @UiField(provided = true)
    @Ignore
    TimePeriodBox timeSpent = TimePeriodBox.getDefaultHourBox();

    @UiField
    Anchor editProductAnchor;
    @UiField
    @Ignore
    HTML readOnlyProduct;
    @UiField(provided = true)
    ValueListBox<Product> product = new ValueListBox<Product>(new AbstractRenderer<Product>() {

        @Override
        public String render(Product object) {
            return object.getName();
        }
    });

    @UiField
    Anchor editComponentAnchor;
    @UiField
    @Ignore
    HTML readOnlyComponent;
    @UiField(provided = true)
    ValueListBox<Component> component = new ValueListBox<Component>(new AbstractRenderer<Component>() {
        @Override
        public String render(Component object) {
            return object.getName();
        }
    });

    @UiField
    Anchor editFoundInAnchor;
    @UiField
    @Ignore
    HTML readOnlyFoundIn;
    @UiField(provided = true)
    SingleValueChooser<String> foundInRelease = new SingleValueChooser<String>(new SuggestOracle() {

        @Override
        public void requestSuggestions(Request request, Callback callback) {
            List<Suggestion> suggestions = new ArrayList<Suggestion>();

            boolean foundExact = false;
            for (String release : repositoryConfiguration.getReleaseTags(product.getValue())) {
                if (release.equals(request.getQuery())) {
                    foundExact = true;
                }

                if (request.getQuery() == null || release.startsWith(request.getQuery())) {
                    suggestions.add(new StringSuggestion(release));
                }
            }

            if (!foundExact) {
                suggestions.add(new StringSuggestion(request.getQuery()));
            }

            callback.onSuggestionsReady(request, new Response(suggestions));
        }

        @Override
        public void requestDefaultSuggestions(Request request, Callback callback) {
            requestSuggestions(request, callback);
        }
    });

    @UiField
    Anchor editReleaseAnchor;
    @UiField
    @Ignore
    HTML readOnlyRelease;
    @UiField(provided = true)
    @SuppressWarnings("unchecked")
    ValueListBox<Milestone> milestone = new ValueListBox<Milestone>(ReferenceValueRenderer.getInstance());

    @UiField
    Anchor editIterationAnchor;
    @UiField(provided = true)
    ValueListBox<Iteration> iteration = new ValueListBox<Iteration>(new AbstractRenderer<Iteration>() {

        @Override
        public String render(Iteration object) {
            return object.getValue();
        }
    });

    @UiField
    @Ignore
    HTML readOnlyIteration;
    @UiField
    Anchor editDueDateAnchor;
    @UiField
    @Ignore
    HTML readOnlyDueDate;
    @UiField(provided = true)
    DateBox deadline = new DateBox(new DatePicker(), new Date(), new DateBox.DefaultFormat(Format.getDateFormat()));

    @UiField
    Anchor editTagsAnchor;
    @UiField
    @Ignore
    HTML readOnlyTags;
    @UiField(provided = true)
    MultiValueChooser<Keyword> keywords = new MultiValueChooser<Keyword>(new KeywordSuggestOracle(null));

    @UiField
    Anchor taskListLink;
    @UiField
    Anchor taskHistoryLink;
    @UiField
    SpanElement newSubTaskSpan;
    @UiField
    Anchor newSubTaskLink;
    @UiField
    Panel attachmentsPanel;

    @UiField
    Anchor editTaskAnchor;

    @UiField
    Anchor editSummaryAnchor;
    @Ignore
    @UiField
    HTML readOnlySummary;
    @UiField
    TextBox shortDescription;

    @UiField
    Image createdByImage;
    @UiField
    @Ignore
    Label createdBy;
    @UiField
    @Ignore
    Label creationDate;
    @UiField
    @Ignore
    Label updateDate;

    @UiField
    WikiHTMLPanel readOnlyDescription;
    @UiField
    EditWikiPanel description;

    @UiField
    DivElement editControlsDiv;
    @UiField
    Button saveButton;
    @UiField
    Button cancelButton;

    @UiField
    Panel statusPanel;
    @UiField
    Anchor editStatusAnchor;
    @UiField
    @Ignore
    HTML readOnlyStatus;
    @UiField
    @Path("")
    StatusEditorView statusEditor;

    @UiField
    Anchor editOwnerAnchor;
    @UiField
    @Ignore
    HTML readOnlyOwner;
    @UiField(provided = true)
    SingleTaskProfileChooser assignee = new SingleTaskProfileChooser(new PersonSuggestOracle(null));;
    @UiField
    Anchor editCCAnchor;
    @UiField
    @Ignore
    HTML readOnlyCC;
    @UiField(provided = true)
    MultipleTaskProfileChooser watchers = new MultipleTaskProfileChooser(new PersonSuggestOracle(null));

    private TaskSuggestOracle blocksTaskOracle = new TaskSuggestOracle();
    private TaskSuggestOracle subTaskOracle = new TaskSuggestOracle();

    @UiField
    Panel parentTasksPanel;
    @UiField
    Anchor editParentAnchor;
    @UiField(provided = true)
    protected ParentTaskChooser blocksTasks = new ParentTaskChooser(blocksTaskOracle);

    @UiField
    Panel subTasksPanel;
    @UiField
    Anchor editSubtasksAnchor;
    @UiField(provided = true)
    MultiValueChooser<Task> subTasks = new MultiValueChooser<Task>(subTaskOracle);
    @UiField
    FieldSetElement duplicatesElement;
    @UiField
    Panel duplicates;
    @UiField
    FieldSetElement externalDepsElement;
    @UiField
    Panel externalTaskRelations;
    @UiField
    FieldSetElement commitsElement;
    @UiField
    Panel commits;
    @UiField
    CommentsPanel commentsPanel;
    @UiField
    Panel customFieldsPanel;

    private TaskPresenter presenter;

    private String projectIdentifier;
    private Task task;

    private static class CustomFieldSource extends EditorSource<CustomFieldEditor> {
        @Override
        public CustomFieldEditor create(int index) {
            return new CustomFieldEditor();

        }

    }

    protected CustomFieldSource customFieldSource = new CustomFieldSource();
    @Ignore
    protected ListEditor<CustomField, CustomFieldEditor> customFieldEditor = ListEditor.of(customFieldSource);

    interface CFDriver
            extends SimpleBeanEditorDriver<List<CustomField>, ListEditor<CustomField, CustomFieldEditor>> {
    }

    private CFDriver customFieldDriver = GWT.create(CFDriver.class);

    private RepositoryConfiguration repositoryConfiguration;
    private List<InlineEditableField<?>> inlineEditFields = new ArrayList<InlineEditableField<?>>();
    private List<InlineEditableField<?>> editingFields = new ArrayList<InlineEditableField<?>>();
    private InlineEditableField<?> lastEditedField;

    private TaskViewImpl() {
        initWidget(uiBinder.createAndBindUi(this));
        initEditFields();
        driver.initialize(this);
        customFieldDriver.initialize(customFieldEditor);

        commentsPanel.postComment.addClickHandler(new ClickHandler() {

            @Override
            public void onClick(ClickEvent event) {
                postComment();
            }
        });
        newSubTaskSpan.appendChild(new Image(TaskResources.resources.newSubtaskIcon()).getElement());
        UIObject.setVisible(editControlsDiv, false);
        keywords.setValueCompositeFactory(new KeywordCompositeFactory());
        foundInRelease.setValueCompositeFactory(new StringValueCompositeFactory());
        subTasks.setValueCompositeFactory(new TaskCompositeFactory());
        blocksTasks.setValueCompositeFactory(new TaskCompositeFactory());
        editTaskAnchor.setHTML(template.iconText(commonProfileMessages.edit()));
    }

    private void initEditFields() {

        inlineEditFields.add(new InlineEditableField<String>(readOnlyTaskType, editTaskTypeAnchor, taskType) {

            @Override
            public void flushTask() {
                presenter.saveTaskType(taskType.getValue());
            }

            @Override
            protected SafeHtml getHtml(Task t) {
                return SafeHtmlUtils.fromString((t.getTaskType()));
            }

            protected SafeHtml editTriggerHtml(SafeHtml html) {
                return html; // No place to layout with the icon
            }

        });

        inlineEditFields.add(new InlineEditableField<Priority>(readOnlyPriority, editPriorityAnchor, priority) {

            @Override
            public void flushTask() {
                presenter.savePriority(priority.getValue());
            }

            @Override
            protected SafeHtml getHtml(Task t) {
                return SafeHtmlUtils.fromString((t.getPriority().getValue()));
            }

        });

        inlineEditFields.add(new InlineEditableField<TaskSeverity>(readOnlySeverity, editSeverityAnchor, severity) {

            @Override
            public void flushTask() {
                presenter.saveSeverity(severity.getValue());
            }

            @Override
            protected SafeHtml getHtml(Task t) {
                return SafeHtmlUtils.fromString(t.getSeverity().getValue());
            }

        });

        inlineEditFields.add(new InlineEditableField<Task>(readOnlyStatus, editStatusAnchor, statusEditor) {

            @Override
            public void flushTask() {
                presenter.saveStatus(statusEditor.getSelectedStatus(), statusEditor.getSelectedResolution(),
                        statusEditor.getDuplicateId());
            }

            @Override
            protected SafeHtml getHtml(Task t) {
                statusPanel.clear();
                if (task.getStatus() != null) {
                    if (task.getResolution() != null) {
                        StringBuffer buf = new StringBuffer();

                        buf.append(task.getResolution().toString());

                        if (task.getResolution().isDuplicate() && task.getDuplicateOf() != null) {
                            buf.append(" ").append(tasksMessages.ofLc()).append(" ");
                            Label label = new Label(buf.toString());
                            statusPanel.add(label);

                            statusPanel.add(createTaskAnchor(task.getDuplicateOf()));
                        } else {
                            Label label = new Label(buf.toString());
                            statusPanel.add(label);
                        }
                    }
                }

                return SafeHtmlUtils.fromString(t.getStatus().getValue());
            }

            @Override
            public void startEdit() {
                super.startEdit();
                statusPanel.clear();
            }

        });

        final InlineEditableField<String> descriptionEditableField = new InlineEditableField<String>(null, null,
                description, null) {

            @Override
            public void flushTask() {
                presenter.saveDescription(description.getValue());
                task.setWikiRenderedDescription(null);

            }

            @Override
            public void setTask(Task t) {
                readOnlyDescription.setWikiHTML(task.getWikiRenderedDescription());
                if (task.getWikiRenderedDescription() != null) {
                    description.setRenderedValue(task.getWikiRenderedDescription());
                }
            }

            public void renderTask(Task t) {
                boolean anon = AuthenticationHelper.isAnonymous();
                readOnlyDescription.setVisible(anon);
                description.setVisible(!anon);
                setTask(t);
            }

            @Override
            public void startEdit() {
                super.startEdit();
                description.setEditMode();
            }

        };
        inlineEditFields.add(descriptionEditableField);
        description.getTextArea().setWidth("98%"); // FIXME
        description.addToggleListener(new ValueChangeHandler<Boolean>() {

            @Override
            public void onValueChange(ValueChangeEvent<Boolean> event) {
                if (event.getValue() && !editingFields.contains(descriptionEditableField)) {
                    startInlineEdit(descriptionEditableField);
                } else {
                    cancelInlineEdit(descriptionEditableField);
                }
            }
        });

        inlineEditFields.add(new InlineEditableField<String>(readOnlySummary, editSummaryAnchor, shortDescription) {

            @Override
            public void flushTask() {
                presenter.saveShortDescription(shortDescription.getText());
            }

            @Override
            protected SafeHtml getHtml(Task t) {
                boolean empty = task.getShortDescription() == null || task.getShortDescription().isEmpty();
                return SafeHtmlUtils.fromString(empty ? NOT_SET : task.getShortDescription());
            }

            protected SafeHtml editTriggerHtml(SafeHtml html) {
                return html; // No place to layout with the icon
            }
        });

        inlineEditFields
                .add(new InlineEditableField<BigDecimal>(readOnlyEstimate, editEstimateAnchor, estimatedTime) {

                    @Override
                    public void flushTask() {
                        presenter.saveEstimate(estimatedTime.getValue());
                    }

                    @Override
                    protected SafeHtml getHtml(Task t) {

                        return formatThisAndSubtaskTime(t.getEstimatedTime(), t.getSumOfSubtasksEstimatedTime());
                    }

                });

        inlineEditFields.add(new InlineEditableField<TaskUserProfile>(readOnlyOwner, editOwnerAnchor, assignee) {

            @Override
            public void flushTask() {
                presenter.saveOwner(assignee.getValue());
            }

            @Override
            protected void deferedStartEdit() {
                assignee.getWrappedChooser().getSuggestBox().showSuggestionList();
                assignee.getWrappedChooser().getSuggestBox().getElement().focus();
            }

            @Override
            protected SafeHtml getHtml(Task t) {
                return SafeHtmlUtils.fromString(t.getAssignee() == null ? NOT_SET : t.getAssignee().getRealname());
            }

            @Override
            protected Element getEditableElement() {
                return assignee.asWidget().getElement();
            }

        });

        inlineEditFields
                .add(new InlineEditableField<BigDecimal>(readOnlyTimeSpent, editTimeSpentAnchor, timeSpent) {

                    @Override
                    public void flushTask() {
                        if (timeSpent.getValue() == null || timeSpent.getValue().signum() == 0) {
                            // otherwise we get into a bad state
                            return;
                        }
                        WorkLog workLog = new WorkLog();
                        workLog.setHoursWorked(timeSpent.getValue());
                        presenter.saveWorkLog(workLog);
                    }

                    @Override
                    protected SafeHtml getHtml(Task t) {
                        BigDecimal sum = null;

                        List<WorkLog> workLogs = t.getWorkLogs();
                        if (workLogs != null && !workLogs.isEmpty()) {
                            sum = BigDecimal.ZERO;
                            for (WorkLog workLog : workLogs) {
                                sum = sum.add(workLog.getHoursWorked());
                            }
                        }
                        return formatThisAndSubtaskTime(sum, t.getSumOfSubtasksTimeSpent());
                    }
                });

        inlineEditFields.add(new InlineEditableField<List<TaskUserProfile>>(readOnlyCC, editCCAnchor, watchers) {

            @Override
            protected void flushTask() {
                presenter.saveCC(watchers.getValue());
            }

            @Override
            protected void deferedStartEdit() {
                watchers.getWrappedChooser().getSuggestBox().showSuggestionList();
                watchers.getWrappedChooser().getSuggestBox().getElement().focus();
            }

            @Override
            protected SafeHtml getHtml(Task t) {
                Collections.sort(t.getWatchers());
                return getHtmlForPeople(t.getWatchers());
            }

            @Override
            protected Element getEditableElement() {
                return watchers.asWidget().getElement();
            }

            @Override
            public List<TaskUserProfile> getEditableFieldValue() {
                List<TaskUserProfile> userList = watchers.getValue();
                Collections.sort(userList);
                return userList;
            }

        });

        inlineEditFields.add(new InlineEditableField<List<Keyword>>(readOnlyTags, editTagsAnchor, keywords) {
            @Override
            protected void flushTask() {
                presenter.saveTags(keywords.getValues());
            }

            @Override
            protected void deferedStartEdit() {
                keywords.getSuggestBox().showSuggestionList();
                keywords.getSuggestBox().getElement().focus();
            }

            @Override
            protected SafeHtml getHtml(Task t) {
                return getHtmlForKeywords(t.getKeywords());

            }
        });

        inlineEditFields.add(new InlineEditableField<Iteration>(readOnlyIteration, editIterationAnchor, iteration) {

            @Override
            protected void flushTask() {
                presenter.saveIteration(iteration.getValue());

            }

            @Override
            protected SafeHtml getHtml(Task t) {
                return SafeHtmlUtils.fromString(t.getIteration().getValue());

            }
        });

        inlineEditFields.add(new InlineEditableField<Date>(readOnlyDueDate, editDueDateAnchor, deadline) {

            @Override
            protected void flushTask() {
                presenter.saveDueDate(deadline.getValue());
            }

            @Override
            protected SafeHtml getHtml(Task t) {
                return SafeHtmlUtils
                        .fromString(t.getDeadline() == null ? NOT_SET : Format.stringValueDate(t.getDeadline()));

            }
        });

        inlineEditFields.add(new InlineEditableField<List<Task>>(null, editSubtasksAnchor, subTasks) {

            @Override
            protected void flushTask() {
                presenter.saveSubTasks(subTasks.getValue());
            }

            @Override
            protected SafeHtml getHtml(Task t) {
                subTasksPanel.clear();
                List<Task> subTaskList = task.getSubTasks();
                if (subTaskList.size() > 0) {
                    for (int i = 0; i < subTaskList.size(); i++) {
                        subTasksPanel.add(createTaskAnchor(subTaskList.get(i)));
                        if (i < (subTaskList.size() - 1)) {
                            subTasksPanel.add(new Label(commonProfileMessages.comma() + " "));
                        }
                    }
                    subTasksPanel.add(editSubtasksAnchor);
                    return null;
                } else {
                    subTasksPanel.add(editSubtasksAnchor);
                    return SafeHtmlUtils.fromTrustedString(commonProfileMessages.none());
                }

            }

        });

        inlineEditFields.add(new InlineEditableField<List<Task>>(null, editParentAnchor, blocksTasks) {

            @Override
            protected void flushTask() {
                presenter.saveBlocks(blocksTasks.getValue());
            }

            @Override
            protected SafeHtml getHtml(Task t) {
                parentTasksPanel.clear();
                if (task.getBlocksTasks() != null && !task.getBlocksTasks().isEmpty()) {
                    for (int i = 0; i < task.getBlocksTasks().size(); i++) {
                        parentTasksPanel.add(createTaskAnchor(task.getBlocksTasks().get(i)));
                        if (i < (t.getBlocksTasks().size() - 1)) {
                            parentTasksPanel.add(new Label(commonProfileMessages.comma() + " "));
                        }
                    }
                    parentTasksPanel.add(editParentAnchor);
                    return null;
                } else {
                    parentTasksPanel.add(editParentAnchor);
                    return SafeHtmlUtils.fromTrustedString(commonProfileMessages.none());
                }

            }

        });

        initProductComopnentReleaseFoundIn();

    }

    private void initProductComopnentReleaseFoundIn() {
        inlineEditFields.add(new InlineEditableField<Product>(readOnlyProduct, editProductAnchor, product) {

            @Override
            protected void flushTask() {
                presenter.saveProduct(product.getValue());

            }

            @Override
            protected SafeHtml getHtml(Task t) {
                return SafeHtmlUtils.fromString(t.getProduct().getName());

            }
        });

        inlineEditFields.add(new InlineEditableField<Component>(readOnlyComponent, editComponentAnchor, component) {

            @Override
            protected void flushTask() {
                presenter.saveComponent(component.getValue());
            }

            @Override
            protected SafeHtml getHtml(Task t) {
                return SafeHtmlUtils.fromString(t.getComponent().getName());

            }
        });
        inlineEditFields.add(new InlineEditableField<String>(readOnlyFoundIn, editFoundInAnchor, foundInRelease) {

            @Override
            protected void flushTask() {
                presenter.saveFoundInRelease(foundInRelease.getValue());
            }

            @Override
            protected void deferedStartEdit() {
                foundInRelease.getSuggestBox().showSuggestionList();
                foundInRelease.getSuggestBox().getElement().focus();
            }

            @Override
            protected SafeHtml getHtml(Task t) {
                return SafeHtmlUtils.fromString(t.getFoundInRelease() == null ? NOT_SET : t.getFoundInRelease());

            }
        });
        inlineEditFields.add(new InlineEditableField<Milestone>(readOnlyRelease, editReleaseAnchor, milestone) {

            @Override
            protected void flushTask() {
                presenter.saveRelease(milestone.getValue());

            }

            @Override
            protected SafeHtml getHtml(Task t) {
                return SafeHtmlUtils.fromString(t.getMilestone().getValue());

            }
        });

    }

    private static SafeHtml formatThisAndSubtaskTime(BigDecimal thisTime, BigDecimal subTasksTime) {
        TasksMessages tasksMessages = GWT.create(TasksMessages.class);
        if (thisTime == null && subTasksTime.signum() == 0) {
            return SafeHtmlUtils.fromTrustedString(NOT_SET);
        } else if (subTasksTime.signum() == 0) {
            return SafeHtmlUtils.fromTrustedString(TimePeriodRenderer.HOUR_RENDERER.render(thisTime));
        } else if (thisTime == null) {
            return SafeHtmlUtils.fromTrustedString(
                    tasksMessages.subtasksTime(TimePeriodRenderer.HOUR_RENDERER.render(subTasksTime)));
        } else {
            BigDecimal totalTime = thisTime == null ? subTasksTime : subTasksTime.add(thisTime);
            return tasksMessages.timeBreakdown(TimePeriodRenderer.HOUR_RENDERER.render(totalTime),
                    TimePeriodRenderer.HOUR_RENDERER.render(thisTime),
                    TimePeriodRenderer.HOUR_RENDERER.render(subTasksTime));
        }
    }

    private static HtmlTemplates template = GWT.create(HtmlTemplates.class);

    static interface HtmlTemplates extends SafeHtmlTemplates {
        @Template("<div>{0}<img src=\"{1}\"/></div>")
        SafeHtml editTriggerHtml(SafeHtml safeHtml, SafeUri imageUri);

        @Template("<img src=\"{0}\"/>")
        SafeHtml imageHtml(SafeUri imageUri);

        @Template("<span></span>{0}")
        SafeHtml iconText(String text);
    }

    public void setProjectIdentifier(String projectIdentifier) {
        this.projectIdentifier = projectIdentifier;
        statusEditor.setProjectIdentifier(projectIdentifier);

        ((KeywordSuggestOracle) keywords.getSuggestBox().getSuggestOracle())
                .setSuggestionService(new KeywordSuggestService(projectIdentifier));

        description.setProjectId(projectIdentifier);
        taskListLink.setHref(ProjectTasksPlace.createDefaultPlace(projectIdentifier).getHref());

        // Hide things which require a logged-in user
        boolean canEdit = !AuthenticationHelper.isAnonymous();
        newSubTaskLink.setVisible(canEdit);
        commentsPanel.setProjectIdentifier(projectIdentifier);
        blocksTaskOracle.setProjectIdentifier(projectIdentifier);
        subTaskOracle.setProjectIdentifier(projectIdentifier);

    }

    public void setTask(Task task) {
        updateTaskInternal(task);
        updateCommentView(task);
    }

    public void updateTask(Task task) {
        Object currentlyEditing = null;
        if (lastEditedField != null) {
            currentlyEditing = lastEditedField.getEditableFieldValue();
        }
        updateTaskInternal(task);
        if (currentlyEditing != null) {
            if (!lastEditedField.getEditableFieldValue().equals(currentlyEditing)) {
                ((InlineEditableField<Object>) lastEditedField).setEditableFieldValue(currentlyEditing);
                startInlineEdit(lastEditedField);
            }
        }
    }

    private void updateTaskInternal(Task task) {
        this.task = task;

        adjustEditControls(null);

        editTaskAnchor.setHref(ProjectEditTaskPlace.createPlace(projectIdentifier, task.getId()).getHref());
        editTaskAnchor.setVisible(!AuthenticationHelper.isAnonymous());

        createdByImage.setUrl(Avatar.computeAvatarUrl(task.getReporter().getGravatarHash(), Avatar.Size.MICRO));
        createdBy.setText(task.getReporter().getLoginName());
        creationDate.setText(Format.stringValueDateTime(task.getCreationDate()));
        updateDate.setText(tasksMessages.updatedOnDate(Format.stringValueDateTime(task.getModificationDate())));

        newSubTaskLink
                .setHref(ProjectNewTaskPlace.createNewSubtaskPlace(projectIdentifier, task.getId()).getHref());

        String markupLanguage = repositoryConfiguration.getMarkupLanguage();
        description.setMarkLanguage(markupLanguage);
        commentsPanel.setMarkupLanguage(markupLanguage);

        // Editable fields
        driver.edit(task);

        updateAcceptableValues();
        updateIterations(repositoryConfiguration.getValidIterationsForTask(task));

        for (InlineEditableField<?> f : inlineEditFields) {
            f.renderTask(task);
        }

        duplicates.clear();
        UIObject.setVisible(duplicatesElement, !task.getDuplicates().isEmpty());
        for (int i = 0; i < task.getDuplicates().size(); i++) {
            if (i != 0) {
                duplicates.add(new Label(commonProfileMessages.comma() + " "));
            }
            duplicates.add(createTaskAnchor(task.getDuplicates().get(i)));
        }

        // external task relations
        externalTaskRelations.clear();
        List<ExternalTaskRelation> externalList = task.getExternalTaskRelations();
        UIObject.setVisible(externalDepsElement, !externalList.isEmpty());
        for (int i = 0; i < externalList.size(); i++) {
            externalTaskRelations
                    .add(new Anchor(externalList.get(i).getUri(), externalList.get(i).getUri(), "new"));
            if (i < (externalList.size() - 1)) {
                externalTaskRelations.add(new Label(commonProfileMessages.comma() + " "));
            }
        }

        commits.clear();
        UIObject.setVisible(commitsElement, !task.getCommits().isEmpty());
        for (int i = 0; i < task.getCommits().size(); i++) {
            String url = task.getCommits().get(i);

            if (i > 0) {
                commits.add(new Label(commonProfileMessages.comma() + " "));
            }
            if (url.startsWith("http")) {
                String id = url;
                int lastSlash = url.lastIndexOf("/");
                if (lastSlash > 0) {
                    id = url.substring(lastSlash + 1);
                }
                id = Commit.minimizeCommitId(id);
                // TODO make relative or new
                commits.add(new Anchor(id, url));
            } else {
                commits.add(new Label(url));
            }

        }

        // attachments
        setAttachments(task.getAttachments());

        taskHistoryLink.setHref(ProjectTaskHistoryPlace.createPlace(projectIdentifier, task.getId()).getHref());

        updateCustomFields(task);

    }

    private void updateCustomFields(Task task) {
        customFieldsPanel.clear();

        List<CustomField> customFields = new ArrayList<CustomField>(
                repositoryConfiguration.getCustomFields().size());

        for (FieldDescriptor field : repositoryConfiguration.getCustomFields()) {
            if (field.isObsolete()) {
                continue;
            }
            String fieldValue = task.getCustomFields().get(field.getName());
            customFields.add(new CustomField(field, fieldValue));

        }

        customFieldDriver.edit(customFields);

        List<InlineEditableField<?>> customInlineEditFields = new ArrayList<InlineEditableField<?>>();

        for (final CustomFieldEditor editor : customFieldEditor.getEditors()) {

            InlineEditableCustomField customFieldWidget = new InlineEditableCustomField(editor);
            customFieldsPanel.add(customFieldWidget);

            customInlineEditFields.add(new InlineEditableField<CustomField>(customFieldWidget.getReadOnlyField(),
                    customFieldWidget.getEditFieldAnchor(), editor) {

                @Override
                protected void flushTask() {
                    CustomField edited = editor.getValue();
                    presenter.saveCustomField(edited.getFieldDescriptor().getName(), edited.getValue());
                }

                @Override
                protected SafeHtml getHtml(Task t) {
                    String value = t.getCustomFields().get(editor.getFieldDescriptor().getName());
                    final String displayValue;
                    if (value == null || value.isEmpty()) {
                        displayValue = NOT_SET;
                    } else if (editor.getFieldDescriptor().getFieldType().equals(FieldType.TIMESTAMP)) {
                        displayValue = Format.stringValueDateTime(new Date(Long.parseLong(value)));
                    } else {
                        displayValue = value;
                    }
                    return SafeHtmlUtils.fromString(displayValue);
                }

                @Override
                protected Element getEditableElement() {
                    return editor.getWidget().getElement();
                }
            });

        }

        for (InlineEditableField<?> f : customInlineEditFields) {
            f.renderTask(task);
        }

    }

    private SafeHtml getHtmlForKeywords(List<Keyword> keywords) {

        // Create strings from our keywords
        List<String> keywordNames = new ArrayList<String>(keywords.size());
        for (Keyword curKeyword : keywords) {
            keywordNames.add(curKeyword.getName());
        }

        return getHtmlForStrings(keywordNames);
    }

    private SafeHtml getHtmlForPeople(List<TaskUserProfile> people) {

        // Create strings from our keywords
        List<String> peopleNames = new ArrayList<String>(people.size());
        for (TaskUserProfile curPerson : people) {
            peopleNames.add(curPerson.getRealname());
        }

        return getHtmlForStrings(peopleNames);
    }

    private SafeHtml getHtmlForStrings(List<String> strings) {
        SafeHtmlBuilder shb = new SafeHtmlBuilder();

        for (int i = 0; i < strings.size(); i++) {
            // First, append this string as an escaped value to prevent XSS
            shb.appendEscaped(strings.get(i));

            // Then, if this isn't the last tag, append a <br/>
            if (i < (strings.size() - 1)) {
                shb.appendHtmlConstant("<br/>");
            }
        }

        // Check if we ended up with a blank string - if we did, send back a default
        if (strings.size() == 0) {
            shb.appendEscaped(commonProfileMessages.none());
        }

        return shb.toSafeHtml();
    }

    private TaskAnchor createTaskAnchor(Task sourceTask) {
        Integer id = sourceTask.getId();
        String url = sourceTask.getUrl();
        TaskAnchor anchor = TaskAnchorManager.createAnchor(projectIdentifier, id, id.toString(), url);
        if (sourceTask.getStatus() != null && !sourceTask.getStatus().isOpen()) {
            anchor.addStyleName(TaskResources.resources.style().completeTask());
        }
        return anchor;
    }

    public void updateCommentView(Task task) {
        commentsPanel.setValue(task);
    }

    private void setAttachments(List<Attachment> attachments) {
        attachmentsPanel.clear();
        if (attachments.size() <= 0) {
            attachmentsPanel.add(new Label(commonProfileMessages.none()));
            return;
        }

        for (int i = 0; i < attachments.size(); i++) {
            Attachment attachment = attachments.get(i);
            Panel attachmentPanel = new FlowPanel();
            attachmentPanel.setStyleName("file-uploaded");
            attachmentPanel.add(new HTML("<span class=\"file-icon\"/>"));
            attachmentPanel.add(new Anchor(attachment.getFilename(), attachment.getUrl()));
            attachmentPanel.add(new Label("   " + commonProfileMessages.parentheses(
                    String.valueOf(Math.round(Float.valueOf(attachment.getByteSize()) / 1024)) + " KB")));
            attachmentsPanel.add(attachmentPanel);
        }
    }

    @Override
    public void setSelf(Person self) {
        commentsPanel.setSelf(self);
    }

    /**
     * @param presenter
     *            the presenter to set
     */
    public void setPresenter(TaskPresenter taskPresenter) {
        this.presenter = taskPresenter;
    }

    private void postComment() {
        presenter.postComment(commentsPanel.getText());
    }

    private void startInlineEdit(InlineEditableField<?> field) {
        // Currently not supporting editing multiple fields in one go.
        for (InlineEditableField<?> inEditField : editingFields) {
            if (driver.isDirty() || customFieldDriver.isDirty()) {
                // FIXME how to handle this?
                Window.alert(tasksMessages.saveOrCancelCurrentEdit());
                return;
            }
            inEditField.cancelEdit();
        }
        editingFields.clear();
        editingFields.add(field);
        adjustEditControls(field);
        field.startEdit();
    }

    private void adjustEditControls(InlineEditableField<?> fieldOrNull) {
        boolean hide = fieldOrNull == null;
        editControlsDiv.removeFromParent();
        UIObject.setVisible(editControlsDiv, !hide);

        if (!hide) {
            InlineEditableField<?> field = fieldOrNull;
            field.getEditableElement().getParentElement().appendChild(editControlsDiv);

            // Seems we lose the clickhandlers with DOM remove so must re-add them each time.
            saveButton.addClickHandler(new ClickHandler() {

                @Override
                public void onClick(ClickEvent event) {
                    onSave(event);
                }
            });

            cancelButton.addClickHandler(new ClickHandler() {

                @Override
                public void onClick(ClickEvent event) {
                    onCancel(event);
                }
            });

        }
    }

    private void saveInlineEdit(InlineEditableField<?> field) {
        field.saveEdit();
        lastEditedField = field;
        completeInlineEdit(field);
    }

    private void cancelInlineEdit(InlineEditableField<?> field) {
        field.cancelEdit();
        completeInlineEdit(field);
    }

    private void completeInlineEdit(InlineEditableField<?> field) {
        editingFields.remove(field);

        adjustEditControls(null);
    }

    protected void onSave(ClickEvent e) {
        for (InlineEditableField<?> field : new ArrayList<InlineEditableField<?>>(editingFields)) {
            saveInlineEdit(field);
        }
    }

    protected void onCancel(ClickEvent e) {
        for (InlineEditableField<?> field : new ArrayList<InlineEditableField<?>>(editingFields)) {
            cancelInlineEdit(field);
        }
        driver.edit(task); // Resets the editiable fields
        updateCustomFields(task);
    }

    @Override
    public void setRepositoryConfiguration(RepositoryConfiguration repositoryConfiguration) {
        this.repositoryConfiguration = repositoryConfiguration;
        statusEditor.setRepositoryConfiguration(repositoryConfiguration);
    }

    private void updateAcceptableValues() {
        priority.setAcceptableValues(repositoryConfiguration.getPriorities());
        severity.setAcceptableValues(repositoryConfiguration.getSeverities());
        product.setAcceptableValues(repositoryConfiguration.getProducts());
        component.setAcceptableValues(repositoryConfiguration.getComponents(product.getValue()));
        milestone.setAcceptableValues(repositoryConfiguration.getMilestones(product.getValue()));
        taskType.setAcceptableValues(repositoryConfiguration.getTaskTypes());
    }

    private void updateIterations(List<Iteration> iterations) {
        iteration.setAcceptableValues(iterations);
    }

    public void setPersonServices(ProjectPersonService personService, Person self) {
        assignee.setSuggestionService(personService);
        assignee.setSelf(self);
        // assignee.addLabel(ownerLabel); ??

        watchers.setSuggestionService(personService);
        watchers.setSelf(self);
        // watchers.addLabel(ccLabel);
    }

    @Override
    public void setCommentText(String text) {
        commentsPanel.setText(text);
    }

    @Override
    public String getCommentText() {
        return commentsPanel.getText();
    }

    @Override
    public boolean isDirty() {
        return commentsPanel.isDirty() || driver.isDirty() || customFieldDriver.isDirty();
    }

    @Override
    public void reEnterEditMode() {
        startInlineEdit(lastEditedField);
    }

    public FocusWidget getCommentButton() {
        return commentsPanel.postComment;
    }
}