/*******************************************************************************
 * Copyright (c) 2005, 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.ltk.internal.ui.refactoring.history;

import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;

import org.eclipse.core.resources.IProject;

import org.eclipse.ltk.core.refactoring.RefactoringCore;
import org.eclipse.ltk.core.refactoring.RefactoringDescriptor;
import org.eclipse.ltk.core.refactoring.RefactoringDescriptorProxy;
import org.eclipse.ltk.core.refactoring.history.RefactoringHistory;

import org.eclipse.ltk.internal.ui.refactoring.BasicElementLabels;
import org.eclipse.ltk.internal.ui.refactoring.Messages;
import org.eclipse.ltk.internal.ui.refactoring.RefactoringUIMessages;

import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.BusyIndicator;
import org.eclipse.swt.custom.CLabel;
import org.eclipse.swt.custom.ViewForm;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.swt.widgets.Widget;

import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.viewers.AbstractTreeViewer;
import org.eclipse.jface.viewers.CheckStateChangedEvent;
import org.eclipse.jface.viewers.CheckboxTreeViewer;
import org.eclipse.jface.viewers.ICheckStateListener;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITreeViewerListener;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TreeExpansionEvent;
import org.eclipse.jface.viewers.TreeViewer;

import org.eclipse.ui.progress.UIJob;

import org.eclipse.compare.CompareViewerPane;
import org.eclipse.compare.Splitter;

import org.eclipse.ltk.ui.refactoring.history.IRefactoringHistoryControl;
import org.eclipse.ltk.ui.refactoring.history.RefactoringHistoryContentProvider;
import org.eclipse.ltk.ui.refactoring.history.RefactoringHistoryControlConfiguration;
import org.eclipse.ltk.ui.refactoring.history.RefactoringHistoryLabelProvider;

/**
 * Control which is capable of displaying refactoring histories.
 * 
 * @since 3.2
 */
public class RefactoringHistoryControl extends Composite implements IRefactoringHistoryControl {

	/** Label for the refactoring history tree viewer */
	protected final class RefactoringHistoryLabel extends CLabel {

		/**
		 * Creates a new refactoring history label.
		 * 
		 * @param parent
		 *            the parent control
		 * @param style
		 *            the style
		 */
		public RefactoringHistoryLabel(final Composite parent, final int style) {
			super(parent, style);
		}

		/**
		 * {@inheritDoc}
		 */
		public Point computeSize(final int wHint, final int hHint, final boolean changed) {
			return super.computeSize(wHint, Math.max(24, hHint), changed);
		}

		/**
		 * {@inheritDoc}
		 */
		public Color getForeground() {
			if (isEnabled())
				return super.getForeground();
			return getDisplay().getSystemColor(SWT.COLOR_WIDGET_NORMAL_SHADOW);
		}
	}

	/** Checkbox tree viewer for the refactoring history */
	protected final class RefactoringHistoryTreeViewer extends CheckboxTreeViewer {

		/**
		 * Creates a new refactoring history tree viewer.
		 * 
		 * @param parent
		 *            the parent control
		 * @param style
		 *            the style
		 */
		public RefactoringHistoryTreeViewer(final Composite parent, final int style) {
			super(parent, style);
			addCheckStateListener(new ICheckStateListener() {

				/**
				 * {@inheritDoc}
				 */
				public final void checkStateChanged(final CheckStateChangedEvent event) {
					reconcileCheckState(event.getElement(), event.getChecked());
					handleCheckStateChanged();
				}
			});
			addTreeListener(new ITreeViewerListener() {

				/**
				 * {@inheritDoc}
				 */
				public final void treeCollapsed(final TreeExpansionEvent event) {
					// Do nothing
				}

				/**
				 * {@inheritDoc}
				 */
				public final void treeExpanded(final TreeExpansionEvent event) {
					BusyIndicator.showWhile(getDisplay(), new Runnable() {

						public final void run() {
							final Object element= event.getElement();
							if (getGrayed(element)) {
								final RefactoringHistory history= RefactoringHistoryControl.this.getInput();
								if (history != null)
									reconcileCheckState(history);
							} else if (getChecked(element)) {
								setSubTreeGrayed(element, false);
								setSubtreeChecked(element, true);
							}
						}
					});
				}
			});
		}

