/*******************************************************************************
 * 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
 *     Eric Booth - initial prototype
 *******************************************************************************/

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

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.action.ContributionManager;
import org.eclipse.jface.action.ControlContribution;
import org.eclipse.jface.action.GroupMarker;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.action.ToolBarManager;
import org.eclipse.jface.dialogs.IMessageProvider;
import org.eclipse.jface.resource.CompositeImageDescriptor;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.TextViewer;
import org.eclipse.jface.util.LocalSelectionTransfer;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.StructuredSelection;
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.PlatformUiUtil;
import org.eclipse.mylyn.commons.ui.SelectionProviderAdapter;
import org.eclipse.mylyn.commons.workbench.BusyAnimator;
import org.eclipse.mylyn.commons.workbench.BusyAnimator.IBusyClient;
import org.eclipse.mylyn.commons.workbench.WorkbenchUtil;
import org.eclipse.mylyn.commons.workbench.editors.CommonTextSupport;
import org.eclipse.mylyn.internal.tasks.core.LocalRepositoryConnector;
import org.eclipse.mylyn.internal.tasks.core.TaskList;
import org.eclipse.mylyn.internal.tasks.ui.TaskEditorBloatMonitor;
import org.eclipse.mylyn.internal.tasks.ui.TasksUiPlugin;
import org.eclipse.mylyn.internal.tasks.ui.actions.OpenWithBrowserAction;
import org.eclipse.mylyn.internal.tasks.ui.actions.TaskEditorScheduleAction;
import org.eclipse.mylyn.internal.tasks.ui.actions.ToggleTaskActivationAction;
import org.eclipse.mylyn.internal.tasks.ui.editors.EditorUtil;
import org.eclipse.mylyn.internal.tasks.ui.editors.Messages;
import org.eclipse.mylyn.internal.tasks.ui.editors.TaskEditorActionContributor;
import org.eclipse.mylyn.internal.tasks.ui.util.TaskDragSourceListener;
import org.eclipse.mylyn.internal.tasks.ui.util.TasksUiInternal;
import org.eclipse.mylyn.tasks.core.ITask;
import org.eclipse.mylyn.tasks.core.TaskRepository;
import org.eclipse.mylyn.tasks.ui.AbstractRepositoryConnectorUi;
import org.eclipse.mylyn.tasks.ui.TasksUiImages;
import org.eclipse.mylyn.tasks.ui.TasksUiUtil;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.DragSource;
import org.eclipse.swt.dnd.FileTransfer;
import org.eclipse.swt.dnd.TextTransfer;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.events.ControlAdapter;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.FocusAdapter;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.RowLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IEditorSite;
import org.eclipse.ui.IWorkbenchActionConstants;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.contexts.IContextService;
import org.eclipse.ui.forms.IFormColors;
import org.eclipse.ui.forms.IManagedForm;
import org.eclipse.ui.forms.editor.IFormPage;
import org.eclipse.ui.forms.editor.SharedHeaderFormEditor;
import org.eclipse.ui.forms.events.HyperlinkAdapter;
import org.eclipse.ui.forms.events.HyperlinkEvent;
import org.eclipse.ui.forms.events.IHyperlinkListener;
import org.eclipse.ui.forms.widgets.Form;
import org.eclipse.ui.forms.widgets.FormToolkit;
import org.eclipse.ui.forms.widgets.ImageHyperlink;
import org.eclipse.ui.handlers.IHandlerService;
import org.eclipse.ui.internal.forms.widgets.BusyIndicator;
import org.eclipse.ui.internal.forms.widgets.FormHeading;
import org.eclipse.ui.internal.forms.widgets.TitleRegion;
import org.eclipse.ui.menus.IMenuService;
import org.eclipse.ui.part.WorkbenchPart;
import org.eclipse.ui.progress.IWorkbenchSiteProgressService;
import org.eclipse.ui.services.IDisposable;
import org.eclipse.ui.views.contentoutline.IContentOutlinePage;

/**
 * @author Mik Kersten
 * @author Rob Elves
 * @author Steffen Pingel
 * @author Thomas Ehrnhoefer
 * @since 2.0
 */
public class TaskEditor extends SharedHeaderFormEditor {

	/**
	 * @since 2.0
	 */
	public static final String ID_EDITOR = "org.eclipse.mylyn.tasks.ui.editors.task"; //$NON-NLS-1$

	/**
	 * @since 3.2
	 */
	public static final String ID_TOOLBAR_HEADER = "org.eclipse.mylyn.tasks.ui.editors.task.toolbar.header"; //$NON-NLS-1$

	private static final String ID_LEFT_TOOLBAR_HEADER = "org.eclipse.mylyn.tasks.ui.editors.task.toolbar.header.left"; //$NON-NLS-1$

	private static final int LEFT_TOOLBAR_HEADER_TOOLBAR_PADDING = 3;

	private ToggleTaskActivationAction activateAction;

	@Deprecated
	private final IEditorPart contentOutlineProvider = null;

	private BusyAnimator editorBusyIndicator;

	private MenuManager menuManager;

	private IHyperlinkListener messageHyperLinkListener;

	private ITask task;

