/*******************************************************************************
 * Copyright (c) 2008 IBM Corporation 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:
 *     IBM Corporation - initial API and implementation
 ******************************************************************************/

package org.eclipse.ui.statushandlers;

import java.net.URL;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;

import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.action.ContributionItem;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IContributionItem;
import org.eclipse.jface.action.ToolBarManager;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.DialogTray;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.dialogs.ErrorSupportProvider;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.MessageDialogWithToggle;
import org.eclipse.jface.dialogs.TrayDialog;
import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.resource.LocalResourceManager;
import org.eclipse.jface.resource.ResourceManager;
import org.eclipse.jface.util.Policy;
import org.eclipse.jface.viewers.IContentProvider;
import org.eclipse.jface.viewers.ILabelProviderListener;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITableLabelProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerComparator;
import org.eclipse.jface.window.Window;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.Clipboard;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Cursor;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.PaletteData;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.RGB;
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.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.swt.widgets.ToolItem;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.internal.IWorkbenchGraphicConstants;
import org.eclipse.ui.internal.WorkbenchImages;
import org.eclipse.ui.internal.WorkbenchMessages;
import org.eclipse.ui.internal.WorkbenchPlugin;
import org.eclipse.ui.internal.progress.ProgressManager;
import org.eclipse.ui.internal.progress.ProgressManagerUtil;
import org.eclipse.ui.internal.progress.ProgressMessages;
import org.eclipse.ui.internal.statushandlers.DefaultDetailsArea;
import org.eclipse.ui.internal.statushandlers.StackTraceSupportArea;
import org.eclipse.ui.progress.IProgressConstants;

import com.ibm.icu.text.DateFormat;

/**
 * <p>
 * The {@link WorkbenchStatusDialogManager} is a utility class for displaying
 * one or more messages (errors, warnings or infos) to the user. The dialog
 * supplied has a Details button that opens/closes the details area. The default
 * {@link AbstractStatusAreaProvider} displays a tree of {@link StatusAdapter}s
 * related to the selected item on the messages list. The dialog also hasa
 * Support button that opens/closes the support area which contains the provided
 * {@link AbstractStatusAreaProvider}. The Support button is disabled and not
 * visible unless
 * {@link WorkbenchStatusDialogManager#enableDefaultSupportArea(boolean)} is
 * invoked.
 * </p>
 * 
 * <p>
 * The default details area can be replaced using
 * {@link WorkbenchStatusDialogManager#setDetailsAreaProvider(AbstractStatusAreaProvider)}
 * </p>
 * 
 * <p>
 * The default support area can be replaced using
 * {@link WorkbenchStatusDialogManager#setSupportAreaProvider(AbstractStatusAreaProvider)}
 * or {@link Policy#setErrorSupportProvider(ErrorSupportProvider)}.
 * </p>
 * 
 * <p>
 * The manager can switch from a non-modal dialog to a modal dialog. See
 * {@link #addStatusAdapter(StatusAdapter, boolean)}
 * </p>
 * 
 * <p>
 * IMPORTANT: This class is <em>not</em> intended to be subclassed by clients.
 * </p>
 * 
 * @see Policy#setErrorSupportProvider(ErrorSupportProvider)
 * @see ErrorSupportProvider
 * @see AbstractStatusAreaProvider
 * @since 3.4
 */
public class WorkbenchStatusDialogManager {

	/**
	 * This class is responsible for managing details area.
	 * 
	 * @since 3.4
	 */
	private final class DetailsAreaManager {
		private AbstractStatusAreaProvider provider = null;
		private Control control = null;

		/**
		 * Closes the details area
		 */
		public void close() {
			if (control != null && !control.isDisposed()) {
				control.dispose();
			}
		}

		/**
		 * This method is responsible for creating details area on the specified
		 * Composite and displaying specified StatusAdapter
		 * 
		 * @param parent
		 *            A composite on which should be the details area created.
		 * @param statusAdapter
		 *            StatusAdapter for which should be the details area
		 *            created.
		 */
		public void createDetailsArea(Composite parent,
				StatusAdapter statusAdapter) {
			Composite container = new Composite(parent, SWT.NONE);
			container.setLayout(GridLayoutFactory.fillDefaults().create());
			container.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
			getProvider().createSupportArea(container, statusAdapter);
			control = container;
		}

		/**
		 * Returns current detail area provider.
		 * 
		 * @return current detail area provider.
		 */
		private AbstractStatusAreaProvider getProvider() {
			if (provider == null) {
				provider = new DefaultDetailsArea(
						WorkbenchStatusDialogManager.this);
			}
			return provider;
		}

		/**
		 * This method allows to check if the details area is open (physically
		 * constructed).
		 * 
		 * @return true if the area is open, false otherwise
		 */
		public boolean isOpen() {
			if (control == null || control.isDisposed()) {
				return false;
			}
			return true;
		}

		/**
		 * This method sets the details area provider. If null is set, the
		 * default area provider (status tree) will be used.
		 * 
		 * @param provider
		 *            A provider that will create contents for details area.
		 */
		public void setDetailsAreaProvider(AbstractStatusAreaProvider provider) {
			this.provider = provider;
		}
	}

	/**
	 * Parent window actually does not use its Shell to build dialog on. The
	 * window passes the shell to the InternalDialog, and it can do switching
	 * modality and recreate the window silently.
	 * 
	 * @since 3.4
	 */
	private class InternalDialog extends TrayDialog {

		private WorkbenchStatusDialogManager statusDialog;

		/**
		 * Instantiates the internal dialog on the given shell. Created dialog
		 * uses statusDialog methods to create its contents.
		 * 
		 * @param parentShell -
		 *            a parent shell for the dialog
		 * @param statusDialog -
		 *            a dialog from which methods should be used to create
		 *            contents
		 * @param modal -
		 *            true if created dialog should be modal, false otherwise.
		 */
		public InternalDialog(Shell parentShell,
				WorkbenchStatusDialogManager statusDialog, boolean modal) {
			super(parentShell);

			this.statusDialog = statusDialog;
			setShellStyle(SWT.RESIZE | SWT.MAX | SWT.MIN | getShellStyle());
			setBlockOnOpen(false);

			if (!modal) {
				setShellStyle(~SWT.APPLICATION_MODAL & getShellStyle());
			}
		}

		protected void buttonPressed(int id) {
			if (id == GOTO_ACTION_ID) {
				IAction gotoAction = getGotoAction();
				if (gotoAction != null) {
					if (isPromptToClose()) {
						okPressed(); // close the dialog
						gotoAction.run(); // run the goto action
					}
				}
			}
			if (id == IDialogConstants.DETAILS_ID) {
				// was the details button pressed?
				detailsOpened = toggleDetailsArea();
			} else {
				super.buttonPressed(id);
			}
		}

		/*
		 * (non-Javadoc) Method declared in Window.
		 */
		final protected void configureShell(Shell shell) {
			super.configureShell(shell);
			if (title != null) {
				shell.setText(title);
			}
		}