		/**
		 * Returns the children of the specified element.
		 * 
		 * @param element
		 *            the element
		 * @return the children of the element
		 */
		private Object[] getChildren(final Object element) {
			return ((RefactoringHistoryContentProvider) getContentProvider()).getChildren(element);
		}

		/**
		 * Reconciles the check state of the specified element and dependencies.
		 * 
		 * @param element
		 *            the changed element
		 */
		private void reconcileCheckState(final Object element) {
			final Object[] children= getChildren(element);
			for (int index= 0; index < children.length; index++) {
				reconcileCheckState(children[index]);
			}
			int checkCount= 0;
			final Collection collection= getCoveredDescriptors(element);
			for (final Iterator iterator= collection.iterator(); iterator.hasNext();) {
				final RefactoringDescriptorProxy proxy= (RefactoringDescriptorProxy) iterator.next();
				if (fCheckedDescriptors.contains(proxy))
					checkCount++;
			}
			setElementChecked(element, checkCount > 0);
			setElementGrayed(element, checkCount != 0 && checkCount != collection.size());
		}

		/**
		 * Reconciles the check state of the specified element and dependencies.
		 * 
		 * @param element
		 *            the changed element
		 * @param checked
		 *            <code>true</code> if the element is checked,
		 *            <code>false</code> otherwise
		 */
		private void reconcileCheckState(final Object element, final boolean checked) {
			if (element instanceof RefactoringHistoryEntry) {
				final RefactoringHistoryEntry entry= (RefactoringHistoryEntry) element;
				final RefactoringDescriptorProxy proxy= entry.getDescriptor();
				if (checked)
					fCheckedDescriptors.add(proxy);
				else
					fCheckedDescriptors.remove(proxy);
			} else if (element instanceof RefactoringHistoryNode) {
				final Collection collection= getCoveredDescriptors(element);
				if (checked)
					fCheckedDescriptors.addAll(collection);
				else
					fCheckedDescriptors.removeAll(collection);
			}
			RefactoringHistoryControl.this.reconcileCheckState();
		}

		/**
		 * Determines whether the specified element is checked.
		 * 
		 * @param element
		 *            the element
		 * @param checked
		 *            <code>true</code> to render it checked,
		 *            <code>false</code> otherwise
		 */
		private void setElementChecked(final Object element, final boolean checked) {
			final Widget widget= RefactoringHistoryTreeViewer.this.findItem(element);
			if (widget instanceof TreeItem) {
				final TreeItem item= (TreeItem) widget;
				item.setChecked(checked);
			}
		}

		/**
		 * Determines whether the specified element is grayed.
		 * 
		 * @param element
		 *            the element
		 * @param grayed
		 *            <code>true</code> to render it grayed,
		 *            <code>false</code> otherwise
		 */
		private void setElementGrayed(final Object element, final boolean grayed) {
			final Widget widget= RefactoringHistoryTreeViewer.this.findItem(element);
			if (widget instanceof TreeItem) {
				final TreeItem item= (TreeItem) widget;
				item.setGrayed(grayed);
			}
		}

		/**
		 * Determines whether the subtree of the specified element is rendered
		 * grayed.
		 * 
		 * @param element
		 *            the element specifying the subtree
		 * @param grayed
		 *            <code>true</code> to render the subtree grayed,
		 *            <code>false</code> otherwise
		 */
		private void setSubTreeGrayed(final Object element, final boolean grayed) {
			setElementGrayed(element, grayed);
			final Object[] children= getChildren(element);
			for (int index= 0; index < children.length; index++) {
				setSubTreeGrayed(children[index], grayed);
			}
		}
	}

	/**
	 * The checked refactoring descriptors (element type:
	 * <code>RefactoringDescriptorProxy</code>)
	 */
	private final Set fCheckedDescriptors= new HashSet();