	private TaskEditorInput taskEditorInput;

	private TaskDragSourceListener titleDragSourceListener;

	private Composite editorParent;

	private IMenuService menuService;

	private IToolBarManager toolBarManager;

	private ToolBarManager leftToolBarManager;

	private ToolBar leftToolBar;

	private Image headerImage;

//	private int initialLeftToolbarSize;

	private boolean noExtraPadding;

//	private boolean headerLabelInitialized;

	private BusyIndicator busyLabel;

	private StyledText titleLabel;

	private CommonTextSupport textSupport;

	private TaskEditorScheduleAction scheduleAction;

	OpenWithBrowserAction openWithBrowserAction;

	private static boolean toolBarFailureLogged;

	public TaskEditor() {
	}

	@Override
	protected void createPages() {
		super.createPages();
		TaskEditorBloatMonitor.editorOpened(this);
	}

	@Override
	protected Composite createPageContainer(Composite parent) {
		this.editorParent = parent;
		Composite composite = super.createPageContainer(parent);

		EditorUtil.initializeScrollbars(getHeaderForm().getForm());

		// create left tool bar that replaces form heading label
		try {
			FormHeading heading = (FormHeading) getHeaderForm().getForm().getForm().getHead();

			Field field = FormHeading.class.getDeclaredField("titleRegion"); //$NON-NLS-1$
			field.setAccessible(true);

			TitleRegion titleRegion = (TitleRegion) field.get(heading);

			leftToolBarManager = new ToolBarManager(SWT.FLAT);
			leftToolBar = leftToolBarManager.createControl(titleRegion);
			leftToolBar.addControlListener(new ControlAdapter() {
				private boolean ignoreResizeEvents;

				@Override
				public void controlResized(ControlEvent e) {
					if (ignoreResizeEvents) {
						return;
					}
					ignoreResizeEvents = true;
					try {
						// the tool bar contents has changed, update state
						updateHeaderImage();
						updateHeaderLabel();
					} finally {
						ignoreResizeEvents = false;
					}
				}
			});

			//titleLabel = new Label(titleRegion, SWT.NONE);
			// need a viewer for copy support
			TextViewer titleViewer = new TextViewer(titleRegion, SWT.READ_ONLY);
			// Eclipse 3.3 needs a document, otherwise an NPE is thrown
			titleViewer.setDocument(new Document());

			titleLabel = titleViewer.getTextWidget();
			titleLabel.setForeground(heading.getForeground());
			titleLabel.setFont(heading.getFont());
			// XXX work-around problem that causes field to maintain selection when unfocused
			titleLabel.addFocusListener(new FocusAdapter() {
				@Override
				public void focusLost(FocusEvent e) {
					titleLabel.setSelection(0);
				}
			});

			titleRegion.addControlListener(new ControlAdapter() {
				@Override
				public void controlResized(ControlEvent e) {
					// do not create busyLabel to avoid recursion
					updateSizeAndLocations();
				}
			});

			IHandlerService handlerService = (IHandlerService) getSite().getService(IHandlerService.class);
			if (handlerService != null) {
				textSupport = new CommonTextSupport(handlerService);
				textSupport.install(titleViewer, false);
			}
		} catch (Exception e) {
			if (!toolBarFailureLogged) {
				StatusHandler.log(new Status(IStatus.ERROR, TasksUiPlugin.ID_PLUGIN,
						"Failed to obtain busy label toolbar", e)); //$NON-NLS-1$
			}
			if (titleLabel != null) {
				titleLabel.dispose();
				titleLabel = null;
			}
			if (leftToolBar != null) {
				leftToolBar.dispose();
				leftToolBar = null;
			}
			if (leftToolBarManager != null) {
				leftToolBarManager.dispose();
				leftToolBarManager = null;
			}
		}

		updateHeader();

		return composite;
	}

	private BusyIndicator getBusyLabel() {
		if (busyLabel != null) {
			return busyLabel;
		}
		try {
			FormHeading heading = (FormHeading) getHeaderForm().getForm().getForm().getHead();
			// ensure that busy label exists
			heading.setBusy(true);
			heading.setBusy(false);

			Field field = FormHeading.class.getDeclaredField("titleRegion"); //$NON-NLS-1$
			field.setAccessible(true);

			TitleRegion titleRegion = (TitleRegion) field.get(heading);

			for (Control child : titleRegion.getChildren()) {
				if (child instanceof BusyIndicator) {
					busyLabel = (BusyIndicator) child;
				}
			}
			if (busyLabel == null) {
				return null;
			}
			busyLabel.addControlListener(new ControlAdapter() {
				@Override
				public void controlMoved(ControlEvent e) {
					updateSizeAndLocations();
				}
			});
			// the busy label may get disposed if it has no image
			busyLabel.addDisposeListener(new DisposeListener() {
				public void widgetDisposed(DisposeEvent e) {
					busyLabel.setMenu(null);
					busyLabel = null;
				}
			});

			if (leftToolBar != null) {
				leftToolBar.moveAbove(busyLabel);
			}
			if (titleLabel != null) {
				titleLabel.moveAbove(busyLabel);
			}

			updateSizeAndLocations();

			return busyLabel;
		} catch (Exception e) {
			if (!toolBarFailureLogged) {
				StatusHandler.log(new Status(IStatus.ERROR, TasksUiPlugin.ID_PLUGIN,
						"Failed to obtain busy label toolbar", e)); //$NON-NLS-1$
			}
			busyLabel = null;
		}
		return busyLabel;
	}