		public int convertHeightInCharsToPixels(int chars) {
			return super.convertHeightInCharsToPixels(chars);
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see org.eclipse.jface.dialogs.Dialog#convertHorizontalDLUsToPixels(int)
		 */
		public int convertHorizontalDLUsToPixels(int dlus) {
			return super.convertHorizontalDLUsToPixels(dlus);
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see org.eclipse.jface.dialogs.Dialog#convertVerticalDLUsToPixels(int)
		 */
		public int convertVerticalDLUsToPixels(int dlus) {
			return super.convertVerticalDLUsToPixels(dlus);
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see org.eclipse.jface.dialogs.Dialog#convertWidthInCharsToPixels(int)
		 */
		public int convertWidthInCharsToPixels(int chars) {
			return super.convertWidthInCharsToPixels(chars);
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see org.eclipse.jface.dialogs.Dialog#createButton(org.eclipse.swt.widgets.Composite,
		 *      int, java.lang.String, boolean)
		 */
		public Button createButton(Composite parent, int id, String label,
				boolean defaultButton) {
			return super.createButton(parent, id, label, defaultButton);
		}
		
		/**
		 * Status dialog button should be aligned SWT.END. 
		 */
		protected void setButtonLayoutData(Button button) {
			GridData data = new GridData(SWT.END, SWT.FILL, false, false);
			int widthHint = convertHorizontalDLUsToPixels(IDialogConstants.BUTTON_WIDTH);
			Point minSize = button.computeSize(SWT.DEFAULT, SWT.DEFAULT, true);
			data.widthHint = Math.max(widthHint, minSize.x);
			button.setLayoutData(data);
		}

		protected Control createButtonBar(Composite parent) {
			return statusDialog.createButtonBar(parent);
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see org.eclipse.jface.dialogs.Dialog#createDialogArea(org.eclipse.swt.widgets.Composite)
		 */
		protected Control createDialogArea(Composite parent) {
			return statusDialog.createDialogArea(parent);
		}

		public Button getButton(int id) {
			return super.getButton(id);
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see org.eclipse.jface.dialogs.Dialog#initializeBounds()
		 */
		protected void initializeBounds() {
			super.initializeBounds();
			this.statusDialog.initializeBounds();
		}

		/**
		 * This function checks if the dialog is modal.
		 * 
		 * @return true if the dialog is modal, false otherwise
		 * 
		 */
		public boolean isModal() {
			return ((getShellStyle() & SWT.APPLICATION_MODAL) == SWT.APPLICATION_MODAL);
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see org.eclipse.jface.dialogs.Dialog#isResizable()
		 */
		protected boolean isResizable() {
			return true;
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see org.eclipse.jface.window.Window#open()
		 */
		public int open() {
			if (shouldDisplay(statusAdapter, displayMask)) {
				int result = super.open();
				if (modalitySwitch) {
					if (detailsOpened) {
						showDetailsArea();
					}
					if (trayOpened) {
						openTray(supportTray);
					}
				}
				return result;
			}
			setReturnCode(OK);
			return OK;
		}

		/* (non-Javadoc)
		 * @see org.eclipse.jface.dialogs.TrayDialog#closeTray()
		 */
		public void closeTray() throws IllegalStateException {
			super.closeTray();
			trayOpened = false;
			if (launchTrayButton != null && !launchTrayButton.isDisposed()) {
				launchTrayButton.setEnabled(supportTray.providesSupport() && !trayOpened);
			}
		}
		
	}

	/**
	 * This class is responsible for disposing dialog elements when the dialog
	 * is closed.
	 * 
	 * @since 3.4
	 * 
	 */
	private final class StatusDialogDisposeListener implements DisposeListener {
		/*
		 * (non-Javadoc)
		 * 
		 * @see org.eclipse.swt.events.DisposeListener#widgetDisposed(org.eclipse.swt.events.DisposeEvent)
		 */
		public void widgetDisposed(org.eclipse.swt.events.DisposeEvent e) {
			dialog = null;
			statusListViewer = null;
			statusAdapter = null;
			errors.clear();
			modals.clear();
		}
	}

	/**
	 * This class is responsible for displaying the support area on the right
	 * side of the status dialog.
	 * 
	 * @since 3.4
	 * 
	 */
	private class SupportTray extends DialogTray implements
			ISelectionChangedListener {

		private IContributionItem closeAction;
		private Image normal;
		private Image hover;

		private Composite supportArea;
		private Composite supportAreaContent;

		private StatusAdapter lastSelectedStatus;
		private boolean defaultSupportAreaEnabled;

		/**
		 * Creates any actions needed by the tray.
		 */
		private void createActions() {
			createImages();
			closeAction = new ContributionItem() {
				public void fill(ToolBar parent, int index) {
					final ToolItem item = new ToolItem(parent, SWT.PUSH);
					item.setImage(normal);
					item.setHotImage(hover);
					item.setToolTipText(JFaceResources.getString("close")); //$NON-NLS-1$
					item.addListener(SWT.Selection, new Listener() {
						public void handleEvent(Event event) {
							trayOpened = false;

							// close the tray
							closeTray();

							// set focus back to shell
							getShell().setFocus();
						}
					});
				}
			};

		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see org.eclipse.jface.dialogs.DialogTray#createContents(org.eclipse.swt.widgets.Composite)
		 */
		protected Control createContents(Composite parent) {
			Composite container = new Composite(parent, SWT.NONE);

			// nothing to display. Should never happen, cause button is disabled
			// when nothing to display.

			if (!providesSupport()) {
				return container;
			}

			GridLayout layout = new GridLayout();
			layout.marginWidth = layout.marginHeight = 0;
			layout.verticalSpacing = 0;
			container.setLayout(layout);
			GridData layoutData = new GridData(SWT.FILL, SWT.FILL, true, true);
			container.setLayoutData(layoutData);

			container.addListener(SWT.Dispose, new Listener() {
				public void handleEvent(Event event) {
					// dispose event
				}
			});

			ToolBarManager toolBarManager = new ToolBarManager(SWT.FLAT);
			toolBarManager.createControl(container);
			GridData gd = new GridData(GridData.HORIZONTAL_ALIGN_END);
			gd.grabExcessHorizontalSpace = true;
			toolBarManager.getControl().setLayoutData(gd);
			Label separator = new Label(container, SWT.SEPARATOR
					| SWT.HORIZONTAL);
			gd = new GridData(GridData.HORIZONTAL_ALIGN_FILL);
			gd.heightHint = 1;
			separator.setLayoutData(gd);

			createActions();
			toolBarManager.add(closeAction);

			toolBarManager.update(true);

			supportArea = new Composite(container, SWT.NONE);
			layout = new GridLayout();
			layout.marginWidth = layout.marginHeight = 0;
			layout.verticalSpacing = 0;
			supportArea.setLayout(layout);
			gd = new GridData(GridData.HORIZONTAL_ALIGN_FILL
					| GridData.VERTICAL_ALIGN_FILL);
			gd.horizontalSpan = 1;
			gd.grabExcessHorizontalSpace = true;
			gd.grabExcessVerticalSpace = true;
			supportArea.setLayoutData(gd);
			
			// if only one status adapter is displayed,
			// selection listener does not work, so it is necessary to
			// set the last selected status manually
			if (getStatusAdapters().size() == 1) {
				lastSelectedStatus = statusAdapter;
			}

			if (lastSelectedStatus != null)
				createSupportArea(supportArea, lastSelectedStatus);

			Point shellSize = getShell().getSize();
			Point desiredSize = getShell().computeSize(SWT.DEFAULT, SWT.DEFAULT);
			
			if(desiredSize.y > shellSize.y){
				getShell().setSize(shellSize.x, Math.min(desiredSize.y,500));
			}

			return container;
		}

		/**
		 * Creates any custom needed by the tray, such as the close button.
		 */
		private void createImages() {
			Display display = Display.getCurrent();
			int[] shape = new int[] { 3, 3, 5, 3, 7, 5, 8, 5, 10, 3, 12, 3, 12,
					5, 10, 7, 10, 8, 12, 10, 12, 12, 10, 12, 8, 10, 7, 10, 5,
					12, 3, 12, 3, 10, 5, 8, 5, 7, 3, 5 };

			/*
			 * Use magenta as transparency color since it is used infrequently.
			 */
			Color border = display.getSystemColor(SWT.COLOR_WIDGET_DARK_SHADOW);
			Color background = display
					.getSystemColor(SWT.COLOR_LIST_BACKGROUND);
			Color backgroundHot = new Color(display, new RGB(252, 160, 160));
			Color transparent = display.getSystemColor(SWT.COLOR_MAGENTA);

			PaletteData palette = new PaletteData(new RGB[] {
					transparent.getRGB(), border.getRGB(), background.getRGB(),
					backgroundHot.getRGB() });
			ImageData data = new ImageData(16, 16, 8, palette);
			data.transparentPixel = 0;

			normal = new Image(display, data);
			normal.setBackground(transparent);
			GC gc = new GC(normal);
			gc.setBackground(background);
			gc.fillPolygon(shape);
			gc.setForeground(border);
			gc.drawPolygon(shape);
			gc.dispose();

			hover = new Image(display, data);
			hover.setBackground(transparent);
			gc = new GC(hover);
			gc.setBackground(backgroundHot);
			gc.fillPolygon(shape);
			gc.setForeground(border);
			gc.drawPolygon(shape);
			gc.dispose();

			backgroundHot.dispose();
		}

		/**
		 * Create the area for extra error support information.
		 * 
		 * @param parent
		 *            A composite on which should be the support area created.
		 * @param statusAdapter
		 *            StatusAdapter for which should be the support area
		 *            created.
		 */
		private void createSupportArea(Composite parent,
				StatusAdapter statusAdapter) {

			ErrorSupportProvider provider = Policy.getErrorSupportProvider();

			if (userSupportProvider != null) {
				provider = userSupportProvider;
			}

			if (defaultSupportAreaEnabled && provider == null) {
				provider = new StackTraceSupportArea();
			}

			// default support area was disabled
			if (provider == null)
				return;

			if (supportAreaContent != null)
				supportAreaContent.dispose();

			supportAreaContent = new Composite(parent, SWT.FILL);

			GridData supportData = new GridData(SWT.FILL, SWT.FILL, true, true);
			supportAreaContent.setLayoutData(supportData);
			if (supportAreaContent.getLayout() == null) {
				GridLayout layout = new GridLayout();
				layout.marginWidth = 0;
				layout.marginHeight = 0;
				supportAreaContent.setLayout(layout); // Give it a default
				// layout
			}

			if (provider instanceof AbstractStatusAreaProvider) {
				((AbstractStatusAreaProvider) provider).createSupportArea(
						supportAreaContent, statusAdapter);
			} else {
				provider.createSupportArea(supportAreaContent, statusAdapter
						.getStatus());
			}
		}

		/**
		 * This method manages the enablement of the default support area.
		 * 
		 * @param enable
		 *            true enables, false disables.
		 */
		public void enableDefaultSupportArea(boolean enable) {
			this.defaultSupportAreaEnabled = enable;
		}

		private StatusAdapter getStatusAdapterFromEvent(
				SelectionChangedEvent event) {

			ISelection selection = event.getSelection();

			if (selection instanceof StructuredSelection) {
				StructuredSelection structuredSelection = (StructuredSelection) selection;
				Object element = structuredSelection.getFirstElement();
				if (element instanceof StatusAdapter) {
					return (StatusAdapter) element;
				}
			}
			return null;
		}

		/**
		 * Checks if the support dialog has any support areas.
		 * 
		 * @return true if support dialog has any support areas to display,
		 *         false otherwise
		 * 
		 */
		private boolean providesSupport() {
			if (Policy.getErrorSupportProvider() != null) {
				return true;
			}
			if (userSupportProvider != null) {
				return true;
			}
			return defaultSupportAreaEnabled;
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see org.eclipse.jface.viewers.ISelectionChangedListener#selectionChanged(org.eclipse.jface.viewers.SelectionChangedEvent)
		 */
		public void selectionChanged(SelectionChangedEvent event) {
			lastSelectedStatus = getStatusAdapterFromEvent(event);
			if (supportArea != null && !supportArea.isDisposed()) {
				if (lastSelectedStatus != null) {
					createSupportArea(supportArea, lastSelectedStatus);
					supportArea.layout(true);
				}
			}
		}
	}

	/**
	 * Preference used to indicate whether the user should be prompted to
	 * confirm the execution of the job's goto action
	 */
	private static final String PREF_SKIP_GOTO_ACTION_PROMPT = "pref_skip_goto_action_prompt"; //$NON-NLS-1$

	/**
	 * The id of the goto action button
	 */
	private static final int GOTO_ACTION_ID = IDialogConstants.CLIENT_ID + 1;

	/**
	 * Returns whether the given StatusAdapter object should be displayed.
	 * 
	 * @param statusAdapter
	 *            a status object
	 * @param mask
	 *            a mask as per <code>IStatus.matches</code>
	 * @return <code>true</code> if the given status should be displayed, and
	 *         <code>false</code> otherwise
	 * @see org.eclipse.core.runtime.IStatus#matches(int)
	 */
	private static boolean shouldDisplay(StatusAdapter statusAdapter, int mask) {
		IStatus status = statusAdapter.getStatus();
		IStatus[] children = status.getChildren();
		if (children == null || children.length == 0) {
			return status.matches(mask);
		}
		for (int i = 0; i < children.length; i++) {
			if (children[i].matches(mask)) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Stores statuses.
	 */
	private Collection errors = Collections.synchronizedSet(new HashSet());

	/**
	 * Stores information, which statuses should be displayed in modal window.
	 */
	private HashMap modals = new HashMap();

	/**
	 * This field stores the real dialog that appears to the user.
	 */
	private InternalDialog dialog;

	/**
	 * This composite holds all components of the dialog.
	 */
	private Composite dialogArea;

	/**
	 * This composite is initially scrolled to the 0 x 0 size. When more than
	 * one status arrives, listArea is resized and a list is created on it to
	 * present statuses to the user.
	 */
	private Composite listArea;

	/**
	 * On this composite are presented additional elements for displaying single
	 * status. Currently it is the second label that displays the second most
	 * important message to the user.
	 */
	private Composite singleStatusDisplayArea;

	/**
	 * This label is used to display the second most important message to the
	 * user. It is placed on singleStatusDisplayArea.
	 */
	private Label singleStatusLabel;

	/**
	 * A list from which the user selects statuses. The list is placed on
	 * listArea.
	 */
	private TableViewer statusListViewer;

	/**
	 * A list label provider
	 */
	private ITableLabelProvider statusListLabelProvider = new ITableLabelProvider() {
		ResourceManager manager = new LocalResourceManager(JFaceResources.getResources());
	
		/*
		 * (non-Javadoc)
		 * 
		 * @see org.eclipse.jface.viewers.IBaseLabelProvider#addListener(org.eclipse.jface.viewers.ILabelProviderListener)
		 */
		public void addListener(ILabelProviderListener listener) {
			// Do nothing
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see org.eclipse.jface.viewers.IBaseLabelProvider#dispose()
		 */
		public void dispose() {
			manager.dispose();
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see org.eclipse.jface.viewers.ITableLabelProvider#getColumnImage(java.lang.Object,
		 *      int)
		 */
		public Image getColumnImage(Object element, int columnIndex) {
			Image result = null;
			if (element != null) {
				StatusAdapter statusAdapter = ((StatusAdapter) element);
				Job job = (Job) (statusAdapter.getAdapter(Job.class));
				if (job != null) {
					result = getIcon(job);
				}
			}
			// if somehow disposed image was received (should not happen)
			if (result != null && result.isDisposed()) {
				result = null;
			}
			return result;
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see org.eclipse.jface.viewers.ITableLabelProvider#getColumnText(java.lang.Object,
		 *      int)
		 */
		public String getColumnText(Object element, int columnIndex) {
			StatusAdapter statusAdapter = (StatusAdapter) element;
			String text = WorkbenchMessages.WorkbenchStatusDialog_ProblemOccurred;
			if (getStatusAdapters().size() == 1) {
				Job job = (Job) (statusAdapter.getAdapter(Job.class));
				if (job != null) {
					text = getPrimaryMessage(statusAdapter);
				} else {
					text = getSecondaryMessage(statusAdapter);
				}
			} else {
				Job job = (Job) (statusAdapter.getAdapter(Job.class));
				if (job != null) {
					text = job.getName();
				} else {
					text = getPrimaryMessage(statusAdapter);
				}
			}
			Long timestamp = (Long) statusAdapter
					.getProperty(IStatusAdapterConstants.TIMESTAMP_PROPERTY);

			if (timestamp != null && getStatusAdapters().size() > 1) {
				String date = DateFormat.getDateTimeInstance(DateFormat.LONG,
						DateFormat.LONG)
						.format(new Date(timestamp.longValue()));
				return NLS.bind(ProgressMessages.JobInfo_Error, (new Object[] {
						text, date }));
			}
			return text;
		}

		/*
		 * Get the icon for the job.
		 */
		private Image getIcon(Job job) {
			if (job != null) {
				Object property = job
						.getProperty(IProgressConstants.ICON_PROPERTY);

				// Create an image from the job's icon property or family
				if (property instanceof ImageDescriptor) {
					return manager.createImage((ImageDescriptor) property);
				} else if (property instanceof URL) {
					return manager.createImage(ImageDescriptor
							.createFromURL((URL) property));
				} else {
					// Let the progress manager handle the resource management
					return ProgressManager.getInstance().getIconFor(job);
				}
			}
			return null;
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see org.eclipse.jface.viewers.IBaseLabelProvider#isLabelProperty(java.lang.Object,
		 *      java.lang.String)
		 */
		public boolean isLabelProperty(Object element, String property) {
			return false;
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see org.eclipse.jface.viewers.IBaseLabelProvider#removeListener(org.eclipse.jface.viewers.ILabelProviderListener)
		 */
		public void removeListener(ILabelProviderListener listener) {
			// Do nothing
		}
	};

	/**
	 * This variable holds current details area provider.
	 */
	private DetailsAreaManager detailsManager = new DetailsAreaManager();

	private DisposeListener disposeListener = new StatusDialogDisposeListener();

	/**
	 * The title of the dialog.
	 */
	private String title;

	/**
	 * The current clipboard. To be disposed when closing the dialog.
	 */
	private Clipboard clipboard;

	/**
	 * Filter mask for determining which status items to display. Default allows
	 * for displaying all statuses.
	 */
	private int displayMask = 0xFFFF;

	/**
	 * Currently selected status adapter.
	 */
	private StatusAdapter statusAdapter;

	/**
	 * In this support tray status support providers are displayed.
	 */
	private SupportTray supportTray = new WorkbenchStatusDialogManager.SupportTray();

	/**
	 * Toolbar on the left botom corner. Allows for opening support tray.
	 */
	private ToolBar toolBar;
	
	/**
	 * This item is used to launch support tray
	 */
	private ToolItem launchTrayButton;

	/**
	 * This flag indicates if the dialog is switching modality. For now it is
	 * possible only to change from non-modal to modal.
	 */
	private boolean modalitySwitch = false;

	/**
	 * This fields holds the information about dialog position and size when
	 * switching the modality.
	 */
	private Rectangle shellBounds;

	/**
	 * This flag indicates if the details area was opened before switching the
	 * modality or not.
	 */
	private boolean detailsOpened = false;

	/**
	 * This flag indicates if the support area was opened before switching the
	 * modality or not.
	 */
	private boolean trayOpened = false;

	/**
	 * This field contains the support provider set by the user.
	 */
	private AbstractStatusAreaProvider userSupportProvider;

	/**
	 * Main dialog image holder.
	 */
	private Label titleImageLabel;

	/**
	 * Message in the header.
	 */
	private Label mainMessageLabel;

	/**
	 * Header area.
	 */
	private Composite titleArea;

	/**
	 * Creates workbench status dialog.
	 * 
	 * @param displayMask
	 *            the mask used to filter the handled <code>StatusAdapter</code>
	 *            objects, the mask is a logical sum of status severities
	 * @param dialogTitle
	 *            the title of the dialog. If null, than default will be used.
	 */
	
	public WorkbenchStatusDialogManager(int displayMask, String dialogTitle) {
		
		Assert.isNotNull(Display.getCurrent(),
						"WorkbenchStatusDialogManager must be instantiated in UI thread"); //$NON-NLS-1$

		this.displayMask = displayMask;
		this.title = dialogTitle == null ? JFaceResources
				.getString("Problem_Occurred") : //$NON-NLS-1$
				dialogTitle;
	}
	
	/**
	 * Creates workbench status dialog.
	 * 
	 * @param parentShell
	 *            the parent shell for the dialog. It may be null.
	 * @param displayMask
	 *            the mask used to filter the handled <code>StatusAdapter</code>
	 *            objects, the mask is a logical sum of status severities
	 * @param dialogTitle
	 *            the title of the dialog. If null, than default will be used.
	 * @deprecated As of 3.4 the <code>parentShell<code> is ignored
	 * @see #WorkbenchStatusDialogManager(int, String)
	 */
	public WorkbenchStatusDialogManager(Shell parentShell, int displayMask,
			String dialogTitle) {

		this(displayMask, dialogTitle);
	}

	/**
	 * Creates workbench status dialog.
	 * 
	 * @param dialogTitle
	 *            the title of the dialog. If null, than default will be used.
	 */
	public WorkbenchStatusDialogManager(String dialogTitle) {
		this(IStatus.INFO | IStatus.WARNING | IStatus.ERROR, dialogTitle);
	}

	/**
	 * Creates workbench status dialog.
	 * 
	 * @param parentShell
	 *            the parent shell for the dialog. It may be null.
	 * @param dialogTitle
	 *            the title of the dialog. If null, than default will be used.
	 * @deprecated As of 3.4 the <code>parentShell<code> is ignored
	 * @see #WorkbenchStatusDialogManager(String)
	 */
	public WorkbenchStatusDialogManager(Shell parentShell, String dialogTitle) {
		this(dialogTitle);
	}

	/**
	 * <p>
	 * Adds a new {@link StatusAdapter} to the status adapters list in the
	 * dialog.
	 * </p>
	 * <p>
	 * If the dialog is already visible, the status adapter will be shown
	 * immediately. Otherwise, the dialog with the added status adapter will
	 * show up, if all conditions below are false.
	 * <ul>
	 * <li>the status adapter has
	 * {@link IProgressConstants#NO_IMMEDIATE_ERROR_PROMPT_PROPERTY} set to true</li>
	 * </ul>
	 * </p>
	 * <p>
	 * All not shown status adapters will be displayed as soon as the dialog
	 * shows up.
	 * </p>
	 * 
	 * @param modal
	 *            <code>true</code> if the dialog should be modal,
	 *            <code>false</code> otherwise
	 * @param statusAdapter
	 *            the status adapter
	 */
	public void addStatusAdapter(final StatusAdapter statusAdapter,
			final boolean modal) {

		if (ErrorDialog.AUTOMATED_MODE == true) {
			return;
		}
		
		Assert.isNotNull(Display.getCurrent(),
						"WorkbenchStatusDialogManager#addStatusAdapter must be called from UI thread"); //$NON-NLS-1$

		if (!PlatformUI.isWorkbenchRunning()) {
			// we are shutting down, so just log
			WorkbenchPlugin.log(statusAdapter.getStatus());
			return;
		}

		// Add the error in the UI thread to ensure thread safety in the
		// dialog
		if (dialog == null || dialog.getShell().isDisposed()) {

			errors.add(statusAdapter);
			modals.put(statusAdapter, new Boolean(modal));
			// Delay prompting if the status adapter property is set
			if (shouldPrompt(statusAdapter)) {
				if (dialog == null) {
					dialog = new InternalDialog(getParentShell(),
							WorkbenchStatusDialogManager.this, shouldBeModal());
					setSelectedStatusAdapter(statusAdapter);
					dialog.open();
					dialog.getShell().addDisposeListener(disposeListener);
				}
				refresh();
				refreshDialogSize();
			}

		} else {
			if (statusAdapter
					.getProperty(IProgressConstants.NO_IMMEDIATE_ERROR_PROMPT_PROPERTY) != null) {
				statusAdapter.setProperty(
						IProgressConstants.NO_IMMEDIATE_ERROR_PROMPT_PROPERTY,
						Boolean.FALSE);
			}
			openStatusDialog(modal, statusAdapter);
		}
	}

	/**
	 * This method closes the dialog.
	 */
	private boolean close() {
		if (detailsOpened) {
			toggleDetailsArea();
		}
		if (trayOpened) {
			closeTray();
		}
		shellBounds = getShell().getBounds();
		if (clipboard != null) {
			clipboard.dispose();
		}
		statusListViewer = null;
		boolean result = this.dialog.close();
		if (!modalitySwitch) {
			ProgressManagerUtil.animateDown(shellBounds);
		}
		return result;
	}

	/**
	 * Closes the dialog tray (it is support area at the right side of the
	 * dialog)
	 * 
	 * @throws IllegalStateException
	 */
	private void closeTray() throws IllegalStateException {
		this.dialog.closeTray();
	}

	/**
	 * This method creates button bar that is available on the bottom of the
	 * dialog.
	 */
	private Control createButtonBar(Composite parent) {
		Composite composite = new Composite(parent, SWT.NONE);
		GridLayout layout = new GridLayout();
		layout.marginWidth = dialog
				.convertHorizontalDLUsToPixels(IDialogConstants.HORIZONTAL_MARGIN);
		layout.marginHeight = dialog
				.convertVerticalDLUsToPixels(IDialogConstants.VERTICAL_MARGIN);
		composite.setLayout(layout);
		composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));

		toolBar = createSupportToolbar(composite);

		// Add the buttons to the button bar.
		createButtonsForButtonBar(composite);

		composite.layout();
		return composite;
	}

	/**
	 * This method creates buttons that are placed on button bar.
	 */
	private void createButtonsForButtonBar(Composite parent) {
		IAction gotoAction = getGotoAction();
		String text = null;
		if (gotoAction != null) {
			text = gotoAction.getText();
		}
		Button button = dialog.createButton(parent, GOTO_ACTION_ID,
				text == null ? "" : text, //$NON-NLS-1$
				false);
		if (text == null)
			hideButton(button, true);

		dialog.createButton(parent, IDialogConstants.OK_ID,
				IDialogConstants.OK_LABEL, true);

		dialog.createButton(parent, IDialogConstants.DETAILS_ID,
				IDialogConstants.SHOW_DETAILS_LABEL, false);
	}

	/**
	 * This method creates dialog area.
	 */
	private Control createDialogArea(Composite parent) {
		createTitleArea(parent);
		createListArea(parent);
		dialogArea = parent;
		Dialog.applyDialogFont(dialogArea);
		return parent;
	}

	/**
	 * Create an area which allows the user to view the status if only one is
	 * created or to select one of reported statuses when there are many.
	 * 
	 * @param parent
	 *            the parent composite on which all components should be placed.
	 */
	private void createListArea(Composite parent) {
		listArea = new Composite(parent, SWT.NONE);
		GridData layoutData = new GridData(SWT.FILL, SWT.FILL, true, true);
		layoutData.heightHint = 0;
		layoutData.widthHint = 0;
		listArea.setLayoutData(layoutData);
		GridLayout layout = new GridLayout();
		listArea.setLayout(layout);
		if(getStatusAdapters().size() > 1){
			fillListArea(listArea);
		}
	}

	/**
	 * This method creates additional display area for {@link StatusAdapter}
	 * when only one is available.
	 * 
	 * It creates one label on a composite currently for secondary message.
	 * 
	 * @param parent
	 *            A parent composite on which all components should be placed.
	 * @return composite the composite on which are all components for
	 *         displaying status when only one is available.
	 */
	private Composite createSingleStatusDisplayArea(Composite parent) {
		// secondary message is displayed on separate composite with no margins
		Composite singleStatusParent = new Composite(parent, SWT.NONE);
		GridLayout gridLayout = new GridLayout();
		gridLayout.marginWidth = 0;
		singleStatusParent.setLayout(gridLayout);
		GridData gd = new GridData(SWT.FILL, SWT.FILL, true, false);
		singleStatusParent.setLayoutData(gd);

		// label that wraps
		singleStatusLabel = new Label(singleStatusParent, SWT.WRAP);
		GridData labelLayoutData = new GridData(SWT.FILL, SWT.FILL, true, true);
		labelLayoutData.widthHint = dialog.convertWidthInCharsToPixels(50);
		singleStatusLabel.setLayoutData(labelLayoutData);
		// main message set up early, to address bug 222391
		singleStatusLabel.setText(statusListLabelProvider.getColumnText(
				statusAdapter, 0));

		singleStatusLabel.addMouseListener(new MouseListener() {	
			public void mouseDoubleClick(MouseEvent e) {
			}

			public void mouseDown(MouseEvent e) {
				showDetailsArea();
			}

			public void mouseUp(MouseEvent e) {
			}
		});
		return singleStatusParent;
	}

	/**
	 * Creates a new control that provides access to support providers.
	 * <p>
	 * The <code>WorkbenchStatusDialog</code> implementation of this method
	 * creates the control, registers it for selection events including
	 * selection, Note that the parent's layout is assumed to be a
	 * <code>GridLayout</code> and the number of columns in this layout is
	 * incremented. Subclasses may override.
	 * </p>
	 * 
	 * @param parent
	 *            A parent composite on which all components should be placed.
	 * @return the report control
	 */
	private ToolBar createSupportToolbar(Composite parent) {
		ToolBar toolBar = new ToolBar(parent, SWT.FLAT | SWT.NO_FOCUS);
		((GridLayout) parent.getLayout()).numColumns++;
		GridData layoutData = new GridData(SWT.BEGINNING, SWT.FILL, true, false);
		toolBar.setLayoutData(layoutData);
		final Cursor cursor = new Cursor(parent.getDisplay(), SWT.CURSOR_HAND);
		toolBar.setCursor(cursor);
		toolBar.addDisposeListener(new DisposeListener() {
			public void widgetDisposed(DisposeEvent e) {
				cursor.dispose();
			}
		});
		toolBar.setEnabled(false);
		return toolBar;
	}

	/**
	 * Creates a button with a report image. This is only used if there is an
	 * image available.
	 */
	private ToolItem createShowSupportToolItem(ToolBar parent) {
		ImageDescriptor descriptor = WorkbenchImages
				.getImageDescriptor(IWorkbenchGraphicConstants.IMG_DTOOL_SHOW_SUPPORT);
		final Image image = new Image(parent.getDisplay(), descriptor
				.getImageData());

		ToolItem toolItem = new ToolItem(parent, SWT.NONE);
		toolItem.setImage(image);
		toolItem
				.setToolTipText(WorkbenchMessages.WorkbenchStatusDialog_Support);
		toolItem.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				openTray(supportTray);
			}
		});
		toolItem.addDisposeListener(new DisposeListener() {
			public void widgetDisposed(DisposeEvent e) {
				image.dispose();
			}
		});
		return toolItem;
	}

	/**
	 * Creates title area.
	 * 
	 * @param parent
	 *            A composite on which the title area should be created.
	 */
	private void createTitleArea(Composite parent) {
		titleArea = new Composite(parent, SWT.NONE);
		titleArea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));

		GridLayout layout = new GridLayout();
		layout.numColumns = 2;
		layout.horizontalSpacing = 10;
		layout.marginLeft = 10;
		layout.marginTop = 10;
		layout.marginBottom = 0;
		titleArea.setLayout(layout);

		titleImageLabel = new Label(titleArea, SWT.NONE);
		titleImageLabel.setImage(getErrorImage());
		GridData layoutData = new GridData();
		layoutData.verticalSpan = 2;
		layoutData.verticalAlignment = SWT.TOP;
		titleImageLabel.setLayoutData(layoutData);

		GridData messageData = new GridData(SWT.FILL, SWT.FILL, true, true);
		messageData.widthHint = dialog.convertWidthInCharsToPixels(50);
		mainMessageLabel = new Label(titleArea, SWT.WRAP);
		mainMessageLabel.setLayoutData(messageData);
		// main message set up early, to address bug 222391
		mainMessageLabel.setText(getMainMessage(statusAdapter));
		if (getStatusAdapters().size() == 1) {
			singleStatusDisplayArea = createSingleStatusDisplayArea(titleArea);
		}
	}

	/**
	 * Enables the default support area that shows stack trace of the exception
	 * contained in the selected status.
	 * 
	 * @param enable
	 *            true enables, false disables default support
	 */
	public void enableDefaultSupportArea(boolean enable) {
		supportTray.enableDefaultSupportArea(enable);
	}

	/**
	 * This method creates display area for {@link StatusAdapter}s when more is
	 * available.
	 * 
	 * @param parent
	 *            A parent composite on which all components should be placed.
	 */
	private void fillListArea(Composite parent) {
		// it is necessary to make list parent composite taller
		GridData listAreaGD = (GridData) parent.getLayoutData();
		listAreaGD.grabExcessHorizontalSpace = true;
		listAreaGD.grabExcessVerticalSpace = true;
		listAreaGD.heightHint = SWT.DEFAULT;

		// create list viewer
		statusListViewer = new TableViewer(parent, SWT.SINGLE | SWT.H_SCROLL
				| SWT.V_SCROLL | SWT.BORDER);
		statusListViewer.setComparator(getViewerComparator());
		Control control = statusListViewer.getControl();
		GridData data = new GridData(SWT.FILL, SWT.FILL, true, true);
		data.heightHint = dialog.convertHeightInCharsToPixels(5);
		statusListViewer.addSelectionChangedListener(supportTray);
		control.setLayoutData(data);
		initContentProvider();
		initLabelProvider();
		statusListViewer
				.addSelectionChangedListener(new ISelectionChangedListener() {
					public void selectionChanged(SelectionChangedEvent event) {
						handleSelectionChange();
					}
				});
		Dialog.applyDialogFont(parent);
	}

	/**
	 * Return the <code>Image</code> to be used when displaying an error.
	 * 
	 * @return image the error image
	 */
	private Image getErrorImage() {
		return getSWTImage(SWT.ICON_ERROR);
	}

	/**
	 * Returns {@link IAction} associated with selected StatusAdapter.
	 * 
	 * @return {@link IAction} that is set as {@link StatusAdapter} property
	 *         with Job.class key.
	 */
	private IAction getGotoAction() {
		Object property = null;

		Job job = (Job) (statusAdapter.getAdapter(Job.class));
		if (job != null) {
			property = job.getProperty(IProgressConstants.ACTION_PROPERTY);
		}

		if (property instanceof IAction) {
			return (IAction) property;
		}
		return null;
	}

	/**
	 * Gets {@link Image} associated with current {@link StatusAdapter}
	 * severity.
	 * 
	 * @return {@link Image} associated with current {@link StatusAdapter}
	 *         severity.
	 */
	private Image getImage() {
		if (statusAdapter != null) {
			IStatus status = statusAdapter.getStatus();
			if (status != null) {
				if (status.getSeverity() == IStatus.WARNING) {
					return getWarningImage();
				}
				if (status.getSeverity() == IStatus.INFO) {
					return getInfoImage();
				}
			}
		}
		// If it was not a warning or an error then return the error image
		return getErrorImage();
	}

	/**
	 * Return the <code>Image</code> to be used when displaying information.
	 * 
	 * @return image the information image
	 */
	private Image getInfoImage() {
		return getSWTImage(SWT.ICON_INFORMATION);
	}

	/**
	 * This method computes the dialog main message.
	 * 
	 * If there is only one reported status adapter, main message should be:
	 * <ul>
	 * <li>information about job that reported an error.</li>
	 * <li>primary message, if the statusAdapter was not reported by job</li>
	 * </ul>
	 * 
	 * If there is more reported statusAdapters, main message should be:
	 * <ul>
	 * <li>primary message for job reported statusAdapters</li>
	 * <li>secondary message for statuses not reported by jobs</li>
	 * </ul>
	 * 
	 * If nothing can be found, some general information should be displayed.
	 * 
	 * @param statusAdapter
	 *            A status adapter which is used as the base for computation.
	 * @return main message of the dialog.
	 * 
	 * @see WorkbenchStatusDialogManager#getPrimaryMessage(StatusAdapter)
	 * @see WorkbenchStatusDialogManager#getSecondaryMessage(StatusAdapter)
	 * @see WorkbenchStatusDialogManager#setStatusListLabelProvider(ITableLabelProvider)
	 */
	private String getMainMessage(StatusAdapter statusAdapter) {
		if (errors.size() == 1) {
			Job job = (Job) (statusAdapter.getAdapter(Job.class));
			// job
			if (job != null) {
				return NLS
						.bind(
								WorkbenchMessages.WorkbenchStatusDialog_ProblemOccurredInJob,
								job.getName());
			}
			// we are not handling job
			return getPrimaryMessage(statusAdapter);
		}
		// we have a list. primary message or job name or on the list name (both
		// with timestamp if available).
		// we display secondary message or status
		if (errors.size() > 1) {
			Job job = (Job) (statusAdapter.getAdapter(Job.class));
			// job
			if (job != null) {
				return getPrimaryMessage(statusAdapter);
			}

			// plain status
			return getSecondaryMessage(statusAdapter);
		}
		return WorkbenchMessages.WorkbenchStatusDialog_ProblemOccurred;
	}

	/**
	 * Return the parent shell.
	 * 
	 * @return the parent shell of the dialog.
	 */
	private Shell getParentShell() {
		return ProgressManagerUtil.getDefaultParent();
	}

	/**
	 * Retrieves primary message from passed statusAdapter. Primary message
	 * should be (from the most important):
	 * <ul>
	 * <li>statusAdapter title</li>
	 * <li>IStatus message</li>
	 * <li>pointing to child statuses if IStatus has them.</li>
	 * <li>exception message</li>
	 * <li>exception class</li>
	 * <li>general message informing about error (no details at all)</li>
	 * </ul>
	 * 
	 * @param statusAdapter
	 *            an status adapter to retrieve primary message from
	 * @return String containing primary message
	 * 
	 * @see WorkbenchStatusDialogManager#getMainMessage(StatusAdapter)
	 * @see WorkbenchStatusDialogManager#getSecondaryMessage(StatusAdapter)
	 */
	private String getPrimaryMessage(StatusAdapter statusAdapter) {
		// if there was nonempty title set, display the title
		Object property = statusAdapter
				.getProperty(IStatusAdapterConstants.TITLE_PROPERTY);
		if (property instanceof String) {
			String header = (String) property;
			if (header.trim().length() > 0) {
				return header;
			}
		}
		// if there was message set in the status
		IStatus status = statusAdapter.getStatus();
		if (status.getMessage() != null
				&& status.getMessage().trim().length() > 0) {
			return status.getMessage();
		}

		// if status has children
		if (status.getChildren().length > 0) {
			return WorkbenchMessages.WorkbenchStatusDialog_StatusWithChildren;
		}

		// check the exception
		Throwable t = status.getException();
		if (t != null) {
			if (t.getMessage() != null && t.getMessage().trim().length() > 0) {
				return t.getMessage();
			}
			return t.getClass().getName();
		}
		return WorkbenchMessages.WorkbenchStatusDialog_ProblemOccurred;
	}

	/**
	 * Retrieves secondary message from the passed statusAdapter. Secondary
	 * message is one level lower than primary. Secondary message should be
	 * (from the most important):
	 * <ul>
	 * <li>IStatus message</li>
	 * <li>pointing to child statuses if IStatus has them.</li>
	 * <li>exception message</li>
	 * <li>exception class</li>
	 * </ul>
	 * Secondary message should not be the same as primary one. If no secondary
	 * message can be extracted, details should be pointed.
	 * 
	 * @param statusAdapter
	 *            an status adapter to retrieve secondary message from
	 * @return String containing secondary message
	 * 
	 * @see WorkbenchStatusDialogManager#getMainMessage(StatusAdapter)
	 * @see WorkbenchStatusDialogManager#getPrimaryMessage(StatusAdapter)
	 */
	private String getSecondaryMessage(StatusAdapter statusAdapter) {
		String primary = getPrimaryMessage(statusAdapter);
		// we can skip the title, it is always displayed as primary message

		// if there was message set in the status
		IStatus status = statusAdapter.getStatus();
		if (status.getMessage() != null
				&& status.getMessage().trim().length() > 0
				&& !primary.equals(status.getMessage())) { // we have not
			// displayed it yet
			return status.getMessage();
		}
		// if status has children
		if (status.getChildren().length > 0
				&& !primary.equals(status.getMessage())) {
			return WorkbenchMessages.WorkbenchStatusDialog_StatusWithChildren;
		}

		// check the exception
		Throwable t = status.getException();
		if (t != null) {
			if (t.getMessage() != null && t.getMessage().trim().length() > 0
					&& !primary.equals(status.getMessage())) {
				return t.getMessage();
			}
			return t.getClass().getName();
		}
		return WorkbenchMessages.WorkbenchStatusDialog_SeeDetails;
	}

	/**
	 * Returns the shell of the dialog.
	 */
	private Shell getShell() {
		return this.dialog.getShell();
	}

	/**
	 * Get the single selection. Return null if the selection is not just one
	 * element.
	 * 
	 * @return StatusAdapter or <code>null</code>.
	 */
	private StatusAdapter getSingleSelection() {
		ISelection rawSelection = statusListViewer.getSelection();
		if (rawSelection != null
				&& rawSelection instanceof IStructuredSelection) {
			IStructuredSelection selection = (IStructuredSelection) rawSelection;
			if (selection.size() == 1) {
				return (StatusAdapter) selection.getFirstElement();
			}
		}
		return null;
	}

	/**
	 * Gets a collection of status adapters that were passed to the dialog.
	 * 
	 * @return collection of {@link StatusAdapter} objects
	 */
	public Collection getStatusAdapters() {
		return Collections.unmodifiableCollection(errors);
	}

	/**
	 * Get an <code>Image</code> from the provide SWT image constant.
	 * 
	 * @param imageID
	 *            the SWT image constant
	 * @return image the image
	 */
	private Image getSWTImage(final int imageID) {
		return getShell().getDisplay().getSystemImage(imageID);
	}

	/**
	 * Return a viewer sorter for looking at the jobs.
	 * 
	 * @return ViewerSorter
	 */
	private ViewerComparator getViewerComparator() {
		return new ViewerComparator() {
			private int compare(StatusAdapter s1, StatusAdapter s2) {
				Long timestamp1 = ((Long) s1
						.getProperty(IStatusAdapterConstants.TIMESTAMP_PROPERTY));
				Long timestamp2 = ((Long) s2
						.getProperty(IStatusAdapterConstants.TIMESTAMP_PROPERTY));
				if (timestamp1 == null || timestamp2 == null
						|| (timestamp1.equals(timestamp2))) {
					String text1 = statusListLabelProvider.getColumnText(s1, 0);
					String text2 = statusListLabelProvider.getColumnText(s2, 0);
					return text1.compareTo(text2);
				}

				if (timestamp1.longValue() < timestamp2.longValue()) {
					return -1;
				}
				if (timestamp1.longValue() > timestamp2.longValue()) {
					return 1;
				}
				// should be never called
				return 0;
			}

			/*
			 * (non-Javadoc)
			 * 
			 * @see org.eclipse.jface.viewers.ViewerComparator#compare(org.eclipse.jface.viewers.Viewer,
			 *      java.lang.Object, java.lang.Object)
			 */
			public int compare(Viewer testViewer, Object o1, Object o2) {
				if (o1 instanceof StatusAdapter && o2 instanceof StatusAdapter) {
					return compare((StatusAdapter) o1, (StatusAdapter) o2);
				}
				// should not happen
				if (o1.hashCode() < o2.hashCode()) {
					return -1;
				}
				if (o2.hashCode() > o2.hashCode()) {
					return 1;
				}
				return 0;
			}
		};
	}

	/**
	 * Return the <code>Image</code> to be used when displaying a warning.
	 * 
	 * @return image the warning image
	 */
	private Image getWarningImage() {
		return getSWTImage(SWT.ICON_WARNING);
	}

	/**
	 * The selection in the multiple job list has changed. Update widget
	 * enablements, repopulate the list and show details.
	 */
	private void handleSelectionChange() {
		StatusAdapter newSelection = getSingleSelection();
		if (newSelection != null) {
			setSelectedStatusAdapter(newSelection);
			showDetailsArea();
			refresh();
		}
	}

	/**
	 * Sets the content provider for the viewer.
	 */
	private void initContentProvider() {
		IContentProvider provider = new IStructuredContentProvider() {
			/*
			 * (non-Javadoc)
			 * 
			 * @see org.eclipse.jface.viewers.IContentProvider#dispose()
			 */
			public void dispose() {
				// Nothing of interest here
			}

			/*
			 * (non-Javadoc)
			 * 
			 * @see org.eclipse.jface.viewers.IStructuredContentProvider#getElements(java.lang.Object)
			 */
			public Object[] getElements(Object inputElement) {
				return getStatusAdapters().toArray();
			}

			/*
			 * (non-Javadoc)
			 * 
			 * @see org.eclipse.jface.viewers.IContentProvider#inputChanged(org.eclipse.jface.viewers.Viewer,
			 *      java.lang.Object, java.lang.Object)
			 */
			public void inputChanged(Viewer viewer, Object oldInput,
					Object newInput) {
				if (newInput != null) {
					refreshStatusListArea();
				}
			}
		};
		statusListViewer.setContentProvider(provider);
		statusListViewer.setInput(this);
		statusListViewer.setSelection(new StructuredSelection(statusAdapter));
	}

	/**
	 * This method should initialize the dialog bounds.
	 */
	private void initializeBounds() {
		refreshDialogSize();
		if (!modalitySwitch) {
			Rectangle shellPosition = getShell().getBounds();
			ProgressManagerUtil.animateUp(shellPosition);
		} else {
			getShell().setBounds(shellBounds);
		}
	}

	/**
	 * Sets initial label provider.
	 */
	private void initLabelProvider() {
		statusListViewer.setLabelProvider(statusListLabelProvider);
	}

	/*
	 * Prompt to inform the user that the dialog will close and the errors
	 * cleared.
	 */
	private boolean isPromptToClose() {
		IPreferenceStore store = WorkbenchPlugin.getDefault()
				.getPreferenceStore();
		if (!store.contains(PREF_SKIP_GOTO_ACTION_PROMPT)
				|| !store.getString(PREF_SKIP_GOTO_ACTION_PROMPT).equals(
						MessageDialogWithToggle.ALWAYS)) {
			MessageDialogWithToggle dialog = MessageDialogWithToggle
					.openOkCancelConfirm(
							getShell(),
							ProgressMessages.JobErrorDialog_CloseDialogTitle,
							ProgressMessages.JobErrorDialog_CloseDialogMessage,
							ProgressMessages.JobErrorDialog_DoNotShowAgainMessage,
							false, store, PREF_SKIP_GOTO_ACTION_PROMPT);
			return dialog.getReturnCode() == Window.OK;
		}
		return true;
	}

	/**
	 * Opens status dialog with particular statusAdapter selected.
	 * 
	 * @param modal
	 *            decides if window is modal or not.
	 * @param statusAdapter
	 *            status adapter to be selected.
	 */
	private void openStatusDialog(final boolean modal,
			final StatusAdapter statusAdapter) {
		errors.add(statusAdapter);
		modals.put(statusAdapter, new Boolean(modal));
		boolean shouldBeModal = shouldBeModal();
		if (shouldBeModal ^ dialog.isModal()) {
			dialog.getShell().removeDisposeListener(disposeListener);
			modalitySwitch = true;
			close();
			setSelectedStatusAdapter(statusAdapter);
			dialog = new InternalDialog(getParentShell(), this, modal);
			dialog.open();
			dialog.getShell().addDisposeListener(disposeListener);
			modalitySwitch = false;
		}
		refresh();
	}

	/**
	 * Opens the dialog tray (support area at the right side of the dialog)
	 */
	private void openTray(DialogTray tray) throws IllegalStateException,
			UnsupportedOperationException {
		launchTrayButton.setEnabled(false);
		this.dialog.openTray(tray);
		trayOpened = true;
	}

	/**
	 * Method which should be invoked when new errors become available for
	 * display.
	 */
	private void refresh() {
		if (dialog == null) {
			return;
		}
		if (dialogArea == null || dialogArea.isDisposed()) {
			return;
		}
		updateTitleArea();
		updateListArea();
		updateEnablements();
		// adjust width if necessary
		Point currentSize = getShell().getSize();
		Point desiredSize = getShell().computeSize(SWT.DEFAULT, SWT.DEFAULT);
		if(currentSize.x < desiredSize.x){
			getShell().setSize(desiredSize.x, currentSize.y);
		} else {
			getShell().layout();
		}
	}

	private void refreshDialogSize() {
		Point newSize = getShell().computeSize(SWT.DEFAULT, SWT.DEFAULT);
		getShell().setSize(newSize);
	}

	/**
	 * Refreshes the single status area. Is called only when there is one and
	 * only one error.
	 */
	private void refreshSingleStatusArea() {
		String description = statusListLabelProvider.getColumnText(
				statusAdapter, 0);
		if (description.equals(singleStatusLabel.getText()))
			singleStatusLabel.setText(" "); //$NON-NLS-1$
		singleStatusLabel.setText(description);
		singleStatusDisplayArea.layout();
		getShell().setText(title);
	}

	/**
	 * Refresh the contents of the viewer.
	 */
	private void refreshStatusListArea() {
		if (statusListViewer != null
				&& !statusListViewer.getControl().isDisposed()) {
			statusListViewer.refresh();
			if (statusListViewer.getTable().getItemCount() > 1) {
				getShell()
						.setText(
								WorkbenchMessages.WorkbenchStatusDialog_MultipleProblemsHaveOccured);
			} else {
				getShell().setText(this.title);
			}
		}
	}

	/**
	 * Sets the details area provider. If null is set, the default area provider
	 * will be used.
	 * 
	 * @param provider
	 *            A details area provider to be set.
	 */
	public void setDetailsAreaProvider(AbstractStatusAreaProvider provider) {
		this.detailsManager.setDetailsAreaProvider(provider);
	}

	/**
	 * Sets current status adapter.
	 * 
	 * @param statusAdapter
	 *            The statusAdapter to set.
	 */
	private void setSelectedStatusAdapter(StatusAdapter statusAdapter) {
		if (this.statusAdapter != statusAdapter) {
			this.statusAdapter = statusAdapter;
		}
	}

	/**
	 * Sets new label provider for the status list. This label provider is used
	 * also to display the second message on the dialog if only one status is
	 * available.
	 * 
	 * @param labelProvider
	 *            a label provider to be used when displaying status adapters.
	 *            It must not be null.
	 */
	public void setStatusListLabelProvider(ITableLabelProvider labelProvider) {
		if (statusListLabelProvider != null) {
			throw new NullPointerException("Label provider cannot be null"); //$NON-NLS-1$
		}
		statusListLabelProvider = labelProvider;
	}

	/**
	 * Sets the support provider.
	 * 
	 * The policy for choosing support provider is:
	 * <ol>
	 * <li>use the support provider set by this method, if set</li>
	 * <li>use the support provider set in JFace Policy, if set</li>
	 * <li>use the default support area, if enabled</li>
	 * </ol>
	 * 
	 * @param provider
	 *            Support provider to be set.
	 */
	public void setSupportAreaProvider(AbstractStatusAreaProvider provider) {
		userSupportProvider = provider;
	}

	/**
	 * Decides if dialog should be modal. Dialog will be modal if any of the
	 * statuses contained by StatusAdapters had been reported with
	 * {@link StatusManager#BLOCK} flag.
	 * 
	 * @return true if any StatusHandler should be displayed in modal window
	 */
	private boolean shouldBeModal() {
		for (Iterator it = modals.keySet().iterator(); it.hasNext();) {
			Object o = it.next();
			Object value = modals.get(o);
			if (value instanceof Boolean) {
				Boolean b = (Boolean) value;
				if (b.booleanValue()) {
					return true;
				}
			}
		}
		return false;
	}

	/**
	 * Checks if the user should be prompted immediately about
	 * {@link StatusAdapter}
	 * 
	 * @param statusAdapter
	 *            to be checked.
	 * @return true if the statusAdapter should be prompted, false otherwise.
	 */
	private boolean shouldPrompt(final StatusAdapter statusAdapter) {
		Object noPromptProperty = statusAdapter
				.getProperty(IProgressConstants.NO_IMMEDIATE_ERROR_PROMPT_PROPERTY);

		boolean prompt = true;
		if (noPromptProperty instanceof Boolean) {
			prompt = !((Boolean) noPromptProperty).booleanValue();
		}
		return prompt;
	}

	/**
	 * Show the details portion of the dialog if it is not already visible. This
	 * method will only work when it is invoked after the control of the dialog
	 * has been set. In other words, after the <code>createContents</code>
	 * method has been invoked and has returned the control for the content area
	 * of the dialog. Invoking the method before the content area has been set
	 * or after the dialog has been disposed will have no effect.
	 */
	private void showDetailsArea() {
		if (dialogArea != null && !dialogArea.isDisposed()) {
			if (detailsManager.isOpen()) {
				detailsManager.close();
				detailsManager.createDetailsArea(dialogArea, statusAdapter);
			} else {
				toggleDetailsArea();
				detailsOpened = true;
			}
			dialogArea.layout();
		}
	}

	/**
	 * Toggles the unfolding of the details area. This is triggered by the user
	 * pressing the details button.
	 * 
	 */
	private boolean toggleDetailsArea() {
		boolean opened = false;
		Point windowSize = getShell().getSize();
		if (detailsManager.isOpen()) {
			detailsManager.close();
			dialog.getButton(IDialogConstants.DETAILS_ID).setText(
					IDialogConstants.SHOW_DETAILS_LABEL);
			opened = false;
		} else {
			detailsManager.createDetailsArea(dialogArea, statusAdapter);
			dialog.getButton(IDialogConstants.DETAILS_ID).setText(
					IDialogConstants.HIDE_DETAILS_LABEL);
			opened = true;
		}
		if(getStatusAdapters().size() == 1){
			GridData gd = (GridData) listArea.getLayoutData();
			if(opened){
				gd.heightHint = 0;
				gd.grabExcessVerticalSpace = false;
			} else {
				gd.grabExcessVerticalSpace = true;
			}
			listArea.setLayoutData(gd);
		}
		Point newSize = getShell().computeSize(SWT.DEFAULT, SWT.DEFAULT);
		int diffY = newSize.y - windowSize.y;
		// increase the dialog height if details were opened and such increase is necessary
		// decrease the dialog height if details were closed and empty space appeared
		if ((opened && diffY > 0) || (!opened && diffY < 0)) {
			getShell().setSize(new Point(windowSize.x, windowSize.y + (diffY)));
		}
		dialogArea.layout();
		return opened;
	}

	/**
	 * Update the button enablements
	 */
	private void updateEnablements() {
		Button details = dialog.getButton(IDialogConstants.DETAILS_ID);
		if (details != null) {
			details.setEnabled(true);
		}
		Button gotoButton = dialog.getButton(GOTO_ACTION_ID);
		if (gotoButton != null) {
			IAction gotoAction = getGotoAction();
			boolean hasValidGotoAction = (gotoAction != null) && (gotoAction.getText() != null);
			if (hasValidGotoAction) {
				hideButton(gotoButton,false);
				gotoButton.setText(gotoAction.getText());
				
				((GridData) gotoButton.getLayoutData()).widthHint = gotoButton
						.computeSize(SWT.DEFAULT, SWT.DEFAULT).x;
				gotoButton.getParent().layout();
			}
			else	
				hideButton(gotoButton,true);
		}
		// and tray enablement button
		if (supportTray.providesSupport()) {
			if(launchTrayButton == null || launchTrayButton.isDisposed()){
				launchTrayButton = createShowSupportToolItem(toolBar);
			}
			launchTrayButton.setEnabled(!trayOpened);
		} else {
			if(launchTrayButton != null && !launchTrayButton.isDisposed()){
				launchTrayButton.dispose();
				launchTrayButton = null;
			}
		}
		toolBar.getParent().layout();
		toolBar.setEnabled(toolBar.getItemCount() > 0);
	}

	/**
	 * Hide the button if hide is <code>true</code>.
	 * 
	 * @param button
	 * @param hide
	 */
	private void hideButton(Button button, boolean hide) {
		((GridData) button.getLayoutData()).exclude = hide;
		button.setVisible(!hide);
		button.setEnabled(!hide);
	}

	/**
	 * This methods switches StatusAdapters presentation depending if there is
	 * one status or more.
	 */
	private void updateListArea() {
		// take care about list area if there is more than one status
		if (errors.size() > 1) {
			if (singleStatusDisplayArea != null) {
				singleStatusDisplayArea.dispose();
			}
			if (statusListViewer == null
					|| statusListViewer.getControl().isDisposed()) {
				fillListArea(listArea);
				getShell().setSize(
						getShell().computeSize(SWT.DEFAULT, SWT.DEFAULT));
			}
			refreshStatusListArea();
		}
	}

	/**
	 * Updated title area. Adjust title, title message and title image according
	 * to selected {@link StatusAdapter}.
	 */
	private void updateTitleArea() {
		Image image = getImage();
		titleImageLabel.setImage(image);
		if (statusAdapter != null) {
			mainMessageLabel.setText(getMainMessage(statusAdapter));
		}
		if (getStatusAdapters().size() > 1 && singleStatusDisplayArea != null) {
			singleStatusDisplayArea.dispose();
		} else {
			refreshSingleStatusArea();
		}
		titleArea.layout();
	}
}