	/** The refactoring history control configuration to use */
	protected final RefactoringHistoryControlConfiguration fControlConfiguration;

	/** The detail field */
	private Text fDetailField= null;

	/** The detail label */
	private Label fDetailLabel= null;

	/** The history pane */
	protected CompareViewerPane fHistoryPane= null;

	/** The history viewer */
	protected TreeViewer fHistoryViewer= null;

	/**
	 * The selected refactoring descriptors (element type:
	 * <code>RefactoringDescriptorProxy</code>)
	 */
	private final Set fSelectedDescriptors= new HashSet();

	/** The selection label, or <code>null</code> */
	private Label fSelectionLabel= null;

	/** The splitter control */
	private Splitter fSplitterControl= null;

	/**
	 * Creates a new refactoring history control.
	 * 
	 * @param parent
	 *            the parent control
	 * @param configuration
	 *            the refactoring history control configuration to use
	 */
	public RefactoringHistoryControl(final Composite parent, final RefactoringHistoryControlConfiguration configuration) {
		super(parent, SWT.NONE);
		Assert.isNotNull(configuration);
		fControlConfiguration= configuration;
	}

	/**
	 * {@inheritDoc}
	 */
	public final void addCheckStateListener(final ICheckStateListener listener) {
		Assert.isNotNull(listener);
		if (fHistoryViewer instanceof RefactoringHistoryTreeViewer) {
			final RefactoringHistoryTreeViewer viewer= (RefactoringHistoryTreeViewer) fHistoryViewer;
			viewer.addCheckStateListener(listener);
		}
	}

	/**
	 * {@inheritDoc}
	 */
	public final void addSelectionChangedListener(final ISelectionChangedListener listener) {
		Assert.isNotNull(listener);
		if (fHistoryViewer != null)
			fHistoryViewer.addSelectionChangedListener(listener);
	}

	/**
	 * Creates the button bar at the bottom of the component.
	 * 
	 * @param parent
	 *            the parent composite
	 */
	protected void createBottomButtonBar(final Composite parent) {
		// Do nothing
	}

	/**
	 * {@inheritDoc}
	 */
	public void createControl() {
		RefactoringCore.getHistoryService().connect();
		GridLayout layout= new GridLayout(getContainerColumns(), false);
		layout.marginHeight= 0;
		layout.marginWidth= 0;
		layout.horizontalSpacing= 0;
		layout.verticalSpacing= 0;
		setLayout(layout);
		setLayoutData(new GridData(GridData.FILL_HORIZONTAL | GridData.FILL_VERTICAL));
		fSplitterControl= new Splitter(this, SWT.VERTICAL);
		fSplitterControl.setLayoutData(new GridData(GridData.FILL_HORIZONTAL | GridData.FILL_VERTICAL));
		createRightButtonBar(this);
		final Composite leftPane= new Composite(fSplitterControl, SWT.NONE);
		layout= new GridLayout();
		layout.marginWidth= 0;
		layout.marginHeight= 2;
		layout.verticalSpacing= 2;
		leftPane.setLayout(layout);
		fHistoryPane= new CompareViewerPane(leftPane, SWT.BORDER | SWT.FLAT);
		fHistoryPane.setTopLeft(new RefactoringHistoryLabel(fHistoryPane, SWT.NONE));
		fHistoryPane.setText(getHistoryPaneText());
		fHistoryPane.setLayoutData(new GridData(GridData.FILL_HORIZONTAL | GridData.FILL_VERTICAL));
		fHistoryViewer= createHistoryViewer(fHistoryPane);
		if (!fControlConfiguration.isTimeDisplayed())
			fHistoryViewer.setAutoExpandLevel(AbstractTreeViewer.ALL_LEVELS);
		fHistoryViewer.getControl().setLayoutData(new GridData(GridData.FILL_HORIZONTAL | GridData.FILL_VERTICAL));
		fHistoryViewer.setUseHashlookup(true);
		fHistoryViewer.setContentProvider(getContentProvider());
		fHistoryViewer.setLabelProvider(getLabelProvider());
		fHistoryViewer.addSelectionChangedListener(new ISelectionChangedListener() {

			public final void selectionChanged(final SelectionChangedEvent event) {
				final ISelection selection= event.getSelection();
				if (selection instanceof IStructuredSelection)
					handleSelectionChanged((IStructuredSelection) selection);
			}
		});
		fHistoryPane.setContent(fHistoryViewer.getControl());
		createToolBar(fHistoryPane);
		createDetailPane(fSplitterControl);
		final MouseAdapter listener= new MouseAdapter() {

			public void mouseDoubleClick(final MouseEvent event) {
				final Control content= fHistoryPane.getContent();
				if (content != null && content.getBounds().contains(event.x, event.y))
					return;
				final Control control= fHistoryPane.getParent().getParent();
				if (control instanceof Splitter)
					((Splitter) control).setMaximizedControl(fHistoryPane.getParent());
			}
		};
		addMouseListener(listener);
		fHistoryPane.getTopLeft().addMouseListener(listener);
		fSplitterControl.setWeights(new int[] { 65, 35});
		createBottomButtonBar(this);
		Dialog.applyDialogFont(this);
	}