	private void updateSizeAndLocations() {
		if (busyLabel == null || busyLabel.isDisposed()) {
			return;
		}

		Point leftToolBarSize = new Point(0, 0);
		if (leftToolBar != null && !leftToolBar.isDisposed()) {
			// bottom align tool bar in title region
			leftToolBarSize = leftToolBar.getSize();
			int y = leftToolBar.getParent().getSize().y - leftToolBarSize.y - 2;
			if (!hasLeftToolBar()) {
				// hide tool bar to avoid overlaying busyLabel on windows
				leftToolBarSize.x = 0;
			}
			leftToolBar.setBounds(busyLabel.getLocation().x, y, leftToolBarSize.x, leftToolBarSize.y);
		}
		if (titleLabel != null && !titleLabel.isDisposed()) {
			// center align title text in title region
			Point size = titleLabel.computeSize(SWT.DEFAULT, SWT.DEFAULT, true);
			int y = (titleLabel.getParent().getSize().y - size.y) / 2;
			titleLabel.setBounds(busyLabel.getLocation().x + LEFT_TOOLBAR_HEADER_TOOLBAR_PADDING + leftToolBarSize.x,
					y, size.x, size.y);
		}
	}

	Composite getEditorParent() {
		return editorParent;
	}

	@Override
	protected void addPages() {
		initialize();

		// determine factories
		Set<String> conflictingIds = new HashSet<String>();
		ArrayList<AbstractTaskEditorPageFactory> pageFactories = new ArrayList<AbstractTaskEditorPageFactory>();
		for (AbstractTaskEditorPageFactory pageFactory : TasksUiPlugin.getDefault().getTaskEditorPageFactories()) {
			if (pageFactory.canCreatePageFor(getTaskEditorInput()) && WorkbenchUtil.allowUseOf(pageFactory)) {
				pageFactories.add(pageFactory);
				String[] ids = pageFactory.getConflictingIds(getTaskEditorInput());
				if (ids != null) {
					conflictingIds.addAll(Arrays.asList(ids));
				}
			}
		}
		for (Iterator<AbstractTaskEditorPageFactory> it = pageFactories.iterator(); it.hasNext();) {
			if (conflictingIds.contains(it.next().getId())) {
				it.remove();
			}
		}

		// sort by priority
		Collections.sort(pageFactories, new Comparator<AbstractTaskEditorPageFactory>() {
			public int compare(AbstractTaskEditorPageFactory o1, AbstractTaskEditorPageFactory o2) {
				return o1.getPriority() - o2.getPriority();
			}
		});

		// create pages
		for (AbstractTaskEditorPageFactory factory : pageFactories) {
			try {
				IFormPage page = factory.createPage(this);
				int index = addPage(page);
				setPageImage(index, factory.getPageImage(this, page));
				setPageText(index, factory.getPageText(this, page));
				if (factory.getPriority() == AbstractTaskEditorPageFactory.PRIORITY_TASK) {
					setActivePage(index);
				}
				if (page instanceof ISelectionProvider) {
					((ISelectionProvider) page).addSelectionChangedListener(getActionBarContributor());
				}
			} catch (Exception e) {
				StatusHandler.log(new Status(IStatus.ERROR, TasksUiPlugin.ID_PLUGIN,
						"Could not create editor via factory: " + factory, e)); //$NON-NLS-1$
			}
		}

		updateTitleImage();
		updateHeaderToolBar();
		installTitleDrag(getHeaderForm().getForm().getForm());

		// do this late to allow pages to replace the selection provider
		getEditorSite().registerContextMenu(menuManager, getEditorSite().getSelectionProvider(), true);
	}

	private void initialize() {
		editorBusyIndicator = new BusyAnimator(new IBusyClient() {
			public Image getImage() {
				return TaskEditor.this.getTitleImage();
			}

			public void setImage(Image image) {
				TaskEditor.this.setTitleImage(image);
			}
		});

		menuManager = new MenuManager();
		configureContextMenuManager(menuManager);
		Menu menu = menuManager.createContextMenu(getContainer());
		getContainer().setMenu(menu);

		// install context menu on form heading and title
		getHeaderForm().getForm().setMenu(menu);
		Composite head = getHeaderForm().getForm().getForm().getHead();
		if (head != null) {
			CommonUiUtil.setMenu(head, menu);
		}
	}

	/**
	 * @since 3.0
	 */
	@Deprecated
	public void configureContextMenuManager(MenuManager manager) {
		if (manager == null) {
			return;
		}
		IMenuListener listener = new IMenuListener() {
			public void menuAboutToShow(IMenuManager manager) {
				contextMenuAboutToShow(manager);
			}
		};
		manager.setRemoveAllWhenShown(true);
		manager.addMenuListener(listener);
	}

