org.eclipse.mylyn.tasks.ui.editors.AbstractTaskEditorPage.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.mylyn.tasks.ui.editors.AbstractTaskEditorPage.java

Source

/*******************************************************************************
 * Copyright (c) 2004, 2010 Tasktop Technologies and others.
 * 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
 *     David Green - fixes for bug 237503
 *     Frank Becker - fixes for bug 252300
 *      Kevin Sawicki - fixes for bug 306029
 *******************************************************************************/

package org.eclipse.mylyn.tasks.ui.editors;

import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.ISafeRunnable;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.ListenerList;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.SafeRunner;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IMenuCreator;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.dialogs.IMessageProvider;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.jface.text.TextSelection;
import org.eclipse.jface.util.LocalSelectionTransfer;
import org.eclipse.jface.util.SafeRunnable;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.window.Window;
import org.eclipse.mylyn.commons.core.StatusHandler;
import org.eclipse.mylyn.commons.ui.CommonImages;
import org.eclipse.mylyn.commons.ui.CommonUiUtil;
import org.eclipse.mylyn.commons.ui.GradientCanvas;
import org.eclipse.mylyn.commons.workbench.editors.CommonTextSupport;
import org.eclipse.mylyn.commons.workbench.forms.CommonFormUtil;
import org.eclipse.mylyn.internal.tasks.core.AbstractTaskContainer;
import org.eclipse.mylyn.internal.tasks.core.ITaskListChangeListener;
import org.eclipse.mylyn.internal.tasks.core.ITaskListRunnable;
import org.eclipse.mylyn.internal.tasks.core.TaskContainerDelta;
import org.eclipse.mylyn.internal.tasks.core.data.ITaskDataManagerListener;
import org.eclipse.mylyn.internal.tasks.core.data.TaskDataManagerEvent;
import org.eclipse.mylyn.internal.tasks.ui.TasksUiPlugin;
import org.eclipse.mylyn.internal.tasks.ui.actions.ClearOutgoingAction;
import org.eclipse.mylyn.internal.tasks.ui.actions.DeleteTaskEditorAction;
import org.eclipse.mylyn.internal.tasks.ui.actions.NewSubTaskAction;
import org.eclipse.mylyn.internal.tasks.ui.actions.OpenWithBrowserAction;
import org.eclipse.mylyn.internal.tasks.ui.actions.SynchronizeEditorAction;
import org.eclipse.mylyn.internal.tasks.ui.editors.AbstractTaskEditorSection;
import org.eclipse.mylyn.internal.tasks.ui.editors.EditorUtil;
import org.eclipse.mylyn.internal.tasks.ui.editors.FocusTracker;
import org.eclipse.mylyn.internal.tasks.ui.editors.Messages;
import org.eclipse.mylyn.internal.tasks.ui.editors.TaskAttachmentDropListener;
import org.eclipse.mylyn.internal.tasks.ui.editors.TaskEditorActionContributor;
import org.eclipse.mylyn.internal.tasks.ui.editors.TaskEditorActionPart;
import org.eclipse.mylyn.internal.tasks.ui.editors.TaskEditorAttachmentPart;
import org.eclipse.mylyn.internal.tasks.ui.editors.TaskEditorAttributePart;
import org.eclipse.mylyn.internal.tasks.ui.editors.TaskEditorCommentPart;
import org.eclipse.mylyn.internal.tasks.ui.editors.TaskEditorContributionExtensionReader;
import org.eclipse.mylyn.internal.tasks.ui.editors.TaskEditorDescriptionPart;
import org.eclipse.mylyn.internal.tasks.ui.editors.TaskEditorFindSupport;
import org.eclipse.mylyn.internal.tasks.ui.editors.TaskEditorNewCommentPart;
import org.eclipse.mylyn.internal.tasks.ui.editors.TaskEditorOutlineNode;
import org.eclipse.mylyn.internal.tasks.ui.editors.TaskEditorOutlinePage;
import org.eclipse.mylyn.internal.tasks.ui.editors.TaskEditorPeoplePart;
import org.eclipse.mylyn.internal.tasks.ui.editors.TaskEditorPlanningPart;
import org.eclipse.mylyn.internal.tasks.ui.editors.TaskEditorRichTextPart;
import org.eclipse.mylyn.internal.tasks.ui.editors.TaskEditorSummaryPart;
import org.eclipse.mylyn.internal.tasks.ui.editors.TaskMigrator;
import org.eclipse.mylyn.internal.tasks.ui.editors.ToolBarButtonContribution;
import org.eclipse.mylyn.internal.tasks.ui.util.AttachmentUtil;
import org.eclipse.mylyn.internal.tasks.ui.util.TasksUiInternal;
import org.eclipse.mylyn.tasks.core.AbstractRepositoryConnector;
import org.eclipse.mylyn.tasks.core.IRepositoryElement;
import org.eclipse.mylyn.tasks.core.ITask;
import org.eclipse.mylyn.tasks.core.ITask.SynchronizationState;
import org.eclipse.mylyn.tasks.core.RepositoryStatus;
import org.eclipse.mylyn.tasks.core.TaskRepository;
import org.eclipse.mylyn.tasks.core.data.ITaskDataWorkingCopy;
import org.eclipse.mylyn.tasks.core.data.TaskAttribute;
import org.eclipse.mylyn.tasks.core.data.TaskData;
import org.eclipse.mylyn.tasks.core.data.TaskDataModel;
import org.eclipse.mylyn.tasks.core.data.TaskDataModelEvent;
import org.eclipse.mylyn.tasks.core.data.TaskDataModelListener;
import org.eclipse.mylyn.tasks.core.data.TaskRelation;
import org.eclipse.mylyn.tasks.core.sync.SubmitJob;
import org.eclipse.mylyn.tasks.core.sync.SubmitJobEvent;
import org.eclipse.mylyn.tasks.core.sync.SubmitJobListener;
import org.eclipse.mylyn.tasks.ui.AbstractRepositoryConnectorUi;
import org.eclipse.mylyn.tasks.ui.TasksUi;
import org.eclipse.mylyn.tasks.ui.TasksUiImages;
import org.eclipse.mylyn.tasks.ui.TasksUiUtil;
import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.DropTarget;
import org.eclipse.swt.dnd.FileTransfer;
import org.eclipse.swt.dnd.TextTransfer;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.ScrollBar;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorSite;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.actions.ActionFactory;
import org.eclipse.ui.forms.FormColors;
import org.eclipse.ui.forms.IFormColors;
import org.eclipse.ui.forms.IFormPart;
import org.eclipse.ui.forms.IManagedForm;
import org.eclipse.ui.forms.editor.FormPage;
import org.eclipse.ui.forms.events.HyperlinkAdapter;
import org.eclipse.ui.forms.events.HyperlinkEvent;
import org.eclipse.ui.forms.widgets.ExpandableComposite;
import org.eclipse.ui.forms.widgets.FormToolkit;
import org.eclipse.ui.forms.widgets.ScrolledForm;
import org.eclipse.ui.handlers.IHandlerService;
import org.eclipse.ui.services.IDisposable;
import org.eclipse.ui.views.contentoutline.IContentOutlinePage;

/**
 * Extend to provide a task editor page.
 * 
 * @author Mik Kersten
 * @author Rob Elves
 * @author Steffen Pingel
 * @since 3.0
 */