	/**
	 * Creates the detail label of the control
	 * 
	 * @param parent
	 *            the parent composite
	 */
	protected void createDetailLabel(final Composite parent) {
		Assert.isNotNull(parent);
		fDetailLabel= new Label(parent, SWT.HORIZONTAL | SWT.LEFT | SWT.WRAP);
		fDetailLabel.setText(RefactoringUIMessages.RefactoringHistoryControl_comment_viewer_label);
		final GridData data= new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING);
		data.horizontalSpan= 1;
		fDetailLabel.setLayoutData(data);
	}

	/**
	 * Creates the detail pane of the control.
	 * 
	 * @param parent
	 *            the parent composite
	 * @return the detail pane
	 */
	protected Composite createDetailPane(final Composite parent) {
		Assert.isNotNull(parent);
		final Composite composite= new Composite(parent, SWT.NONE);
		final GridLayout layout= new GridLayout(getDetailColumns(), true);
		layout.horizontalSpacing= 0;
		layout.marginHeight= 0;
		layout.marginWidth= 0;
		composite.setLayout(layout);
		createDetailLabel(composite);
		createSelectionLabel(composite);
		fDetailField= new Text(composite, SWT.BORDER | SWT.FLAT | SWT.MULTI | SWT.READ_ONLY | SWT.H_SCROLL | SWT.V_SCROLL);
		fDetailField.setText(fControlConfiguration.getCommentCaption());
		final GridData data= new GridData(GridData.FILL_HORIZONTAL | GridData.FILL_VERTICAL);
		data.horizontalSpan= getDetailColumns();
		fDetailField.setLayoutData(data);
		return composite;
	}

	/**
	 * Creates the history viewer of the control.
	 * 
	 * @param parent
	 *            the parent composite
	 * @return the history viewer
	 */
	protected TreeViewer createHistoryViewer(final Composite parent) {
		Assert.isNotNull(parent);
		if (fControlConfiguration.isCheckableViewer())
			return new RefactoringHistoryTreeViewer(parent, SWT.H_SCROLL | SWT.V_SCROLL);
		else
			return new TreeViewer(parent, SWT.H_SCROLL | SWT.V_SCROLL);
	}

	/**
	 * Creates the button bar at the right of the component.
	 * 
	 * @param parent
	 *            the parent composite
	 */
	protected void createRightButtonBar(final Composite parent) {
		// Do nothing
	}

	/**
	 * Creates the selection label of the control
	 * 
	 * @param parent
	 *            the parent composite
	 */
	protected void createSelectionLabel(final Composite parent) {
		Assert.isNotNull(parent);
		fSelectionLabel= new Label(parent, SWT.HORIZONTAL | SWT.RIGHT | SWT.WRAP);
		final GridData data= new GridData(GridData.HORIZONTAL_ALIGN_END);
		data.horizontalSpan= 1;
		fSelectionLabel.setLayoutData(data);
	}

	/**
	 * Creates the toolbar of the control.
	 * 
	 * @param parent
	 *            the parent control
	 */
	protected void createToolBar(final ViewForm parent) {
		// Do nothing
	}

	/**
	 * {@inheritDoc}
	 */
	public final void dispose() {
		RefactoringCore.getHistoryService().disconnect();
		super.dispose();
	}

	/**
	 * {@inheritDoc}
	 */
	public final RefactoringDescriptorProxy[] getCheckedDescriptors() {
		if (fHistoryViewer instanceof RefactoringHistoryTreeViewer)
			return (RefactoringDescriptorProxy[]) fCheckedDescriptors.toArray(new RefactoringDescriptorProxy[fCheckedDescriptors.size()]);
		return getSelectedDescriptors();
	}

	/**
	 * Returns the number of columns to use for the container layout.
	 * 
	 * @return the number of columns
	 */
	protected int getContainerColumns() {
		return 2;
	}

	/**
	 * Returns the content provider to use.
	 * 
	 * @return the content provider
	 */
	protected RefactoringHistoryContentProvider getContentProvider() {
		return fControlConfiguration.getContentProvider();
	}

	/**
	 * {@inheritDoc}
	 */
	public final Control getControl() {
		return this;
	}

	/**
	 * Returns the refactoring descriptors covered by the specified node.
	 * 
	 * @param element
	 *            the refactoring history element
	 * @return the collection of covered descriptors
	 */
	private Collection getCoveredDescriptors(final Object element) {
		Assert.isNotNull(element);
		final Set set= new HashSet();
		getCoveredDescriptors(element, set);
		return set;
	}

	/**
	 * Computes the refactoring descriptors covered by the specified node.
	 * 
	 * @param element
	 *            the refactoring history element
	 * @param set
	 *            the set of refactoring descriptor proxies
	 */
	private void getCoveredDescriptors(final Object element, final Set set) {
		Assert.isNotNull(element);
		Assert.isNotNull(set);
		final RefactoringHistoryContentProvider provider= (RefactoringHistoryContentProvider) fHistoryViewer.getContentProvider();
		if (provider != null) {
			if (element instanceof RefactoringHistoryEntry) {
				final RefactoringHistoryEntry entry= (RefactoringHistoryEntry) element;
				final RefactoringDescriptorProxy proxy= entry.getDescriptor();
				set.add(proxy);
			} else {
				final Object[] children= provider.getChildren(element);
				for (int index= 0; index < children.length; index++) {
					final Object child= children[index];
					if (child instanceof RefactoringHistoryNode)
						getCoveredDescriptors(child, set);
				}
			}
		}
	}

	/**
	 * Returns the number of columns to use for the detail pane layout.
	 * 
	 * @return the number of columns
	 */
	protected int getDetailColumns() {
		return 2;
	}

	/**
	 * Returns the text to be displayed in the history pane.
	 * 
	 * @return the text in the history pane
	 */
	private String getHistoryPaneText() {
		String text= null;
		final IProject project= fControlConfiguration.getProject();
		if (project != null)
			text= Messages.format(fControlConfiguration.getProjectPattern(), BasicElementLabels.getResourceName(project));
		else
			text= fControlConfiguration.getWorkspaceCaption();
		return text;
	}

	/**
	 * Returns the input of the refactoring history control.
	 * 
	 * @return the input, or <code>null</code>
	 */
	public final RefactoringHistory getInput() {
		return (RefactoringHistory) fHistoryViewer.getInput();
	}

	/**
	 * Returns the label provider to use.
	 * 
	 * @return the label provider
	 */
	protected RefactoringHistoryLabelProvider getLabelProvider() {
		return fControlConfiguration.getLabelProvider();
	}

	/**
	 * {@inheritDoc}
	 */
	public final RefactoringDescriptorProxy[] getSelectedDescriptors() {
		return (RefactoringDescriptorProxy[]) fSelectedDescriptors.toArray(new RefactoringDescriptorProxy[fSelectedDescriptors.size()]);
	}

	/**
	 * Handles the check state changed event.
	 */
	protected void handleCheckStateChanged() {
		if (fSelectionLabel != null) {
			final RefactoringHistory history= getInput();
			if (history != null) {
				final int total= history.getDescriptors().length;
				final int checked= fCheckedDescriptors.size();
				if (fSelectionLabel.isEnabled()) {
					if (total > 0 && fControlConfiguration.isCheckableViewer())
						fSelectionLabel.setText(Messages.format(RefactoringUIMessages.RefactoringHistoryControl_selection_pattern, new String[] { String.valueOf(checked), String.valueOf(total)}));
					else
						fSelectionLabel.setText(RefactoringUIMessages.RefactoringHistoryControl_no_selection);
				} else
					fSelectionLabel.setText(""); //$NON-NLS-1$
			}
		}
	}

	/**
	 * Handles the selection changed event.
	 * 
	 * @param selection
	 *            the new selection
	 */
	protected void handleSelectionChanged(final IStructuredSelection selection) {
		Assert.isNotNull(selection);
		fSelectedDescriptors.clear();
		final Object[] elements= selection.toArray();
		for (int index= 0; index < elements.length; index++) {
			final Object element= elements[index];
			if (element instanceof RefactoringHistoryEntry) {
				final RefactoringHistoryEntry entry= (RefactoringHistoryEntry) element;
				final RefactoringDescriptorProxy proxy= entry.getDescriptor();
				fSelectedDescriptors.add(proxy);
			} else if (element instanceof RefactoringHistoryNode) {
				final RefactoringHistoryNode node= (RefactoringHistoryNode) element;
				fSelectedDescriptors.addAll(getCoveredDescriptors(node));
			}
		}
		if (elements.length == 1 && elements[0] instanceof RefactoringHistoryEntry) {
			final RefactoringHistoryEntry entry= (RefactoringHistoryEntry) elements[0];
			final RefactoringDescriptorProxy proxy= entry.getDescriptor();
			final Job job= new UIJob(RefactoringUIMessages.RefactoringHistoryControl_resolving_information) {

				public final IStatus runInUIThread(final IProgressMonitor monitor) {
					final RefactoringDescriptor descriptor= proxy.requestDescriptor(monitor);
					if (descriptor != null) {
						String comment= descriptor.getComment();
						if ("".equals(comment)) //$NON-NLS-1$
							comment= RefactoringUIMessages.RefactoringHistoryControl_no_comment;
						fDetailField.setText(comment);
					}
					return Status.OK_STATUS;
				}
			};
			job.setSystem(true);
			job.schedule();
		} else
			fDetailField.setText(fControlConfiguration.getCommentCaption());
	}

	/**
	 * Reconciles the check state of the control.
	 */
	public void reconcileCheckState() {
		final RefactoringHistory history= getInput();
		if (history != null && fHistoryViewer instanceof RefactoringHistoryTreeViewer)
			((RefactoringHistoryTreeViewer) fHistoryViewer).reconcileCheckState(history);
	}

	/**
	 * Reconciles the selection state of the control.
	 */
	public void reconcileSelectionState() {
		final RefactoringHistoryNode[] nodes= new RefactoringHistoryNode[fSelectedDescriptors.size()];
		int index= 0;
		for (final Iterator iterator= fSelectedDescriptors.iterator(); iterator.hasNext(); index++) {
			final RefactoringDescriptorProxy descriptor= (RefactoringDescriptorProxy) iterator.next();
			nodes[index]= new RefactoringHistoryEntry(null, descriptor);
			fHistoryViewer.expandToLevel(nodes[index], AbstractTreeViewer.ALL_LEVELS);
		}
		fHistoryViewer.setSelection(new StructuredSelection(nodes), true);
	}

	/**
	 * {@inheritDoc}
	 */
	public final void removeCheckStateListener(final ICheckStateListener listener) {
		Assert.isNotNull(listener);
		Assert.isNotNull(listener);
		if (fHistoryViewer instanceof RefactoringHistoryTreeViewer) {
			final RefactoringHistoryTreeViewer viewer= (RefactoringHistoryTreeViewer) fHistoryViewer;
			viewer.removeCheckStateListener(listener);
		}
	}

	/**
	 * {@inheritDoc}
	 */
	public final void removeSelectionChangedListener(final ISelectionChangedListener listener) {
		Assert.isNotNull(listener);
		if (fHistoryViewer != null)
			fHistoryViewer.removeSelectionChangedListener(listener);
	}

	/**
	 * {@inheritDoc}
	 */
	public final void setCheckedDescriptors(final RefactoringDescriptorProxy[] descriptors) {
		Assert.isNotNull(descriptors);
		if (fHistoryViewer instanceof RefactoringHistoryTreeViewer) {
			fCheckedDescriptors.clear();
			fCheckedDescriptors.addAll(Arrays.asList(descriptors));
			final RefactoringHistoryTreeViewer viewer= (RefactoringHistoryTreeViewer) fHistoryViewer;
			final RefactoringHistory history= RefactoringHistoryControl.this.getInput();
			if (history != null)
				viewer.reconcileCheckState(history);
			handleCheckStateChanged();
		} else
			setSelectedDescriptors(descriptors);
	}

	/**
	 * Sets the expanded state of the viewer.
	 */
	protected void setExpandedState() {
		final RefactoringHistoryContentProvider provider= (RefactoringHistoryContentProvider) fHistoryViewer.getContentProvider();
		if (provider != null) {
			handleCheckStateChanged();
			final Object[] roots= provider.getRootElements();
			if (roots != null) {
				for (int index= 0; index < roots.length; index++) {
					if (!(roots[index] instanceof RefactoringHistoryEntry)) {
						fHistoryViewer.setExpandedState(roots[index], true);
						return;
					}
				}
			}
		}
	}

	/**
	 * Sets the enablement of the detail pane.
	 */
	protected void setHistoryControlEnablement() {
		boolean enable= false;
		final RefactoringHistory history= (RefactoringHistory) fHistoryViewer.getInput();
		if (history != null) {
			final RefactoringDescriptorProxy[] proxies= history.getDescriptors();
			if (proxies.length > 0)
				enable= true;
		}
		if (fDetailField != null)
			fDetailField.setEnabled(enable);
		if (fDetailLabel != null)
			fDetailLabel.setEnabled(enable);
		if (fHistoryPane != null)
			fHistoryPane.setEnabled(enable);
		if (fSelectionLabel != null)
			fSelectionLabel.setEnabled(enable);
		if (enable) {
			fDetailField.setText(fControlConfiguration.getCommentCaption());
			if (fSelectionLabel != null)
				fSelectionLabel.setText(RefactoringUIMessages.RefactoringHistoryControl_no_selection);
		} else {
			fDetailField.setText(""); //$NON-NLS-1$
			if (fSelectionLabel != null)
				fSelectionLabel.setText(""); //$NON-NLS-1$
		}
	}

	/**
	 * {@inheritDoc}
	 */
	public void setInput(final RefactoringHistory history) {
		fHistoryViewer.setInput(history);
		fSelectedDescriptors.clear();
		fCheckedDescriptors.clear();
		if (history != null) {
			final RefactoringHistoryContentProvider provider= (RefactoringHistoryContentProvider) fHistoryViewer.getContentProvider();
			if (provider != null) {
				provider.inputChanged(fHistoryViewer, null, history);
				setHistoryControlEnablement();
			}
		}
	}

	/**
	 * {@inheritDoc}
	 */
	public final void setSelectedDescriptors(final RefactoringDescriptorProxy[] descriptors) {
		Assert.isNotNull(descriptors);
		if (fHistoryViewer != null) {
			fSelectedDescriptors.clear();
			fSelectedDescriptors.addAll(Arrays.asList(descriptors));
			final RefactoringHistoryNode[] nodes= new RefactoringHistoryNode[descriptors.length];
			for (int index= 0; index < descriptors.length; index++)
				nodes[index]= new RefactoringHistoryEntry(null, descriptors[index]);
			fHistoryViewer.setSelection(new StructuredSelection(nodes));
		}
	}
}