	@Deprecated
	protected void contextMenuAboutToShow(IMenuManager manager) {
		TaskEditorActionContributor contributor = getActionBarContributor();
		if (contributor != null) {
			contributor.contextMenuAboutToShow(manager);
		}
	}

	@Override
	protected FormToolkit createToolkit(Display display) {
		// create a toolkit that shares colors between editors.
		return new FormToolkit(TasksUiPlugin.getDefault().getFormColors(display));
	}

	@Override
	protected void createHeaderContents(IManagedForm headerForm) {
		getToolkit().decorateFormHeading(headerForm.getForm().getForm());
	}

	@Override
	public void dispose() {
		disposeScheduleAction();
		if (headerImage != null) {
			headerImage.dispose();
		}
		if (editorBusyIndicator != null) {
			editorBusyIndicator.stop();
		}
		if (activateAction != null) {
			activateAction.dispose();
		}
		if (menuService != null) {
			if (leftToolBarManager != null) {
				menuService.releaseContributions(leftToolBarManager);
			}
			if (toolBarManager instanceof ContributionManager) {
				menuService.releaseContributions((ContributionManager) toolBarManager);
			}
		}
		if (textSupport != null) {
			textSupport.dispose();
		}
		if (messageHyperLinkListener instanceof IDisposable) {
			((IDisposable) messageHyperLinkListener).dispose();
		}
		super.dispose();
	}

	@Override
	public void doSave(IProgressMonitor monitor) {
		for (IFormPage page : getPages()) {
			if (page.isDirty()) {
				page.doSave(monitor);
			}
		}

		editorDirtyStateChanged();
	}

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

	private TaskEditorActionContributor getActionBarContributor() {
		return (TaskEditorActionContributor) getEditorSite().getActionBarContributor();
	}

	@SuppressWarnings("rawtypes")
	@Override
	public Object getAdapter(Class adapter) {
		if (contentOutlineProvider != null) {
			return contentOutlineProvider.getAdapter(adapter);
		} else if (IContentOutlinePage.class.equals(adapter)) {
			IFormPage[] pages = getPages();
			for (IFormPage page : pages) {
				Object outlinePage = page.getAdapter(adapter);
				if (outlinePage != null) {
					return outlinePage;
				}
			}
		}
		return super.getAdapter(adapter);
	}

	/**
	 * @since 3.0
	 */
	public Menu getMenu() {
		return getContainer().getMenu();
	}

	IFormPage[] getPages() {
		List<IFormPage> formPages = new ArrayList<IFormPage>();
		if (pages != null) {
			for (int i = 0; i < pages.size(); i++) {
				Object page = pages.get(i);
				if (page instanceof IFormPage) {
					formPages.add((IFormPage) page);
				}
			}
		}
		return formPages.toArray(new IFormPage[formPages.size()]);
	}

	@Deprecated
	protected IWorkbenchSiteProgressService getProgressService() {
		Object siteService = getEditorSite().getAdapter(IWorkbenchSiteProgressService.class);
		if (siteService != null) {
			return (IWorkbenchSiteProgressService) siteService;
		}
		return null;
	}

	@Deprecated
	public ISelection getSelection() {
		if (getSite() != null && getSite().getSelectionProvider() != null) {
			return getSite().getSelectionProvider().getSelection();
		} else {
			return StructuredSelection.EMPTY;
		}
	}

	public TaskEditorInput getTaskEditorInput() {
		return taskEditorInput;
	}

	@Deprecated
	public Form getTopForm() {
		return this.getHeaderForm().getForm().getForm();
	}

	@Override
	public void init(IEditorSite site, IEditorInput input) throws PartInitException {
		if (!(input instanceof TaskEditorInput)) {
			throw new PartInitException("Invalid editor input \"" + input.getClass() + "\""); //$NON-NLS-1$ //$NON-NLS-2$
		}

		super.init(site, input);

		this.taskEditorInput = (TaskEditorInput) input;
		this.task = taskEditorInput.getTask();

		// initialize selection 
		site.getSelectionProvider().setSelection(new StructuredSelection(task));

		setPartName(input.getName());

		// activate context
		IContextService contextSupport = (IContextService) site.getService(IContextService.class);
		if (contextSupport != null) {
			contextSupport.activateContext(ID_EDITOR);
		}
	}

	private void installTitleDrag(Form form) {
		if (titleDragSourceListener == null /*&& !hasLeftToolBar()*/) {
			Transfer[] transferTypes;
			if (null == task) {
				transferTypes = new Transfer[] { TextTransfer.getInstance() };
			} else {
				transferTypes = new Transfer[] { LocalSelectionTransfer.getTransfer(), TextTransfer.getInstance(),
						FileTransfer.getInstance() };
			}
			titleDragSourceListener = new TaskDragSourceListener(new SelectionProviderAdapter() {
				@Override
				public ISelection getSelection() {
					return new StructuredSelection(task);
				}
			});

			if (titleLabel != null) {
				DragSource source = new DragSource(titleLabel, DND.DROP_MOVE | DND.DROP_LINK);
				source.setTransfer(transferTypes);
				source.addDragListener(titleDragSourceListener);
			} else {
				form.addTitleDragSupport(DND.DROP_MOVE | DND.DROP_LINK, transferTypes, titleDragSourceListener);
			}
		}
	}