public abstract class AbstractTaskEditorPage extends TaskFormPage
        implements ISelectionProvider, ISelectionChangedListener {

    /**
     * Causes the form page to reflow on resize.
     */
    private final class ParentResizeHandler implements Listener {
        private int generation;

        public void handleEvent(Event event) {
            ++generation;

            Display.getCurrent().timerExec(300, new Runnable() {
                int scheduledGeneration = generation;

                public void run() {
                    if (getManagedForm().getForm().isDisposed()) {
                        return;
                    }

                    // only reflow if this is the latest generation to prevent
                    // unnecessary reflows while the form is being resized
                    if (scheduledGeneration == generation) {
                        getManagedForm().reflow(true);
                    }
                }
            });
        }
    }

    private class SubmitTaskJobListener extends SubmitJobListener {

        private final boolean attachContext;

        private final boolean expandLastComment;

        public SubmitTaskJobListener(boolean attachContext, boolean expandLastComment) {
            this.attachContext = attachContext;
            this.expandLastComment = expandLastComment;
        }

        @Override
        public void done(SubmitJobEvent event) {
            final SubmitJob job = event.getJob();
            PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() {

                private void addTask(ITask newTask) {
                    AbstractTaskContainer parent = null;
                    AbstractTaskEditorPart actionPart = getPart(ID_PART_ACTIONS);
                    if (actionPart instanceof TaskEditorActionPart) {
                        parent = ((TaskEditorActionPart) actionPart).getCategory();
                    }
                    TasksUiInternal.getTaskList().addTask(newTask, parent);
                }

                public void run() {
                    try {
                        if (job.getStatus() == null) {
                            TasksUiInternal.synchronizeRepositoryInBackground(getTaskRepository());
                            if (job.getTask().equals(getTask())) {
                                refresh();
                            } else {
                                ITask oldTask = getTask();
                                ITask newTask = job.getTask();
                                addTask(newTask);

                                TaskMigrator migrator = new TaskMigrator(oldTask);
                                migrator.setDelete(true);
                                migrator.setEditor(getTaskEditor());
                                migrator.setMigrateDueDate(
                                        !connector.hasRepositoryDueDate(getTaskRepository(), newTask, taskData));
                                migrator.execute(newTask);
                            }
                            if (expandLastComment) {
                                expandLastComment();
                            }
                        }
                        handleTaskSubmitted(new SubmitJobEvent(job));
                    } finally {
                        showEditorBusy(false);
                    }
                }
            });
        }

        @Override
        public void taskSubmitted(SubmitJobEvent event, IProgressMonitor monitor) throws CoreException {
            if (!getModel().getTaskData().isNew() && attachContext) {
                TaskData taskData = getModel().getTaskData();
                TaskAttribute taskAttribute = null;
                if (taskData != null) {
                    taskAttribute = taskData.getRoot().createMappedAttribute(TaskAttribute.NEW_ATTACHMENT);
                }
                AttachmentUtil.postContext(connector, getModel().getTaskRepository(), task, "", taskAttribute, //$NON-NLS-1$
                        monitor);
            }
        }

        @Override
        public void taskSynchronized(SubmitJobEvent event, IProgressMonitor monitor) {
        }

    }

    //   private class TaskListChangeListener extends TaskListChangeAdapter {
    //      @Override
    //      public void containersChanged(Set<TaskContainerDelta> containers) {
    //         if (refreshDisabled) {
    //            return;
    //         }
    //         ITask taskToRefresh = null;
    //         for (TaskContainerDelta taskContainerDelta : containers) {
    //            if (task.equals(taskContainerDelta.getElement())) {
    //               if (taskContainerDelta.getKind().equals(TaskContainerDelta.Kind.CONTENT)
    //                     && !taskContainerDelta.isTransient()) {
    //                  taskToRefresh = (ITask) taskContainerDelta.getElement();
    //                  break;
    //               }
    //            }
    //         }
    //         if (taskToRefresh != null) {
    //            PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() {
    //               public void run() {
    //                  if (!isDirty() && task.getSynchronizationState() == SynchronizationState.SYNCHRONIZED) {
    //                     // automatically refresh if the user has not made any changes and there is no chance of missing incomings
    //                     refreshFormContent();
    //                  } else {
    //                     getTaskEditor().setMessage("Task has incoming changes", IMessageProvider.WARNING,
    //                           new HyperlinkAdapter() {
    //                              @Override
    //                              public void linkActivated(HyperlinkEvent e) {
    //                                 refreshFormContent();
    //                              }
    //                           });
    //                     setSubmitEnabled(false);
    //                  }
    //               }
    //            });
    //         }
    //      }
    //   }

    private final ITaskDataManagerListener TASK_DATA_LISTENER = new ITaskDataManagerListener() {

        public void taskDataUpdated(final TaskDataManagerEvent event) {
            ITask task = event.getTask();
            if (task.equals(AbstractTaskEditorPage.this.getTask()) && event.getTaskDataUpdated()) {
                refresh(task);
            }
        }

        private void refresh(final ITask task) {
            PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() {
                public void run() {
                    if (refreshDisabled || busy) {
                        return;
                    }

                    if (!isDirty() && task.getSynchronizationState() == SynchronizationState.SYNCHRONIZED) {
                        // automatically refresh if the user has not made any changes and there is no chance of missing incomings
                        AbstractTaskEditorPage.this.refresh();
                    } else {
                        getTaskEditor().setMessage(Messages.AbstractTaskEditorPage_Task_has_incoming_changes,
                                IMessageProvider.WARNING, new HyperlinkAdapter() {
                                    @Override
                                    public void linkActivated(HyperlinkEvent e) {
                                        AbstractTaskEditorPage.this.refresh();
                                    }
                                });
                        setSubmitEnabled(false);
                    }
                }
            });
        }

        public void editsDiscarded(TaskDataManagerEvent event) {
            if (event.getTask().equals(AbstractTaskEditorPage.this.getTask())) {
                refresh(event.getTask());
            }
        }
    };

    private class NotInTaskListListener extends HyperlinkAdapter implements ITaskListChangeListener, IDisposable {

        public NotInTaskListListener() {
            TasksUiPlugin.getTaskList().addChangeListener(this);
        }

        @Override
        public void linkActivated(HyperlinkEvent e) {
            TasksUiPlugin.getTaskList().addTaskIfAbsent(task);
            getTaskEditor().setMessage(null, IMessageProvider.NONE);
        }

        public void containersChanged(Set<TaskContainerDelta> containers) {
            // clears message if task is added to Task List.
            for (TaskContainerDelta taskContainerDelta : containers) {
                if (task.equals(taskContainerDelta.getElement())) {
                    if (taskContainerDelta.getKind().equals(TaskContainerDelta.Kind.ADDED)) {
                        PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() {
                            public void run() {
                                getTaskEditor().setMessage(null, IMessageProvider.NONE);
                            }
                        });
                    }
                }
            }
        }

        public void dispose() {
            TasksUiPlugin.getTaskList().removeChangeListener(this);
        }

    };

    private class MenuCreator implements IMenuCreator {

        private MenuManager menuManager;

        private Menu menu;

        public MenuCreator() {
        }

        public void dispose() {
            if (menu != null) {
                menu.dispose();
                menu = null;
            }
            if (menuManager != null) {
                menuManager.dispose();
                menuManager = null;
            }
        }

        public Menu getMenu(Control parent) {
            if (menuManager == null) {
                menuManager = new MenuManager();
                initialize(menuManager);
            }
            return menuManager.createContextMenu(parent);
        }

        public Menu getMenu(Menu parent) {
            return null;
        }

        protected void initialize(MenuManager menuManager) {
        }

    }

    private static final String ERROR_NOCONNECTIVITY = Messages.AbstractTaskEditorPage_Unable_to_submit_at_this_time;

    public static final String ID_PART_ACTIONS = "org.eclipse.mylyn.tasks.ui.editors.parts.actions"; //$NON-NLS-1$

    public static final String ID_PART_ATTACHMENTS = "org.eclipse.mylyn.tasks.ui.editors.parts.attachments"; //$NON-NLS-1$

    public static final String ID_PART_ATTRIBUTES = "org.eclipse.mylyn.tasks.ui.editors.parts.attributes"; //$NON-NLS-1$

    public static final String ID_PART_COMMENTS = "org.eclipse.mylyn.tasks.ui.editors.parts.comments"; //$NON-NLS-1$

    public static final String ID_PART_DESCRIPTION = "org.eclipse.mylyn.tasks.ui.editors.parts.descriptions"; //$NON-NLS-1$

    public static final String ID_PART_NEW_COMMENT = "org.eclipse.mylyn.tasks.ui.editors.parts.newComment"; //$NON-NLS-1$

    public static final String ID_PART_PEOPLE = "org.eclipse.mylyn.tasks.ui.editors.parts.people"; //$NON-NLS-1$

    public static final String ID_PART_PLANNING = "org.eclipse.mylyn.tasks.ui.editors.parts.planning"; //$NON-NLS-1$

    public static final String ID_PART_SUMMARY = "org.eclipse.mylyn.tasks.ui.editors.parts.summary"; //$NON-NLS-1$

    public static final String PATH_ACTIONS = "actions"; //$NON-NLS-1$

    /**
     * @since 3.7
     */
    public static final String PATH_ASSOCIATIONS = "associations"; //$NON-NLS-1$

    public static final String PATH_ATTACHMENTS = "attachments"; //$NON-NLS-1$

    public static final String PATH_ATTRIBUTES = "attributes"; //$NON-NLS-1$

    public static final String PATH_COMMENTS = "comments"; //$NON-NLS-1$

    public static final String PATH_HEADER = "header"; //$NON-NLS-1$

    public static final String PATH_PEOPLE = "people"; //$NON-NLS-1$

    public static final String PATH_PLANNING = "planning"; //$NON-NLS-1$

    //   private static final String ID_POPUP_MENU = "org.eclipse.mylyn.tasks.ui.editor.menu.page";

    private AttributeEditorFactory attributeEditorFactory;

    private AttributeEditorToolkit attributeEditorToolkit;

    private AbstractRepositoryConnector connector;

    private final String connectorKind;

    private StructuredSelection defaultSelection;

    private Composite editorComposite;

    private ScrolledForm form;

    private boolean busy;

    private ISelection lastSelection;

    private TaskDataModel model;

    private boolean needsAddToCategory;

    private boolean reflow;

    private volatile boolean refreshDisabled;

    private final ListenerList selectionChangedListeners;

    private SynchronizeEditorAction synchronizeEditorAction;

    private ITask task;

    private TaskData taskData;

    //   private ITaskListChangeListener taskListChangeListener;

    private FormToolkit toolkit;

    private TaskEditorOutlinePage outlinePage;

    private TaskAttachmentDropListener defaultDropListener;

    private CommonTextSupport textSupport;

    private Composite partControl;

    private GradientCanvas footerComposite;

    private boolean needsFooter;

    private Button submitButton;

    private boolean submitEnabled;

    private boolean needsSubmit;

    private boolean needsSubmitButton;

    private boolean needsPrivateSection;

    private FocusTracker focusTracker;

    private TaskEditorFindSupport findSupport;

    /**
     * @since 3.1
     */
    public AbstractTaskEditorPage(TaskEditor editor, String id, String label, String connectorKind) {
        super(editor, id, label);
        Assert.isNotNull(connectorKind);
        this.connectorKind = connectorKind;
        this.reflow = true;
        this.selectionChangedListeners = new ListenerList();
        this.submitEnabled = true;
        this.needsSubmit = true;
    }

    public AbstractTaskEditorPage(TaskEditor editor, String connectorKind) {
        this(editor, "id", "label", connectorKind); //$NON-NLS-1$ //$NON-NLS-2$
    }

    /**
     * @since 3.1
     * @see FormPage#getEditor()
     */
    @Override
    public TaskEditor getEditor() {
        return (TaskEditor) super.getEditor();
    }

    public void addSelectionChangedListener(ISelectionChangedListener listener) {
        selectionChangedListeners.add(listener);
    }

    public void appendTextToNewComment(String text) {
        AbstractTaskEditorPart newCommentPart = getPart(ID_PART_NEW_COMMENT);
        if (newCommentPart instanceof TaskEditorRichTextPart) {
            ((TaskEditorRichTextPart) newCommentPart).appendText(text);
            newCommentPart.setFocus();
        }
    }

    public boolean canPerformAction(String actionId) {
        if (findSupport != null && actionId.equals(ActionFactory.FIND.getId())) {
            return true;
        }
        return CommonTextSupport.canPerformAction(actionId, EditorUtil.getFocusControl(this));
    }

    public void close() {
        if (Display.getCurrent() != null) {
            getSite().getPage().closeEditor(getTaskEditor(), false);
        } else {
            // TODO consider removing asyncExec()
            Display activeDisplay = getSite().getShell().getDisplay();
            activeDisplay.asyncExec(new Runnable() {
                public void run() {
                    if (getSite() != null && getSite().getPage() != null
                            && !getManagedForm().getForm().isDisposed()) {
                        if (getTaskEditor() != null) {
                            getSite().getPage().closeEditor(getTaskEditor(), false);
                        } else {
                            getSite().getPage().closeEditor(AbstractTaskEditorPage.this, false);
                        }
                    }
                }
            });
        }
    }

    protected AttributeEditorFactory createAttributeEditorFactory() {
        return new AttributeEditorFactory(getModel(), getTaskRepository(), getEditorSite());
    }

    AttributeEditorToolkit createAttributeEditorToolkit() {
        return new AttributeEditorToolkit(textSupport);
    }

    @Override
    public void createPartControl(Composite parent) {
        parent.addListener(SWT.Resize, new ParentResizeHandler());

        if (needsFooter()) {
            partControl = getEditor().getToolkit().createComposite(parent);
            GridLayout partControlLayout = new GridLayout(1, false);
            partControlLayout.marginWidth = 0;
            partControlLayout.marginHeight = 0;
            partControlLayout.verticalSpacing = 0;
            partControl.setLayout(partControlLayout);

            super.createPartControl(partControl);
            getManagedForm().getForm().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));

            footerComposite = new GradientCanvas(partControl, SWT.NONE);
            footerComposite.setSeparatorVisible(true);
            footerComposite.setSeparatorAlignment(SWT.TOP);
            GridLayout headLayout = new GridLayout();
            headLayout.marginHeight = 0;
            headLayout.marginWidth = 0;
            headLayout.horizontalSpacing = 0;
            headLayout.verticalSpacing = 0;
            headLayout.numColumns = 1;
            footerComposite.setLayout(headLayout);
            footerComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));

            FormColors colors = getEditor().getToolkit().getColors();
            Color top = colors.getColor(IFormColors.H_GRADIENT_END);
            Color bottom = colors.getColor(IFormColors.H_GRADIENT_START);
            footerComposite.setBackgroundGradient(new Color[] { bottom, top }, new int[] { 100 }, true);

            footerComposite.putColor(IFormColors.H_BOTTOM_KEYLINE1, colors.getColor(IFormColors.H_BOTTOM_KEYLINE1));
            footerComposite.putColor(IFormColors.H_BOTTOM_KEYLINE2, colors.getColor(IFormColors.H_BOTTOM_KEYLINE2));
            footerComposite.putColor(IFormColors.H_HOVER_LIGHT, colors.getColor(IFormColors.H_HOVER_LIGHT));
            footerComposite.putColor(IFormColors.H_HOVER_FULL, colors.getColor(IFormColors.H_HOVER_FULL));
            footerComposite.putColor(IFormColors.TB_TOGGLE, colors.getColor(IFormColors.TB_TOGGLE));
            footerComposite.putColor(IFormColors.TB_TOGGLE_HOVER, colors.getColor(IFormColors.TB_TOGGLE_HOVER));
            footerComposite.setLayoutData(new GridData(SWT.FILL, SWT.BOTTOM, true, false));

            createFooterContent(footerComposite);
        } else {
            super.createPartControl(parent);
        }
    }

    @Override
    protected void createFormContent(final IManagedForm managedForm) {
        super.createFormContent(managedForm);
        form = managedForm.getForm();

        toolkit = managedForm.getToolkit();
        registerDefaultDropListener(form);
        CommonFormUtil.disableScrollingOnFocus(form);

        try {
            setReflow(false);

            editorComposite = form.getBody();
            // TODO consider using TableWrapLayout, it makes resizing much faster
            GridLayout editorLayout = new GridLayout();
            editorLayout.verticalSpacing = 0;
            editorComposite.setLayout(editorLayout);

            //form.setData("focusScrolling", Boolean.FALSE);

            //         menuManager = new MenuManager();
            //         menuManager.setRemoveAllWhenShown(true);
            //         getEditorSite().registerContextMenu(ID_POPUP_MENU, menuManager, this, true);
            //         editorComposite.setMenu(menuManager.createContextMenu(editorComposite));
            editorComposite.setMenu(getTaskEditor().getMenu());

            AbstractRepositoryConnectorUi connectorUi = TasksUiPlugin.getConnectorUi(getConnectorKind());
            if (connectorUi == null) {
                PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() {
                    public void run() {
                        getTaskEditor().setMessage(
                                Messages.AbstractTaskEditorPage_Synchronize_to_update_editor_contents,
                                IMessageProvider.INFORMATION, new HyperlinkAdapter() {
                                    @Override
                                    public void linkActivated(HyperlinkEvent e) {
                                        AbstractTaskEditorPage.this.refresh();
                                    }
                                });
                    }
                });
            }

            if (taskData != null) {
                createFormContentInternal();
            }

            updateHeaderMessage();
        } finally {
            setReflow(true);

            // if the editor is restored as part of workbench startup then we must reflow() asynchronously
            // otherwise the editor layout is incorrect
            boolean reflowRequired = calculateReflowRequired(form);

            if (reflowRequired) {
                Display.getCurrent().asyncExec(new Runnable() {
                    public void run() {
                        // this fixes a problem with layout that occurs when an editor
                        // is restored before the workbench is fully initialized
                        reflow();
                    }
                });
            }
        }
    }

    private boolean calculateReflowRequired(ScrolledForm form) {
        Composite stopComposite = getEditor().getEditorParent().getParent().getParent();
        Composite composite = form.getParent();
        while (composite != null) {
            Rectangle clientArea = composite.getClientArea();
            if (clientArea.width > 1) {
                return false;
            }
            if (composite == stopComposite) {
                return true;
            }
            composite = composite.getParent();
        }
        return true;
    }

    private void createFormContentInternal() {
        // end life-cycle of previous editor controls
        if (attributeEditorToolkit != null) {
            attributeEditorToolkit.dispose();
        }

        // start life-cycle of previous editor controls 
        if (attributeEditorFactory == null) {
            attributeEditorFactory = createAttributeEditorFactory();
            Assert.isNotNull(attributeEditorFactory);
        }
        attributeEditorToolkit = createAttributeEditorToolkit();
        Assert.isNotNull(attributeEditorToolkit);
        attributeEditorToolkit.setMenu(editorComposite.getMenu());
        attributeEditorFactory.setEditorToolkit(attributeEditorToolkit);

        createParts();

        focusTracker = new FocusTracker();
        focusTracker.track(editorComposite);
        //      AbstractTaskEditorPart summaryPart = getPart(ID_PART_SUMMARY);
        //      if (summaryPart != null) {
        //         lastFocusControl = summaryPart.getControl();
        //      }
    }

    protected TaskDataModel createModel(TaskEditorInput input) throws CoreException {
        ITaskDataWorkingCopy taskDataState;
        try {
            taskDataState = TasksUi.getTaskDataManager().getWorkingCopy(task);
        } catch (OperationCanceledException e) {
            // XXX retry once to work around bug 235479
            taskDataState = TasksUi.getTaskDataManager().getWorkingCopy(task);
        }
        TaskRepository taskRepository = TasksUi.getRepositoryManager()
                .getRepository(taskDataState.getConnectorKind(), taskDataState.getRepositoryUrl());
        return new TaskDataModel(taskRepository, input.getTask(), taskDataState);
    }

    /**
     * To suppress a section, just remove its descriptor from the list. To add your own section in a specific order on
     * the page, use the path value for where you want it to appear (your descriptor will appear after previously added
     * descriptors with the same path), and add it to the descriptors list in your override of this method.
     */
    protected Set<TaskEditorPartDescriptor> createPartDescriptors() {
        Set<TaskEditorPartDescriptor> descriptors = new LinkedHashSet<TaskEditorPartDescriptor>();
        descriptors.add(new TaskEditorPartDescriptor(ID_PART_SUMMARY) {
            @Override
            public AbstractTaskEditorPart createPart() {
                return new TaskEditorSummaryPart();
            }
        }.setPath(PATH_HEADER));
        descriptors.add(new TaskEditorPartDescriptor(ID_PART_ATTRIBUTES) {
            @Override
            public AbstractTaskEditorPart createPart() {
                return new TaskEditorAttributePart();
            }
        }.setPath(PATH_ATTRIBUTES));
        if (!taskData.isNew() && connector.getTaskAttachmentHandler() != null
                && (AttachmentUtil.canDownloadAttachment(task) || AttachmentUtil.canUploadAttachment(task))) {
            descriptors.add(new TaskEditorPartDescriptor(ID_PART_ATTACHMENTS) {
                @Override
                public AbstractTaskEditorPart createPart() {
                    return new TaskEditorAttachmentPart();
                }
            }.setPath(PATH_ATTACHMENTS));
        }
        if (needsPrivateSection() || taskData.isNew()) {
            descriptors.add(new TaskEditorPartDescriptor(ID_PART_PLANNING) {
                @Override
                public AbstractTaskEditorPart createPart() {
                    return new TaskEditorPlanningPart();
                }
            }.setPath(PATH_PLANNING));
        }
        descriptors.add(new TaskEditorPartDescriptor(ID_PART_DESCRIPTION) {
            @Override
            public AbstractTaskEditorPart createPart() {
                TaskEditorDescriptionPart part = new TaskEditorDescriptionPart();
                if (getModel().getTaskData().isNew()) {
                    part.setExpandVertically(true);
                    part.setSectionStyle(ExpandableComposite.TITLE_BAR | ExpandableComposite.EXPANDED);
                }
                return part;
            }
        }.setPath(PATH_COMMENTS));
        if (!taskData.isNew()) {
            descriptors.add(new TaskEditorPartDescriptor(ID_PART_COMMENTS) {
                @Override
                public AbstractTaskEditorPart createPart() {
                    return new TaskEditorCommentPart();
                }
            }.setPath(PATH_COMMENTS));
        }
        descriptors.add(new TaskEditorPartDescriptor(ID_PART_NEW_COMMENT) {
            @Override
            public AbstractTaskEditorPart createPart() {
                return new TaskEditorNewCommentPart();
            }
        }.setPath(PATH_COMMENTS));
        descriptors.add(new TaskEditorPartDescriptor(ID_PART_ACTIONS) {
            @Override
            public AbstractTaskEditorPart createPart() {
                return new TaskEditorActionPart();
            }
        }.setPath(PATH_ACTIONS));
        descriptors.add(new TaskEditorPartDescriptor(ID_PART_PEOPLE) {
            @Override
            public AbstractTaskEditorPart createPart() {
                return new TaskEditorPeoplePart();
            }
        }.setPath(PATH_PEOPLE));

        descriptors.addAll(getContributionPartDescriptors());
        return descriptors;
    }

    private Collection<TaskEditorPartDescriptor> getContributionPartDescriptors() {
        return TaskEditorContributionExtensionReader.getRepositoryEditorContributions();
    }

    protected void createParts() {
        List<TaskEditorPartDescriptor> descriptors = new LinkedList<TaskEditorPartDescriptor>(
                createPartDescriptors());
        // single column
        createParts(PATH_HEADER, editorComposite, descriptors);
        createParts(PATH_ASSOCIATIONS, editorComposite, descriptors);
        createParts(PATH_ATTRIBUTES, editorComposite, descriptors);
        createParts(PATH_ATTACHMENTS, editorComposite, descriptors);
        createParts(PATH_PLANNING, editorComposite, descriptors);
        createParts(PATH_COMMENTS, editorComposite, descriptors);
        // two column
        Composite bottomComposite = toolkit.createComposite(editorComposite);
        bottomComposite.setLayout(GridLayoutFactory.fillDefaults().numColumns(2).create());
        GridDataFactory.fillDefaults().grab(true, false).applyTo(bottomComposite);
        createParts(PATH_ACTIONS, bottomComposite, descriptors);
        createParts(PATH_PEOPLE, bottomComposite, descriptors);
        bottomComposite.pack(true);

    }

    private void createParts(String path, final Composite parent,
            final Collection<TaskEditorPartDescriptor> descriptors) {
        for (Iterator<TaskEditorPartDescriptor> it = descriptors.iterator(); it.hasNext();) {
            final TaskEditorPartDescriptor descriptor = it.next();
            if (path == null || path.equals(descriptor.getPath())) {
                SafeRunner.run(new ISafeRunnable() {
                    public void handleException(Throwable e) {
                        StatusHandler.log(new Status(IStatus.ERROR, TasksUiPlugin.ID_PLUGIN,
                                "Error creating task editor part: \"" + descriptor.getId() + "\"", e)); //$NON-NLS-1$ //$NON-NLS-2$
                    }

                    public void run() throws Exception {
                        AbstractTaskEditorPart part = descriptor.createPart();
                        part.setPartId(descriptor.getId());
                        initializePart(parent, part, descriptors);
                    }
                });
                it.remove();
            }
        }
    }

    private void createSubParts(final AbstractTaskEditorSection parentPart,
            final Collection<TaskEditorPartDescriptor> descriptors) {
        for (final TaskEditorPartDescriptor descriptor : descriptors) {
            int i;
            String path = descriptor.getPath();
            if (path != null && (i = path.indexOf("/")) != -1) { //$NON-NLS-1$
                String parentId = path.substring(0, i);
                final String subPath = path.substring(i + 1);
                if (parentId.equals(parentPart.getPartId())) {
                    SafeRunner.run(new ISafeRunnable() {
                        public void handleException(Throwable e) {
                            StatusHandler.log(new Status(IStatus.ERROR, TasksUiPlugin.ID_PLUGIN,
                                    "Error creating task editor part: \"" + descriptor.getId() + "\"", e)); //$NON-NLS-1$ //$NON-NLS-2$
                        }

                        public void run() throws Exception {
                            AbstractTaskEditorPart part = descriptor.createPart();
                            part.setPartId(descriptor.getId());
                            getManagedForm().addPart(part);
                            part.initialize(AbstractTaskEditorPage.this);
                            parentPart.addSubPart(subPath, part);
                        }
                    });
                }
            }
        }
    }

    @Override
    public void dispose() {
        if (textSupport != null) {
            textSupport.dispose();
        }
        if (attributeEditorToolkit != null) {
            attributeEditorToolkit.dispose();
        }
        TasksUiPlugin.getTaskDataManager().removeListener(TASK_DATA_LISTENER);
        super.dispose();
    }

    public void doAction(String actionId) {
        if (findSupport != null && actionId.equals(ActionFactory.FIND.getId())) {
            findSupport.toggleFind();
        }
        CommonTextSupport.doAction(actionId, EditorUtil.getFocusControl(this));
    }

    @Override
    public void doSave(IProgressMonitor monitor) {
        if (!isDirty()) {
            return;
        }

        getManagedForm().commit(true);

        if (model.isDirty()) {
            try {
                model.save(monitor);
            } catch (final CoreException e) {
                StatusHandler.log(new Status(IStatus.ERROR, TasksUiPlugin.ID_PLUGIN, "Error saving task", e)); //$NON-NLS-1$
                getTaskEditor().setMessage(Messages.AbstractTaskEditorPage_Could_not_save_task,
                        IMessageProvider.ERROR, new HyperlinkAdapter() {
                            @Override
                            public void linkActivated(HyperlinkEvent event) {
                                TasksUiInternal.displayStatus(Messages.AbstractTaskEditorPage_Save_failed,
                                        e.getStatus());
                            }
                        });
            }
        }
        // update the summary of unsubmitted repository tasks
        if (getTask().getSynchronizationState() == SynchronizationState.OUTGOING_NEW) {
            final String summary = connector.getTaskMapping(model.getTaskData()).getSummary();
            try {
                TasksUiPlugin.getTaskList().run(new ITaskListRunnable() {
                    public void execute(IProgressMonitor monitor) throws CoreException {
                        task.setSummary(summary);
                    }
                });
                TasksUiPlugin.getTaskList().notifyElementChanged(task);
            } catch (CoreException e) {
                StatusHandler.log(new Status(IStatus.ERROR, TasksUiPlugin.ID_PLUGIN,
                        "Failed to set summary for task \"" + task + "\"", e)); //$NON-NLS-1$ //$NON-NLS-2$
            }
        }

        TasksUiPlugin.getTaskList().addTaskIfAbsent(task);

        updateHeaderMessage();
        getManagedForm().dirtyStateChanged();
        getTaskEditor().updateHeaderToolBar();
    }

    @Override
    public void doSaveAs() {
        throw new UnsupportedOperationException();
    }

    public void doSubmit() {
        if (!submitEnabled || !needsSubmit()) {
            return;
        }

        try {
            showEditorBusy(true);

            doSave(new NullProgressMonitor());

            TaskAttribute newCommentAttribute = getModel().getTaskData().getRoot()
                    .getMappedAttribute(TaskAttribute.COMMENT_NEW);
            boolean expandLastComment = newCommentAttribute != null
                    && getModel().getChangedAttributes().contains(newCommentAttribute);

            SubmitJob submitJob = TasksUiInternal.getJobFactory().createSubmitTaskJob(connector,
                    getModel().getTaskRepository(), task, getModel().getTaskData(),
                    getModel().getChangedOldAttributes());
            submitJob.addSubmitJobListener(new SubmitTaskJobListener(getAttachContext(), expandLastComment));
            submitJob.schedule();
        } catch (RuntimeException e) {
            showEditorBusy(false);
            throw e;
        }

        TasksUiPlugin.getTaskList().addTaskIfAbsent(task);
    }

    /**
     * Override for customizing the tool bar.
     */
    @Override
    public void fillToolBar(IToolBarManager toolBarManager) {
        final TaskRepository taskRepository = (model != null) ? getModel().getTaskRepository() : null;

        if (taskData == null) {
            synchronizeEditorAction = new SynchronizeEditorAction();
            synchronizeEditorAction.selectionChanged(new StructuredSelection(getTaskEditor()));
            toolBarManager.appendToGroup("repository", synchronizeEditorAction); //$NON-NLS-1$
        } else {
            if (taskData.isNew()) {
                DeleteTaskEditorAction deleteAction = new DeleteTaskEditorAction(getTask());
                deleteAction.setImageDescriptor(CommonImages.CLEAR);
                toolBarManager.appendToGroup("new", deleteAction); //$NON-NLS-1$
            } else if (taskRepository != null) {
                ClearOutgoingAction clearOutgoingAction = new ClearOutgoingAction(
                        Collections.singletonList((IRepositoryElement) task));
                (clearOutgoingAction).setTaskEditorPage(this);
                if (clearOutgoingAction.isEnabled()) {
                    toolBarManager.appendToGroup("new", clearOutgoingAction); //$NON-NLS-1$
                }

                if (task.getSynchronizationState() != SynchronizationState.OUTGOING_NEW) {
                    synchronizeEditorAction = new SynchronizeEditorAction();
                    synchronizeEditorAction.selectionChanged(new StructuredSelection(getTaskEditor()));
                    toolBarManager.appendToGroup("repository", synchronizeEditorAction); //$NON-NLS-1$
                }

                NewSubTaskAction newSubTaskAction = new NewSubTaskAction();
                newSubTaskAction.selectionChanged(newSubTaskAction, new StructuredSelection(task));
                if (newSubTaskAction.isEnabled()) {
                    toolBarManager.appendToGroup("new", newSubTaskAction); //$NON-NLS-1$
                }

                AbstractRepositoryConnectorUi connectorUi = TasksUiPlugin
                        .getConnectorUi(taskData.getConnectorKind());
                if (connectorUi != null) {
                    final String historyUrl = connectorUi.getTaskHistoryUrl(taskRepository, task);
                    if (historyUrl != null) {
                        final Action historyAction = new Action() {
                            @Override
                            public void run() {
                                TasksUiUtil.openUrl(historyUrl);
                            }
                        };

                        historyAction.setText(Messages.AbstractTaskEditorPage_History);
                        historyAction.setImageDescriptor(TasksUiImages.TASK_REPOSITORY_HISTORY);
                        historyAction.setToolTipText(Messages.AbstractTaskEditorPage_History);
                        if (getEditor().openWithBrowserAction != null) {
                            getEditor().openWithBrowserAction.setMenuCreator(new MenuCreator() {
                                @Override
                                protected void initialize(MenuManager menuManager) {
                                    OpenWithBrowserAction openWithBrowserAction = new OpenWithBrowserAction();
                                    openWithBrowserAction.selectionChanged(new StructuredSelection(task));
                                    menuManager.add(openWithBrowserAction);
                                    menuManager.add(new Separator());
                                    menuManager.add(historyAction);
                                };
                            });
                        } else {
                            toolBarManager.prependToGroup("open", historyAction); //$NON-NLS-1$
                        }
                    }
                }
            }
            if (needsSubmitButton()) {
                ToolBarButtonContribution submitButtonContribution = new ToolBarButtonContribution(
                        "org.eclipse.mylyn.tasks.toolbars.submit") { //$NON-NLS-1$
                    @Override
                    protected Control createButton(Composite composite) {
                        submitButton = new Button(composite, SWT.FLAT);
                        submitButton.setText(Messages.TaskEditorActionPart_Submit + " "); //$NON-NLS-1$
                        submitButton.setImage(CommonImages.getImage(TasksUiImages.REPOSITORY_SUBMIT));
                        submitButton.setBackground(null);
                        submitButton.addListener(SWT.Selection, new Listener() {
                            public void handleEvent(Event e) {
                                doSubmit();
                            }
                        });
                        return submitButton;
                    }
                };
                submitButtonContribution.marginLeft = 10;
                toolBarManager.add(submitButtonContribution);
            }
            if (findSupport != null) {
                findSupport.addFindAction(toolBarManager);
            }
        }
    }

    protected void fireSelectionChanged(ISelection selection) {
        // create an event
        final SelectionChangedEvent event = new SelectionChangedEvent(this, selection);

        // fire the event
        Object[] listeners = selectionChangedListeners.getListeners();
        for (Object listener : listeners) {
            final ISelectionChangedListener l = (ISelectionChangedListener) listener;
            SafeRunner.run(new SafeRunnable() {
                public void run() {
                    l.selectionChanged(event);
                }
            });
        }
    }

    @SuppressWarnings("rawtypes")
    @Override
    public Object getAdapter(Class adapter) {
        if (adapter == IContentOutlinePage.class) {
            updateOutlinePage();
            return outlinePage;
        }
        // TODO 3.5 replace by getTextSupport() method
        if (adapter == CommonTextSupport.class) {
            return textSupport;
        }
        return super.getAdapter(adapter);
    }

    private void updateOutlinePage() {
        if (outlinePage == null) {
            outlinePage = new TaskEditorOutlinePage();
            outlinePage.addSelectionChangedListener(new ISelectionChangedListener() {
                public void selectionChanged(SelectionChangedEvent event) {
                    ISelection selection = event.getSelection();
                    if (selection instanceof StructuredSelection) {
                        Object select = ((StructuredSelection) selection).getFirstElement();
                        selectReveal(select);
                        getEditor().setActivePage(getId());
                    }
                }
            });
        }
        if (getModel() != null) {
            TaskEditorOutlineNode node = TaskEditorOutlineNode.parse(getModel().getTaskData(), false);
            outlinePage.setInput(getTaskRepository(), node);
        } else {
            outlinePage.setInput(null, null);
        }
    }

    private boolean getAttachContext() {
        AbstractTaskEditorPart actionPart = getPart(ID_PART_ACTIONS);
        if (actionPart instanceof TaskEditorActionPart) {
            return ((TaskEditorActionPart) actionPart).getAttachContext();
        }
        return false;
    }

    public AttributeEditorFactory getAttributeEditorFactory() {
        return attributeEditorFactory;
    }

    public AttributeEditorToolkit getAttributeEditorToolkit() {
        return attributeEditorToolkit;
    }

    public AbstractRepositoryConnector getConnector() {
        return connector;
    }

    public String getConnectorKind() {
        return connectorKind;
    }

    /**
     * @return The composite for the whole editor.
     */
    public Composite getEditorComposite() {
        return editorComposite;
    }

    public TaskDataModel getModel() {
        return model;
    }

    public AbstractTaskEditorPart getPart(String partId) {
        Assert.isNotNull(partId);
        if (getManagedForm() != null) {
            for (IFormPart part : getManagedForm().getParts()) {
                if (part instanceof AbstractTaskEditorPart) {
                    AbstractTaskEditorPart taskEditorPart = (AbstractTaskEditorPart) part;
                    if (partId.equals(taskEditorPart.getPartId())) {
                        return taskEditorPart;
                    }
                }
            }
        }
        return null;
    }

    public ISelection getSelection() {
        return lastSelection;
    }

    public ITask getTask() {
        return task;
    }

    public TaskEditor getTaskEditor() {
        return getEditor();
    }

    public TaskRepository getTaskRepository() {
        // FIXME model can be null
        return getModel().getTaskRepository();
    }

    /**
     * Invoked after task submission has completed. This method is invoked on the UI thread in all cases whether
     * submission was successful, canceled or failed. The value returned by <code>event.getJob().getStatus()</code>
     * indicates the result of the submit job. Sub-classes may override but are encouraged to invoke the super method.
     * 
     * @since 3.2
     * @see SubmitJob
     */
    protected void handleTaskSubmitted(SubmitJobEvent event) {
        IStatus status = event.getJob().getStatus();
        if (status != null && status.getSeverity() != IStatus.CANCEL) {
            handleSubmitError(event.getJob());
        }
    }

    private void handleSubmitError(SubmitJob job) {
        if (form != null && !form.isDisposed()) {
            final IStatus status = job.getStatus();
            String message = null;
            if (status.getCode() == RepositoryStatus.REPOSITORY_COMMENT_REQUIRED) {
                TasksUiInternal.displayStatus(Messages.AbstractTaskEditorPage_Comment_required, status);
                AbstractTaskEditorPart newCommentPart = getPart(ID_PART_NEW_COMMENT);
                if (newCommentPart != null) {
                    newCommentPart.setFocus();
                }
                return;
            } else if (status.getCode() == RepositoryStatus.ERROR_REPOSITORY_LOGIN) {
                if (TasksUiUtil.openEditRepositoryWizard(getTaskRepository()) == Window.OK) {
                    submitEnabled = true;
                    doSubmit();
                    return;
                } else {
                    message = getMessageFromStatus(status);
                }
            } else if (status.getCode() == RepositoryStatus.ERROR_IO) {
                message = ERROR_NOCONNECTIVITY;
            } else {
                message = getMessageFromStatus(status);
            }
            getTaskEditor().setMessage(message, IMessageProvider.ERROR, new HyperlinkAdapter() {
                @Override
                public void linkActivated(HyperlinkEvent e) {
                    TasksUiInternal.displayStatus(Messages.AbstractTaskEditorPage_Submit_failed, status);
                }
            });
        }
    }

    private String getMessageFromStatus(final IStatus status) {
        String message;
        if (status.getMessage().length() > 0) {
            if (status.getMessage().length() < 256) {
                message = Messages.AbstractTaskEditorPage_Submit_failed_ + status.getMessage();
            } else {
                message = Messages.AbstractTaskEditorPage_Submit_failed_ + status.getMessage().substring(0, 256)
                        + "..."; //$NON-NLS-1$
            }
        } else {
            message = Messages.AbstractTaskEditorPage_Submit_failed;
        }
        return message.replaceAll("\n", " ").replaceAll("\r", " "); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
    }

    @Override
    public void init(IEditorSite site, IEditorInput input) {
        super.init(site, input);

        TaskEditorInput taskEditorInput = (TaskEditorInput) input;
        this.task = taskEditorInput.getTask();
        this.defaultSelection = new StructuredSelection(task);
        this.lastSelection = defaultSelection;
        IHandlerService handlerService = (IHandlerService) getSite().getService(IHandlerService.class);
        this.textSupport = new CommonTextSupport(handlerService);
        this.textSupport.setSelectionChangedListener(this);
        createFindSupport();

        initModel(taskEditorInput);

        TasksUiPlugin.getTaskDataManager().addListener(TASK_DATA_LISTENER);
    }

    private void initModel(TaskEditorInput input) {
        Assert.isTrue(model == null);
        try {
            this.model = createModel(input);
            this.connector = TasksUi.getRepositoryManager().getRepositoryConnector(getConnectorKind());
            setTaskData(model.getTaskData());
            model.addModelListener(new TaskDataModelListener() {
                @Override
                public void attributeChanged(TaskDataModelEvent event) {
                    IManagedForm form = getManagedForm();
                    if (form != null) {
                        form.dirtyStateChanged();
                    }
                }
            });
            setNeedsAddToCategory(model.getTaskData().isNew());
        } catch (final CoreException e) {
            StatusHandler.log(new Status(IStatus.ERROR, TasksUiPlugin.ID_PLUGIN, "Error opening task", e)); //$NON-NLS-1$
            getTaskEditor().setStatus(Messages.AbstractTaskEditorPage_Error_opening_task,
                    Messages.AbstractTaskEditorPage_Open_failed, e.getStatus());
        }
    }

    private void initializePart(Composite parent, AbstractTaskEditorPart part,
            Collection<TaskEditorPartDescriptor> descriptors) {
        getManagedForm().addPart(part);
        part.initialize(this);
        if (part instanceof AbstractTaskEditorSection) {
            createSubParts((AbstractTaskEditorSection) part, descriptors);
        }
        if (parent != null) {
            part.createControl(parent, toolkit);
            if (part.getControl() != null) {
                if (ID_PART_ACTIONS.equals(part.getPartId())) {
                    // do not expand horizontally
                    GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL).grab(false, false)
                            .applyTo(part.getControl());
                } else {
                    if (part.getExpandVertically()) {
                        GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL).grab(true, true)
                                .applyTo(part.getControl());
                    } else {
                        GridDataFactory.fillDefaults().align(SWT.FILL, SWT.TOP).grab(true, false)
                                .applyTo(part.getControl());
                    }
                }
                // for outline
                if (ID_PART_COMMENTS.equals(part.getPartId())) {
                    EditorUtil.setMarker(part.getControl(), TaskEditorOutlineNode.LABEL_COMMENTS);
                } else if (ID_PART_ATTACHMENTS.equals(part.getPartId())) {
                    EditorUtil.setMarker(part.getControl(), TaskEditorOutlineNode.LABEL_ATTACHMENTS);
                }
            }
        }
    }

    /**
     * Subclasses may override to disable the task editor find functionality.
     * 
     * @since 3.11
     */
    protected void createFindSupport() {
        this.findSupport = new TaskEditorFindSupport(this);
    }

    @Override
    public boolean isDirty() {
        return (getModel() != null && getModel().isDirty())
                || (getManagedForm() != null && getManagedForm().isDirty());
    }

    @Override
    public boolean isSaveAsAllowed() {
        return false;
    }

    public boolean needsAddToCategory() {
        return needsAddToCategory;
    }

    /**
     * Force a re-layout of entire form.
     */
    public void reflow() {
        if (reflow) {
            try {
                form.setRedraw(false);
                // help the layout managers: ensure that the form width always matches
                // the parent client area width.
                Rectangle parentClientArea = form.getParent().getClientArea();
                Point formSize = form.getSize();
                if (formSize.x != parentClientArea.width) {
                    ScrollBar verticalBar = form.getVerticalBar();
                    int verticalBarWidth = verticalBar != null ? verticalBar.getSize().x : 15;
                    form.setSize(parentClientArea.width - verticalBarWidth, formSize.y);
                }

                form.layout(true, false);
                form.reflow(true);
            } finally {
                form.setRedraw(true);
            }
        }
    }

    /**
     * Updates the editor contents in place.
     * 
     * @deprecated Use {@link #refresh()} instead
     */
    @Deprecated
    public void refreshFormContent() {
        refresh();
    }

    /**
     * Updates the editor contents in place.
     */
    @Override
    public void refresh() {
        if (getManagedForm() == null || getManagedForm().getForm().isDisposed()) {
            // editor possibly closed or page has not been initialized
            return;
        }

        try {
            showEditorBusy(true);

            boolean hasIncoming = false;

            if (getTask() != null) {
                hasIncoming = getTask().getSynchronizationState().isIncoming();
            }
            if (model != null) {
                doSave(new NullProgressMonitor());
                refreshInput();
            } else {
                initModel(getTaskEditor().getTaskEditorInput());
            }

            if (taskData != null) {
                try {
                    setReflow(false);
                    // prevent menu from being disposed when disposing control on the form during refresh
                    Menu menu = editorComposite.getMenu();
                    CommonUiUtil.setMenu(editorComposite, null);

                    // clear old controls and parts
                    for (Control control : editorComposite.getChildren()) {
                        control.dispose();
                    }
                    if (focusTracker != null) {
                        focusTracker.reset();
                    }
                    lastSelection = null;
                    for (IFormPart part : getManagedForm().getParts()) {
                        part.dispose();
                        getManagedForm().removePart(part);
                    }

                    // restore menu
                    editorComposite.setMenu(menu);

                    createFormContentInternal();

                    getTaskEditor().setMessage(null, 0);
                    if (hasIncoming) {
                        getTaskEditor().setActivePage(getId());
                    }

                    setSubmitEnabled(true);
                } finally {
                    setReflow(true);
                }
            }

            updateOutlinePage();
            updateHeaderMessage();
            getManagedForm().dirtyStateChanged();
            getTaskEditor().updateHeaderToolBar();
        } finally {
            showEditorBusy(false);
        }
        reflow();
    }

    private void refreshInput() {
        try {
            refreshDisabled = true;
            model.refresh(null);
        } catch (CoreException e) {
            getTaskEditor().setMessage(Messages.AbstractTaskEditorPage_Failed_to_read_task_data_ + e.getMessage(),
                    IMessageProvider.ERROR);
            taskData = null;
            return;
        } finally {
            refreshDisabled = false;
        }

        setTaskData(model.getTaskData());
    }

    /**
     * Registers a drop listener for <code>control</code>. The default implementation registers a listener for attaching
     * files. Does nothing if the editor is showing a new task.
     * <p>
     * Clients may override.
     * </p>
     * 
     * @param control
     *            the control to register the listener for
     */
    public void registerDefaultDropListener(final Control control) {
        if (getModel() == null || getModel().getTaskData().isNew()) {
            return;
        }

        DropTarget target = new DropTarget(control, DND.DROP_COPY | DND.DROP_DEFAULT);
        LocalSelectionTransfer localSelectionTransfer = LocalSelectionTransfer.getTransfer();
        final TextTransfer textTransfer = TextTransfer.getInstance();
        final FileTransfer fileTransfer = FileTransfer.getInstance();
        Transfer[] types = new Transfer[] { localSelectionTransfer, textTransfer, fileTransfer };
        target.setTransfer(types);
        if (defaultDropListener == null) {
            defaultDropListener = new TaskAttachmentDropListener(this);
        }
        target.addDropListener(defaultDropListener);
    }

    public void removeSelectionChangedListener(ISelectionChangedListener listener) {
        selectionChangedListeners.remove(listener);
    }

    public void selectionChanged(Object element) {
        selectionChanged(new SelectionChangedEvent(this, new StructuredSelection(element)));
    }

    public void selectionChanged(SelectionChangedEvent event) {
        ISelection selection = event.getSelection();
        if (selection instanceof TextSelection) {
            // only update global actions
            ((TaskEditorActionContributor) getEditorSite().getActionBarContributor())
                    .updateSelectableActions(event.getSelection());
            return;
        }
        if (selection.isEmpty()) {
            // something was unselected, reset to default selection
            selection = defaultSelection;
            // XXX a styled text widget has lost focus, re-enable all edit actions
            ((TaskEditorActionContributor) getEditorSite().getActionBarContributor()).forceActionsEnabled();
        }
        if (!selection.equals(lastSelection)) {
            this.lastSelection = selection;
            fireSelectionChanged(lastSelection);
            getSite().getSelectionProvider().setSelection(selection);
        }
    }

    @Override
    public void setFocus() {
        if (focusTracker != null && focusTracker.setFocus()) {
            return;
        } else {
            IFormPart[] parts = getManagedForm().getParts();
            if (parts.length > 0) {
                parts[0].setFocus();
                return;
            }
        }
        super.setFocus();
    }

    public void setNeedsAddToCategory(boolean needsAddToCategory) {
        this.needsAddToCategory = needsAddToCategory;
    }

    public void setReflow(boolean reflow) {
        this.reflow = reflow;
        form.setRedraw(reflow);
    }

    public void setSelection(ISelection selection) {
        IFormPart[] parts = getManagedForm().getParts();
        for (IFormPart formPart : parts) {
            if (formPart instanceof AbstractTaskEditorPart) {
                if (((AbstractTaskEditorPart) formPart).setSelection(selection)) {
                    lastSelection = selection;
                    return;
                }
            }
        }
    }

    // TODO EDITOR this needs to be tracked somewhere else
    private void setSubmitEnabled(boolean enabled) {
        AbstractTaskEditorPart actionPart = getPart(ID_PART_ACTIONS);
        if (actionPart instanceof TaskEditorActionPart) {
            ((TaskEditorActionPart) actionPart).setSubmitEnabled(enabled);
        }
        if (submitButton != null && !submitButton.isDisposed()) {
            submitButton.setEnabled(enabled);
        }
        submitEnabled = enabled;
    }

    private void setTaskData(TaskData taskData) {
        this.taskData = taskData;
    }

    @Override
    public void showBusy(boolean busy) {
        if (getManagedForm() != null && !getManagedForm().getForm().isDisposed() && this.busy != busy) {
            setSubmitEnabled(!busy);
            CommonUiUtil.setEnabled(editorComposite, !busy);
            this.busy = busy;
        }
    }

    // TODO m4.0 remove
    public void showEditorBusy(boolean busy) {
        getTaskEditor().showBusy(busy);
        refreshDisabled = busy;
    }

    private void updateHeaderMessage() {
        if (taskData == null) {
            getTaskEditor().setMessage(Messages.AbstractTaskEditorPage_Synchronize_to_retrieve_task_data,
                    IMessageProvider.WARNING, new HyperlinkAdapter() {
                        @Override
                        public void linkActivated(HyperlinkEvent e) {
                            if (synchronizeEditorAction != null) {
                                synchronizeEditorAction.run();
                            }
                        }
                    });
        }
        if (getTaskEditor().getMessage() == null
                && TasksUiPlugin.getTaskList().getTask(task.getRepositoryUrl(), task.getTaskId()) == null) {
            getTaskEditor().setMessage(Messages.AbstractTaskEditorPage_Add_task_to_tasklist,
                    IMessageProvider.INFORMATION, new NotInTaskListListener());
        }
    }

    @Override
    public Control getPartControl() {
        return partControl != null ? partControl : super.getPartControl();
    }

    /**
     * Returns true, if the page has an always visible footer.
     * 
     * @see #setNeedsFooter(boolean)
     */
    private boolean needsFooter() {
        return needsFooter;
    }

    /**
     * Specifies that the page should provide an always visible footer. This flag is not set by default.
     * 
     * @see #createFooterContent(Composite)
     * @see #needsFooter()
     */
    @SuppressWarnings("unused")
    private void setNeedsFooter(boolean needsFooter) {
        this.needsFooter = needsFooter;
    }

    private void createFooterContent(Composite parent) {
        parent.setLayout(new GridLayout());

        //      submitButton = toolkit.createButton(parent, Messages.TaskEditorActionPart_Submit, SWT.NONE);
        //      GridData submitButtonData = new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING);
        //      submitButtonData.widthHint = 100;
        //      submitButton.setBackground(null);
        //      submitButton.setImage(CommonImages.getImage(TasksUiImages.REPOSITORY_SUBMIT));
        //      submitButton.setLayoutData(submitButtonData);
        //      submitButton.addListener(SWT.Selection, new Listener() {
        //         public void handleEvent(Event e) {
        //            doSubmit();
        //         }
        //      });
    }

    /**
     * Returns true, if the page supports a submit operation.
     * 
     * @since 3.2
     * @see #setNeedsSubmit(boolean)
     */
    public boolean needsSubmit() {
        return needsSubmit;
    }

    /**
     * Specifies that the page supports the submit operation. This flag is set to true by default.
     * 
     * @since 3.2
     * @see #needsSubmit()
     * @see #doSubmit()
     */
    public void setNeedsSubmit(boolean needsSubmit) {
        this.needsSubmit = needsSubmit;
    }

    /**
     * Returns true, if the page provides a submit button.
     * 
     * @since 3.2
     * @see #setNeedsSubmitButton(boolean)
     */
    public boolean needsSubmitButton() {
        return needsSubmitButton;
    }

    /**
     * Specifies that the page supports submitting. This flag is set to false by default.
     * 
     * @since 3.2
     * @see #needsSubmitButton()
     */
    public void setNeedsSubmitButton(boolean needsSubmitButton) {
        this.needsSubmitButton = needsSubmitButton;
    }

    /**
     * Returns true, if the page provides a submit button.
     * 
     * @since 3.2
     * @see #setNeedsPrivateSection(boolean)
     */
    public boolean needsPrivateSection() {
        return needsPrivateSection;
    }

    /**
     * Specifies that the page should provide the private section. This flag is not set by default.
     * 
     * @since 3.2
     * @see #needsPrivateSection()
     */
    public void setNeedsPrivateSection(boolean needsPrivateSection) {
        this.needsPrivateSection = needsPrivateSection;
    }

    //   private void fillLeftHeaderToolBar(IToolBarManager toolBarManager) {
    //      if (needsSubmit()) {
    //         ControlContribution submitButtonContribution = new ControlContribution(
    //               "org.eclipse.mylyn.tasks.toolbars.submit") { //$NON-NLS-1$
    //            @Override
    //            protected int computeWidth(Control control) {
    //               return super.computeWidth(control) + 5;
    //            }
    //
    //            @Override
    //            protected Control createControl(Composite parent) {
    //               Composite composite = new Composite(parent, SWT.NONE);
    //               composite.setBackground(null);
    //               GridLayout layout = new GridLayout();
    //               layout.marginWidth = 0;
    //               layout.marginHeight = 0;
    //               layout.marginLeft = 10;
    //               composite.setLayout(layout);
    //
    //               submitButton = toolkit.createButton(composite, Messages.TaskEditorActionPart_Submit + " ", SWT.NONE); //$NON-NLS-1$
    //               submitButton.setImage(CommonImages.getImage(TasksUiImages.REPOSITORY_SUBMIT));
    //               submitButton.setBackground(null);
    //               submitButton.addListener(SWT.Selection, new Listener() {
    //                  public void handleEvent(Event e) {
    //                     doSubmit();
    //                  }
    //               });
    //               GridDataFactory.fillDefaults().align(SWT.BEGINNING, SWT.BOTTOM).applyTo(submitButton);
    //               return composite;
    //            }
    //         };
    //         toolBarManager.add(submitButtonContribution);
    //      }
    //   }

    @Override
    public boolean selectReveal(Object object) {
        if (object instanceof TaskEditorOutlineNode) {
            TaskEditorOutlineNode node = (TaskEditorOutlineNode) object;
            TaskAttribute attribute = node.getData();
            if (attribute != null) {
                super.selectReveal(attribute.getId());
            } else {
                TaskRelation taskRelation = node.getTaskRelation();
                TaskRepository taskRepository = node.getTaskRepository();
                if (taskRelation != null && taskRepository != null) {
                    String taskID = taskRelation.getTaskId();
                    TasksUiUtil.openTask(taskRepository, taskID);
                } else {
                    EditorUtil.reveal(this.getManagedForm().getForm(), node.getLabel());
                }
                return true;
            }
        }
        return super.selectReveal(object);
    }

    void expandLastComment() {
        if (getManagedForm() == null || getManagedForm().getForm().isDisposed()) {
            // editor possibly closed or page has not been initialized
            return;
        }

        if (taskData == null) {
            return;
        }

        List<TaskAttribute> commentAttributes = taskData.getAttributeMapper().getAttributesByType(taskData,
                TaskAttribute.TYPE_COMMENT);
        if (commentAttributes.size() > 0) {
            selectReveal(commentAttributes.get(commentAttributes.size() - 1).getId());
        }
    }

}