	@Override
	public boolean isDirty() {
		for (IFormPage page : getPages()) {
			if (page.isDirty()) {
				return true;
			}
		}
		return false;
	}

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

	@Deprecated
	public void markDirty() {
		firePropertyChange(PROP_DIRTY);
	}

	/**
	 * Refresh editor pages with new contents.
	 * 
	 * @since 3.0
	 */
	public void refreshPages() {
		for (IFormPage page : getPages()) {
			if (page instanceof TaskFormPage) {
				if (page.getManagedForm() != null && !page.getManagedForm().getForm().isDisposed()) {
					((TaskFormPage) page).refresh();
				}
			}
		}
	}

	@Override
	public void setFocus() {
		IFormPage page = getActivePageInstance();
		if (page != null) {
			page.setFocus();
		} else {
			super.setFocus();
		}
	}

	@Deprecated
	public void setFocusOfActivePage() {
		if (this.getActivePage() > -1) {
			IFormPage page = this.getPages()[this.getActivePage()];
			if (page != null) {
				page.setFocus();
			}
		}
	}

	public void setMessage(String message, int type) {
		setMessage(message, type, null);
	}

	private boolean isHeaderFormDisposed() {
		return getHeaderForm() == null || getHeaderForm().getForm() == null || getHeaderForm().getForm().isDisposed();
	}

	/**
	 * @since 2.3
	 */
	public void setMessage(String message, int type, IHyperlinkListener listener) {
		if (isHeaderFormDisposed()) {
			return;
		}

		try {
			// avoid flicker of the left header toolbar
			getHeaderForm().getForm().setRedraw(false);

			Form form = getHeaderForm().getForm().getForm();
			if (message != null) {
				message = message.replace('\n', ' ');
			}
			form.setMessage(message, type, null);
			if (messageHyperLinkListener != null) {
				form.removeMessageHyperlinkListener(messageHyperLinkListener);
				if (messageHyperLinkListener instanceof IDisposable) {
					((IDisposable) messageHyperLinkListener).dispose();
				}
			}
			if (listener != null) {
				form.addMessageHyperlinkListener(listener);
			}
			messageHyperLinkListener = listener;

			// make sure the busyLabel image is large enough to accommodate the tool bar
			if (hasLeftToolBar()) {
				BusyIndicator busyLabel = getBusyLabel();
				if (message != null && busyLabel != null) {
					setHeaderImage(busyLabel.getImage());
				} else {
					setHeaderImage(null);
				}
			}
		} finally {
			getHeaderForm().getForm().setRedraw(true);
		}
	}

	private void setHeaderImage(final Image image) {
		BusyIndicator busyLabel = getBusyLabel();
		if (busyLabel == null) {
			return;
		}

		final Point size = leftToolBar.computeSize(SWT.DEFAULT, SWT.DEFAULT, true);
		Point titleSize = titleLabel.computeSize(SWT.DEFAULT, SWT.DEFAULT, true);
		size.x += titleSize.x + LEFT_TOOLBAR_HEADER_TOOLBAR_PADDING;
		size.y = Math.max(titleSize.y, size.y);

		// padding between toolbar and image, ensure image is at least one pixel wide to avoid SWT error 
		final int padding = (size.x > 0 && !noExtraPadding) ? 10 : 1;
		final Rectangle imageBounds = (image != null) ? image.getBounds() : new Rectangle(0, 0, 0, 0);
		int tempHeight = (image != null) ? Math.max(size.y + 1, imageBounds.height) : size.y + 1;
		// avoid extra padding due to margin added by TitleRegion.VMARGIN
		final int height = (tempHeight > imageBounds.height + 5) ? tempHeight - 5 : tempHeight;

		CompositeImageDescriptor descriptor = new CompositeImageDescriptor() {
			@Override
			protected void drawCompositeImage(int width, int height) {
				if (image != null) {
					drawImage(image.getImageData(), size.x + padding, (height - image.getBounds().height) / 2);
				}
			}

			@Override
			protected Point getSize() {
				return new Point(size.x + padding + imageBounds.width, height);
			}

		};
		Image newHeaderImage = descriptor.createImage();

		// directly set on busyLabel since getHeaderForm().getForm().setImage() does not update 
		// the image if a message is currently displayed
		busyLabel.setImage(newHeaderImage);

		if (headerImage != null) {
			headerImage.dispose();
		}
		headerImage = newHeaderImage;

		// avoid extra padding due to large title font
		// TODO reset font in case tool bar is empty
		//leftToolBar.getParent().setFont(JFaceResources.getDefaultFont());
		getHeaderForm().getForm().reflow(true);
	}

	/**
	 * @since 3.1
	 */
	public String getMessage() {
		if (getHeaderForm() != null && getHeaderForm().getForm() != null) {
			if (!getHeaderForm().getForm().isDisposed()) {
				Form form = getHeaderForm().getForm().getForm();
				return form.getMessage();
			}
		}
		return null;
	}

	/**
	 * @since 3.0
	 */
	public void setStatus(String message, final String title, final IStatus status) {
		setMessage(message, IMessageProvider.ERROR, new HyperlinkAdapter() {
			@Override
			public void linkActivated(HyperlinkEvent event) {
				TasksUiInternal.displayStatus(title, status);
			}
		});
	}

	@Override
	public void showBusy(boolean busy) {
		if (editorBusyIndicator != null) {
			if (busy) {
				if (TasksUiInternal.isAnimationsEnabled()) {
					editorBusyIndicator.start();
				}
			} else {
				editorBusyIndicator.stop();
			}
		}

		if (!isHeaderFormDisposed()) {
			Form form = getHeaderForm().getForm().getForm();
			if (form != null && !form.isDisposed()) {
				// TODO consider only disabling certain actions 
				IToolBarManager toolBarManager = form.getToolBarManager();
				if (toolBarManager instanceof ToolBarManager) {
					ToolBar control = ((ToolBarManager) toolBarManager).getControl();
					if (control != null) {
						control.setEnabled(!busy);
					}
				}

				if (leftToolBar != null) {
					leftToolBar.setEnabled(!busy);
				}
				if (titleLabel != null) {
					titleLabel.setEnabled(!busy);
				}

				CommonUiUtil.setEnabled(form.getBody(), !busy);
				for (IFormPage page : getPages()) {
					if (page instanceof WorkbenchPart) {
						WorkbenchPart part = (WorkbenchPart) page;
						part.showBusy(busy);
					}
				}
			}
		}
	}

	private void updateHeader() {
		IEditorInput input = getEditorInput();
		updateHeaderImage();
		updateHeaderLabel();
		setTitleToolTip(input.getToolTipText());
		setPartName(input.getName());
	}

	/**
	 * @since 3.0
	 */
	public void updateHeaderToolBar() {
		if (isHeaderFormDisposed()) {
			return;
		}

		final Form form = getHeaderForm().getForm().getForm();
		toolBarManager = form.getToolBarManager();

		toolBarManager.removeAll();
//		toolBarManager.update(true);

		TaskRepository outgoingNewRepository = TasksUiUtil.getOutgoingNewTaskRepository(task);
		final TaskRepository taskRepository = (outgoingNewRepository != null)
				? outgoingNewRepository
				: taskEditorInput.getTaskRepository();
		ControlContribution repositoryLabelControl = new ControlContribution(Messages.AbstractTaskEditorPage_Title) {
			@Override
			protected Control createControl(Composite parent) {
				FormToolkit toolkit = getHeaderForm().getToolkit();
				Composite composite = toolkit.createComposite(parent);
				RowLayout layout = new RowLayout();
				if (PlatformUiUtil.hasNarrowToolBar()) {
					layout.marginTop = 0;
					layout.marginBottom = 0;
					layout.center = true;
				}
				composite.setLayout(layout);
				composite.setBackground(null);
				String label = taskRepository.getRepositoryLabel();
				if (label.indexOf("//") != -1) { //$NON-NLS-1$
					label = label.substring((taskRepository.getRepositoryUrl().indexOf("//") + 2)); //$NON-NLS-1$
				}

				ImageHyperlink link = new ImageHyperlink(composite, SWT.NONE);
				link.setText(label);
				link.setFont(JFaceResources.getBannerFont());
				link.setForeground(toolkit.getColors().getColor(IFormColors.TITLE));
				link.setToolTipText(Messages.TaskEditor_Edit_Task_Repository_ToolTip);
				link.addHyperlinkListener(new HyperlinkAdapter() {
					@Override
					public void linkActivated(HyperlinkEvent e) {
						TasksUiUtil.openEditRepositoryWizard(taskRepository);
					}
				});

				return composite;
			}
		};
		toolBarManager.add(repositoryLabelControl);

		toolBarManager.add(new GroupMarker("repository")); //$NON-NLS-1$
		toolBarManager.add(new GroupMarker("new")); //$NON-NLS-1$
		toolBarManager.add(new GroupMarker("open")); //$NON-NLS-1$
		toolBarManager.add(new GroupMarker(IWorkbenchActionConstants.MB_ADDITIONS));

		openWithBrowserAction = new OpenWithBrowserAction();
		openWithBrowserAction.selectionChanged(new StructuredSelection(task));
		if (openWithBrowserAction.isEnabled()) {
//			ImageDescriptor overlay = TasksUiPlugin.getDefault().getOverlayIcon(taskRepository.getConnectorKind());
//			ImageDescriptor compositeDescriptor = new TaskListImageDescriptor(TasksUiImages.REPOSITORY_SMALL_TOP,
//					overlay, false, true);
			openWithBrowserAction.setImageDescriptor(CommonImages.WEB);
			//openWithBrowserAction.setImageDescriptor(CommonImages.BROWSER_OPEN_TASK);
			openWithBrowserAction.setToolTipText(Messages.AbstractTaskEditorPage_Open_with_Web_Browser);
			toolBarManager.appendToGroup("open", openWithBrowserAction); //$NON-NLS-1$
		} else {
			openWithBrowserAction = null;
		}

		if (activateAction == null) {
			activateAction = new ToggleTaskActivationAction(task) {
				@Override
				public void run() {
					TaskList taskList = TasksUiPlugin.getTaskList();
					if (taskList.getTask(task.getRepositoryUrl(), task.getTaskId()) == null) {
						setMessage(Messages.TaskEditor_Task_added_to_the_Uncategorized_container,
								IMessageProvider.INFORMATION);
					}
					super.run();
				}
			};
		}

		toolBarManager.add(new Separator("planning")); //$NON-NLS-1$
		disposeScheduleAction();
		scheduleAction = new TaskEditorScheduleAction(task);
		toolBarManager.add(scheduleAction);

		toolBarManager.add(new GroupMarker("page")); //$NON-NLS-1$
		for (IFormPage page : getPages()) {
			if (page instanceof TaskFormPage) {
				TaskFormPage taskEditorPage = (TaskFormPage) page;
				taskEditorPage.fillToolBar(toolBarManager);
			}
		}

		toolBarManager.add(new Separator("activation")); //$NON-NLS-1$

//		ContributionItem spacer = new ContributionItem() {
//			@Override
//			public void fill(ToolBar toolbar, int index) {
//				ToolItem item = new ToolItem(toolbar, SWT.NONE);
//				int scaleHeight = 42;
//				if (PlatformUtil.needsCarbonToolBarFix()) {
//					scaleHeight = 32;
//				}
//				final Image image = new Image(toolbar.getDisplay(), CommonImages.getImage(CommonImages.BLANK)
//						.getImageData()
//						.scaledTo(1, scaleHeight));
//				item.setImage(image);
//				item.addDisposeListener(new DisposeListener() {
//					public void widgetDisposed(DisposeEvent e) {
//						image.dispose();
//					}
//				});
//				item.setWidth(5);
//				item.setEnabled(false);
//			}
//		};
//		toolBarManager.add(spacer);

//		for (IFormPage page : getPages()) {
//			if (page instanceof AbstractTaskEditorPage) {
//				AbstractTaskEditorPage taskEditorPage = (AbstractTaskEditorPage) page;
//				taskEditorPage.fillLeftHeaderToolBar(toolBarManager);
//			} else if (page instanceof TaskPlanningEditor) {
//				TaskPlanningEditor taskEditorPage = (TaskPlanningEditor) page;
//				taskEditorPage.fillLeftHeaderToolBar(toolBarManager);
//			}
//		}

		// add external contributions
		menuService = (IMenuService) getSite().getService(IMenuService.class);
		if (menuService != null && toolBarManager instanceof ContributionManager) {
			menuService.populateContributionManager((ContributionManager) toolBarManager, "toolbar:" //$NON-NLS-1$
					+ ID_TOOLBAR_HEADER + "." + taskRepository.getConnectorKind()); //$NON-NLS-1$
			menuService.populateContributionManager((ContributionManager) toolBarManager, "toolbar:" //$NON-NLS-1$
					+ ID_TOOLBAR_HEADER);
		}

		toolBarManager.update(true);

		// XXX move this call
		updateLeftHeaderToolBar();
		updateHeader();
	}

	private void disposeScheduleAction() {
		if (scheduleAction != null) {
			scheduleAction.dispose();
			scheduleAction = null;
		}
	}

	private void updateLeftHeaderToolBar() {
		leftToolBarManager.removeAll();

		leftToolBarManager.add(new Separator("activation")); //$NON-NLS-1$
		leftToolBarManager.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS));

//		initialLeftToolbarSize = leftToolBarManager.getSize();

		leftToolBarManager.add(activateAction);

//		for (IFormPage page : getPages()) {
//			if (page instanceof AbstractTaskEditorPage) {
//				AbstractTaskEditorPage taskEditorPage = (AbstractTaskEditorPage) page;
//				taskEditorPage.fillLeftHeaderToolBar(leftToolBarManager);
//			} else if (page instanceof TaskPlanningEditor) {
//				TaskPlanningEditor taskEditorPage = (TaskPlanningEditor) page;
//				taskEditorPage.fillLeftHeaderToolBar(leftToolBarManager);
//			}
//		}

		// add external contributions
		menuService = (IMenuService) getSite().getService(IMenuService.class);
		if (menuService != null && leftToolBarManager instanceof ContributionManager) {
			TaskRepository outgoingNewRepository = TasksUiUtil.getOutgoingNewTaskRepository(task);
			TaskRepository taskRepository = (outgoingNewRepository != null)
					? outgoingNewRepository
					: taskEditorInput.getTaskRepository();
			menuService.populateContributionManager(leftToolBarManager, "toolbar:" + ID_LEFT_TOOLBAR_HEADER + "." //$NON-NLS-1$ //$NON-NLS-2$
					+ taskRepository.getConnectorKind());
		}

		leftToolBarManager.update(true);

		if (hasLeftToolBar()) {
			// XXX work around a bug in Gtk that causes the toolbar size to be incorrect if no
			// tool bar buttons are contributed
//			if (leftToolBar != null) {
//				Point size = leftToolBar.computeSize(SWT.DEFAULT, SWT.DEFAULT, false);
//				boolean changed = false;
//				for (Control control : leftToolBar.getChildren()) {
//					final Point childSize = control.computeSize(SWT.DEFAULT, SWT.DEFAULT, false);
//					if (childSize.y > size.y) {
//						size.y = childSize.y;
//						changed = true;
//					}
//				}
//				if (changed) {
//					leftToolBar.setSize(size);
//				}
//			}
//
//			if (PlatformUtil.isToolBarHeightBroken(leftToolBar)) {
//				ToolItem item = new ToolItem(leftToolBar, SWT.NONE);
//				item.setEnabled(false);
//				item.setImage(CommonImages.getImage(CommonImages.BLANK));
//				item.setWidth(1);
//				noExtraPadding = true;
//			} else if (PlatformUtil.needsToolItemToForceToolBarHeight()) {
//				ToolItem item = new ToolItem(leftToolBar, SWT.NONE);
//				item.setEnabled(false);
//				int scaleHeight = 22;
//				if (PlatformUtil.needsCarbonToolBarFix()) {
//					scaleHeight = 32;
//				}
//				final Image image = new Image(item.getDisplay(), CommonImages.getImage(CommonImages.BLANK)
//						.getImageData()
//						.scaledTo(1, scaleHeight));
//				item.setImage(image);
//				item.addDisposeListener(new DisposeListener() {
//					public void widgetDisposed(DisposeEvent e) {
//						image.dispose();
//					}
//				});
//				item.setWidth(1);
//				noExtraPadding = true;
//			}

			// fix size of toolbar on Gtk with Eclipse 3.3 
			Point size = leftToolBar.getSize();
			if (size.x == 0 && size.y == 0) {
				size = leftToolBar.computeSize(SWT.DEFAULT, SWT.DEFAULT, true);
				leftToolBar.setSize(size);
			}
		}
	}

	private void updateHeaderImage() {
		if (hasLeftToolBar()) {
			setHeaderImage(null);
		} else {
			getHeaderForm().getForm().setImage(getBrandingImage());
		}
	}

	private Image getBrandingImage() {
		String connectorKind;
		TaskRepository outgoingNewRepository = TasksUiUtil.getOutgoingNewTaskRepository(task);
		if (outgoingNewRepository != null) {
			connectorKind = outgoingNewRepository.getConnectorKind();
		} else {
			connectorKind = task.getConnectorKind();
		}

		if (LocalRepositoryConnector.CONNECTOR_KIND.equals(connectorKind)) {
			return CommonImages.getImage(TasksUiImages.TASK);
		} else {
			ImageDescriptor overlay = TasksUiPlugin.getDefault().getOverlayIcon(connectorKind);
			Image image = CommonImages.getImageWithOverlay(TasksUiImages.REPOSITORY, overlay, false, false);
			return image;
		}
	}

	private boolean hasLeftToolBar() {
		return leftToolBar != null && leftToolBarManager != null;
//				&& leftToolBarManager.getSize() > initialLeftToolbarSize;
	}

	private void updateHeaderLabel() {
		TaskRepository outgoingNewRepository = TasksUiUtil.getOutgoingNewTaskRepository(task);
		final TaskRepository taskRepository = (outgoingNewRepository != null)
				? outgoingNewRepository
				: taskEditorInput.getTaskRepository();

//		if (taskRepository.getConnectorKind().equals(LocalRepositoryConnector.CONNECTOR_KIND)) {
//			getHeaderForm().getForm().setText(Messages.TaskEditor_Task_ + task.getSummary());
//		} else {
		AbstractRepositoryConnectorUi connectorUi = TasksUiPlugin.getConnectorUi(taskRepository.getConnectorKind());
		String kindLabel = Messages.TaskEditor_Task;
		if (connectorUi != null) {
			kindLabel = connectorUi.getTaskKindLabel(task);
		}

		String idLabel = task.getTaskKey();
		if (idLabel != null) {
			kindLabel += " " + idLabel; //$NON-NLS-1$
		}

		if (hasLeftToolBar() && titleLabel != null) {
			titleLabel.setText(kindLabel);
			getHeaderForm().getForm().setText(null);
			setHeaderImage(null);
		} else {
			getHeaderForm().getForm().setText(kindLabel);
		}
	}

	/**
	 * Update the title of the editor.
	 * 
	 * @deprecated use {@link #updateHeaderToolBar()} instead
	 */
	@Deprecated
	public void updateTitle(String name) {
		updateHeader();
	}

	private void updateTitleImage() {
		if (task != null) {
			AbstractRepositoryConnectorUi connectorUi = TasksUiPlugin.getConnectorUi(task.getConnectorKind());
			if (connectorUi != null) {
				ImageDescriptor overlayDescriptor = connectorUi.getTaskKindOverlay(task);
				setTitleImage(CommonImages.getCompositeTaskImage(TasksUiImages.TASK, overlayDescriptor, false));
			} else {
				setTitleImage(CommonImages.getImage(TasksUiImages.TASK));
			}
//		} else if (getEditorInput() instanceof AbstractRepositoryTaskEditorInput) {
//			setTitleImage(CommonImages.getImage(TasksUiImages.TASK_REMOTE));
		} else {
			setTitleImage(CommonImages.getImage(TasksUiImages.TASK));
		}
	}

}
