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

package org.eclipse.birt.report.designer.ui.ide.wizards;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;

import org.eclipse.birt.report.designer.internal.ui.util.IHelpContextIds;
import org.eclipse.birt.report.designer.internal.ui.util.UIUtil;
import org.eclipse.birt.report.designer.nls.Messages;
import org.eclipse.birt.report.designer.ui.ReportPlugin;
import org.eclipse.birt.report.model.api.LibraryHandle;
import org.eclipse.birt.report.model.api.ModuleHandle;
import org.eclipse.birt.report.model.api.ReportDesignHandle;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerSorter;
import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.model.WorkbenchLabelProvider;
import org.eclipse.ui.part.DrillDownComposite;

/**
 * 
 */

public class WizardSaveAsPage extends WizardPage
{

	private static String EXTENSIONS = ".rptdesign";
	static
	{
		List extensionList = ReportPlugin.getDefault( )
				.getReportExtensionNameList( );
		if ( !extensionList.isEmpty( ) )
		{
			EXTENSIONS = "." + extensionList.get( 0 );
		}

		for ( int i = 1; i < extensionList.size( ); i++ )
		{
			EXTENSIONS += ", ." + extensionList.get( i );
		}
	}
	private static final String WRONG_EXTENSION = MessageFormat.format( Messages.getString( "WizardReportSettingPage.Error.ReportorTemplate" ),
			new String[]{
				EXTENSIONS
			} );
	private ResourceAndContainerGroup resourceGroup;
	private IResource originalFile;
	private String originalName;
	private ModuleHandle model;

	public WizardSaveAsPage( String pageName )
	{
		super( pageName );
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.jface.wizard.WizardPage#canFlipToNextPage()
	 */
	public boolean canFlipToNextPage( )
	{
		if ( validatePage( ) == false )
		{
			return false;
		}

		if ( resourceGroup.getResource( ).endsWith( ".rpttemplate" ) )
		{
			return true;
		}

		return false;

	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.jface.dialogs.IDialogPage#createControl(org.eclipse.swt.widgets.Composite)
	 */
	public void createControl( Composite parent )
	{

		// create a composite with standard margins and spacing
		Composite composite = new Composite( parent, SWT.NONE );
		GridLayout layout = new GridLayout( );
		layout.marginHeight = convertVerticalDLUsToPixels( IDialogConstants.VERTICAL_MARGIN );
		layout.marginWidth = convertHorizontalDLUsToPixels( IDialogConstants.HORIZONTAL_MARGIN );
		layout.verticalSpacing = convertVerticalDLUsToPixels( IDialogConstants.VERTICAL_SPACING );
		layout.horizontalSpacing = convertHorizontalDLUsToPixels( IDialogConstants.HORIZONTAL_SPACING );
		composite.setLayout( layout );
		composite.setLayoutData( new GridData( GridData.FILL_BOTH ) );
		composite.setFont( parent.getFont( ) );

		Listener listener = new Listener( ) {

			public void handleEvent( Event event )
			{
				validatePage( );
				try
				{
					getContainer( ).updateButtons( );
				}
				catch ( Throwable e )
				{
					// ignore
				}
			}
		};

		resourceGroup = new ResourceAndContainerGroup( composite,
				listener,
				Messages.getString( "WizardSaveAsPage.FileLabel" ), //$NON-NLS-1$
				"file", //$NON-NLS-1$
				false,
				200 );
		resourceGroup.setAllowExistingResources( true );

		setControl( composite );

		initializeControls( );

		UIUtil.bindHelp( getControl( ), IHelpContextIds.SAVE_AS_WIZARD_ID );
	}

	/**
	 * Initializes the controls of this dialog.
	 */
	private void initializeControls( )
	{
		if ( originalFile != null )
		{
			resourceGroup.setContainerFullPath( originalFile.getParent( )
					.getFullPath( ) );
			resourceGroup.setResource( originalFile.getName( ) );
		}
		else if ( originalName != null )
			resourceGroup.setResource( originalName );
	}

	/**
	 * Sets the original file to use.
	 * 
	 * @param originalFile
	 *            the original file
	 */
	public void setOriginalFile( IFile originalFile )
	{
		this.originalFile = originalFile;
	}

	/**
	 * Set the original file name to use. Used instead of
	 * <code>setOriginalFile</code> when the original resource is not an
	 * IFile. Must be called before <code>create</code>.
	 * 
	 * @param originalName
	 *            default file name
	 */
	public void setOriginalName( String originalName )
	{
		this.originalName = originalName;
	}

	/**
	 * Sets the model to use.
	 * 
	 * @param ModuleHandle
	 *            the original file
	 */
	public void setModel( ModuleHandle model )
	{
		this.model = model;
	}

	/**
	 * Returns whether this page's visual components all contain valid values.
	 * 
	 * @return <code>true</code> if valid, and <code>false</code> otherwise
	 */
	public boolean validatePage( )
	{
		setErrorMessage( null );
		if ( !resourceGroup.areAllValuesValid( ) )
		{
			if ( !resourceGroup.getResource( ).equals( "" ) ) // if blank name
				// //$NON-NLS-1$
				// then fail
				// silently//$NON-NLS-1$
				setErrorMessage( resourceGroup.getProblemMessage( ) );
			return false;
		}
		if ( resourceGroup.getResource( ) != null
				&& model instanceof LibraryHandle )
		{
			if ( !resourceGroup.getResource( ).endsWith( ".rptlibrary" ) )
			{
				setErrorMessage( Messages.getString( "WizardReportSettingPage.Error.Library" ) );
				return false;
			}
		}

		if ( resourceGroup.getResource( ) != null
				&& model instanceof ReportDesignHandle )
		{
			if ( !( ReportPlugin.getDefault( )
					.isReportDesignFile( resourceGroup.getResource( ) ) || resourceGroup.getResource( )
					.endsWith( ".rpttemplate" ) ) )
			{
				setErrorMessage( WRONG_EXTENSION );
				return false;
			}
		}

		return true;
	}

	/**
	 * Get the saving path
	 * 
	 * @return the saving path
	 */
	public IPath getResult( )
	{

		IPath path = resourceGroup.getContainerFullPath( )
				.append( resourceGroup.getResource( ) );

		// If the user does not supply a file extension and if the save
		// as dialog was provided a default file name append the extension
		// of the default filename to the new name
		if ( path.getFileExtension( ) == null )
		{
			if ( originalFile != null
					&& originalFile.getFileExtension( ) != null )
				path = path.addFileExtension( originalFile.getFileExtension( ) );
			else if ( originalName != null )
			{
				int pos = originalName.lastIndexOf( '.' );
				if ( ++pos > 0 && pos < originalName.length( ) )
					path = path.addFileExtension( originalName.substring( pos ) );
			}
		}

		// If the path already exists then confirm overwrite.
		IFile file = ResourcesPlugin.getWorkspace( ).getRoot( ).getFile( path );

		if ( file.exists( ) )
		{
			String[] buttons = new String[]{
					IDialogConstants.YES_LABEL,
					IDialogConstants.NO_LABEL,
					IDialogConstants.CANCEL_LABEL
			};
			String question = Messages.getFormattedString( "WizardSaveAsPage.OverwriteQuestion", //$NON-NLS-1$
					new Object[]{
						path.toOSString( )
					} );
			MessageDialog d = new MessageDialog( getShell( ),
					Messages.getString( "WizardSaveAsPage.Question" ), //$NON-NLS-1$
					null,
					question,
					MessageDialog.QUESTION,
					buttons,
					0 );
			int overwrite = d.open( );
			switch ( overwrite )
			{
				case 0 : // Yes
					break;
				case 1 : // No
					return null;
				case 2 : // Cancel
				default :
					return null;
			}
		}

		return path;
	}
}

// the follow classes are copy from package org.eclipse.ui.internal.ide.misc

class ResourceAndContainerGroup implements Listener
{

	// problem identifiers
	public static final int PROBLEM_NONE = 0;

	public static final int PROBLEM_RESOURCE_EMPTY = 1;

	public static final int PROBLEM_RESOURCE_EXIST = 2;

	public static final int PROBLEM_RESOURCE_CONTAINS_SEPARATOR = 3;

	public static final int PROBLEM_PATH_INVALID = 4;

	public static final int PROBLEM_CONTAINER_EMPTY = 5;

	public static final int PROBLEM_PROJECT_DOES_NOT_EXIST = 6;

	public static final int PROBLEM_NAME_INVALID = 7;

	public static final int PROBLEM_PATH_OCCUPIED = 8;

	// the client to notify of changes
	private Listener client;

	// whether to allow existing resources
	private boolean allowExistingResources = false;

	// resource type (file, folder, project)
	private String resourceType = "file"; //$NON-NLS-1$

	// show closed projects in the tree, by default
	private boolean showClosedProjects = true;

	// problem indicator
	private String problemMessage = "";//$NON-NLS-1$

	private int problemType = PROBLEM_NONE;

	// widgets
	private ContainerSelectionGroup containerGroup;

	private Text resourceNameField;

	// constants
	private static final int SIZING_TEXT_FIELD_WIDTH = 250;

	/**
	 * Create an instance of the group to allow the user to enter/select a
	 * container and specify a resource name.
	 * 
	 * @param parent
	 *            composite widget to parent the group
	 * @param client
	 *            object interested in changes to the group's fields value
	 * @param resourceFieldLabel
	 *            label to use in front of the resource name field
	 * @param resourceType
	 *            one word, in lowercase, to describe the resource to the user
	 *            (file, folder, project)
	 */
	public ResourceAndContainerGroup( Composite parent, Listener client,
			String resourceFieldLabel, String resourceType )
	{
		this( parent, client, resourceFieldLabel, resourceType, true );
	}

	/**
	 * Create an instance of the group to allow the user to enter/select a
	 * container and specify a resource name.
	 * 
	 * @param parent
	 *            composite widget to parent the group
	 * @param client
	 *            object interested in changes to the group's fields value
	 * @param resourceFieldLabel
	 *            label to use in front of the resource name field
	 * @param resourceType
	 *            one word, in lowercase, to describe the resource to the user
	 *            (file, folder, project)
	 * @param showClosedProjects
	 *            whether or not to show closed projects
	 */
	public ResourceAndContainerGroup( Composite parent, Listener client,
			String resourceFieldLabel, String resourceType,
			boolean showClosedProjects )
	{
		this( parent,
				client,
				resourceFieldLabel,
				resourceType,
				showClosedProjects,
				SWT.DEFAULT );
	}

	/**
	 * Create an instance of the group to allow the user to enter/select a
	 * container and specify a resource name.
	 * 
	 * @param parent
	 *            composite widget to parent the group
	 * @param client
	 *            object interested in changes to the group's fields value
	 * @param resourceFieldLabel
	 *            label to use in front of the resource name field
	 * @param resourceType
	 *            one word, in lowercase, to describe the resource to the user
	 *            (file, folder, project)
	 * @param showClosedProjects
	 *            whether or not to show closed projects
	 * @param heightHint
	 *            height hint for the container selection widget group
	 */
	public ResourceAndContainerGroup( Composite parent, Listener client,
			String resourceFieldLabel, String resourceType,
			boolean showClosedProjects, int heightHint )
	{
		super( );
		this.resourceType = resourceType;
		this.showClosedProjects = showClosedProjects;
		createContents( parent, resourceFieldLabel, heightHint );
		this.client = client;
	}

	/**
	 * Returns a boolean indicating whether all controls in this group contain
	 * valid values.
	 * 
	 * @return boolean
	 */
	public boolean areAllValuesValid( )
	{
		return problemType == PROBLEM_NONE;
	}

	/**
	 * Creates this object's visual components.
	 * 
	 * @param parent
	 *            org.eclipse.swt.widgets.Composite
	 * @param heightHint
	 *            height hint for the container selection widget group
	 */
	protected void createContents( Composite parent,
			String resourceLabelString, int heightHint )
	{

		Font font = parent.getFont( );
		// server name group
		Composite composite = new Composite( parent, SWT.NONE );
		GridLayout layout = new GridLayout( );
		layout.marginWidth = 0;
		layout.marginHeight = 0;
		composite.setLayout( layout );
		composite.setLayoutData( new GridData( SWT.FILL, SWT.FILL, true, true ) );
		composite.setFont( font );

		// container group
		if ( heightHint == SWT.DEFAULT )
			containerGroup = new ContainerSelectionGroup( composite,
					this,
					true,
					null,
					showClosedProjects );
		else
			containerGroup = new ContainerSelectionGroup( composite,
					this,
					true,
					null,
					showClosedProjects,
					heightHint );

		// resource name group
		Composite nameGroup = new Composite( composite, SWT.NONE );
		layout = new GridLayout( );
		layout.numColumns = 2;
		layout.marginWidth = 0;
		nameGroup.setLayout( layout );
		nameGroup.setLayoutData( new GridData( GridData.HORIZONTAL_ALIGN_FILL
				| GridData.GRAB_HORIZONTAL ) );
		nameGroup.setFont( font );

		Label label = new Label( nameGroup, SWT.NONE );
		label.setText( resourceLabelString );
		label.setFont( font );

		// resource name entry field
		resourceNameField = new Text( nameGroup, SWT.BORDER );
		resourceNameField.addListener( SWT.Modify, this );
		GridData data = new GridData( GridData.HORIZONTAL_ALIGN_FILL
				| GridData.GRAB_HORIZONTAL );
		data.widthHint = SIZING_TEXT_FIELD_WIDTH;
		resourceNameField.setLayoutData( data );
		resourceNameField.setFont( font );

		validateControls( );
	}

	/**
	 * Returns the path of the currently selected container or null if no
	 * container has been selected. Note that the container may not exist yet if
	 * the user entered a new container name in the field.
	 */
	public IPath getContainerFullPath( )
	{
		return containerGroup.getContainerFullPath( );
	}

	/**
	 * Returns an error message indicating the current problem with the value of
	 * a control in the group, or an empty message if all controls in the group
	 * contain valid values.
	 * 
	 * @return java.lang.String
	 */
	public String getProblemMessage( )
	{
		return problemMessage;
	}

	/**
	 * Returns the type of problem with the value of a control in the group.
	 * 
	 * @return one of the PROBLEM_* constants
	 */
	public int getProblemType( )
	{
		return problemType;
	}

	/**
	 * Returns a string that is the path of the currently selected container.
	 * Returns an empty string if no container has been selected.
	 */
	public String getResource( )
	{
		return resourceNameField.getText( );
	}

	/**
	 * Handles events for all controls in the group.
	 * 
	 * @param e
	 *            org.eclipse.swt.widgets.Event
	 */
	public void handleEvent( Event e )
	{
		validateControls( );
		if ( client != null )
		{
			client.handleEvent( e );
		}
	}

	/**
	 * Sets the flag indicating whether existing resources are permitted.
	 */
	public void setAllowExistingResources( boolean value )
	{
		allowExistingResources = value;
	}

	/**
	 * Sets the value of this page's container.
	 * 
	 * @param path
	 *            Full path to the container.
	 */
	public void setContainerFullPath( IPath path )
	{
		IResource initial = ResourcesPlugin.getWorkspace( )
				.getRoot( )
				.findMember( path );
		if ( initial != null )
		{
			if ( !( initial instanceof IContainer ) )
			{
				initial = initial.getParent( );
			}
			containerGroup.setSelectedContainer( (IContainer) initial );
		}
		validateControls( );
	}

	/**
	 * Gives focus to the resource name field and selects its contents
	 */
	public void setFocus( )
	{
		// select the whole resource name.
		resourceNameField.setSelection( 0, resourceNameField.getText( )
				.length( ) );
		resourceNameField.setFocus( );
	}

	/**
	 * Sets the value of this page's resource name.
	 * 
	 * @param value
	 *            new value
	 */
	public void setResource( String value )
	{
		resourceNameField.setText( value );
		validateControls( );
	}

	/**
	 * Returns a <code>boolean</code> indicating whether a container name
	 * represents a valid container resource in the workbench. An error message
	 * is stored for future reference if the name does not represent a valid
	 * container.
	 * 
	 * @return <code>boolean</code> indicating validity of the container name
	 */
	protected boolean validateContainer( )
	{
		IPath path = containerGroup.getContainerFullPath( );
		if ( path == null )
		{
			problemType = PROBLEM_CONTAINER_EMPTY;
			problemMessage = Messages.getString( "WizardSaveAsPage.FolderEmpty" ); //$NON-NLS-1$
			return false;
		}
		IWorkspace workspace = ResourcesPlugin.getWorkspace( );
		String projectName = path.segment( 0 );
		if ( projectName == null
				|| !workspace.getRoot( ).getProject( projectName ).exists( ) )
		{
			problemType = PROBLEM_PROJECT_DOES_NOT_EXIST;
			problemMessage = Messages.getString( "WizardSaveAsPage.NoProject" ); //$NON-NLS-1$
			return false;
		}
		// path is invalid if any prefix is occupied by a file
		IWorkspaceRoot root = workspace.getRoot( );
		while ( path.segmentCount( ) > 1 )
		{
			if ( root.getFile( path ).exists( ) )
			{
				problemType = PROBLEM_PATH_OCCUPIED;
				problemMessage = Messages.getFormattedString( "WizardSaveAsPage.PathOccupied", new Object[]{path.makeRelative( )} ); //$NON-NLS-1$
				return false;
			}
			path = path.removeLastSegments( 1 );
		}
		return true;
	}

	/**
	 * Validates the values for each of the group's controls. If an invalid
	 * value is found then a descriptive error message is stored for later
	 * reference. Returns a boolean indicating the validity of all of the
	 * controls in the group.
	 */
	protected boolean validateControls( )
	{
		// don't attempt to validate controls until they have been created
		if ( containerGroup == null )
		{
			return false;
		}
		problemType = PROBLEM_NONE;
		problemMessage = "";//$NON-NLS-1$

		if ( !validateContainer( ) || !validateResourceName( ) )
			return false;

		IPath path = containerGroup.getContainerFullPath( )
				.append( resourceNameField.getText( ) );
		return validateFullResourcePath( path );
	}

	/**
	 * Returns a <code>boolean</code> indicating whether the specified
	 * resource path represents a valid new resource in the workbench. An error
	 * message is stored for future reference if the path does not represent a
	 * valid new resource path.
	 * 
	 * @param resourcePath
	 *            the path to validate
	 * @return <code>boolean</code> indicating validity of the resource path
	 */
	protected boolean validateFullResourcePath( IPath resourcePath )
	{
		IWorkspace workspace = ResourcesPlugin.getWorkspace( );

		IStatus result = workspace.validatePath( resourcePath.toString( ),
				IResource.FOLDER );
		if ( !result.isOK( ) )
		{
			problemType = PROBLEM_PATH_INVALID;
			problemMessage = result.getMessage( );
			return false;
		}

		if ( !allowExistingResources
				&& ( workspace.getRoot( ).getFolder( resourcePath ).exists( ) || workspace.getRoot( )
						.getFile( resourcePath )
						.exists( ) ) )
		{
			problemType = PROBLEM_RESOURCE_EXIST;
			problemMessage = Messages.getString( "WizardSaveAsPage.NameExists" ); //$NON-NLS-1$
			return false;
		}
		return true;
	}

	/**
	 * Returns a <code>boolean</code> indicating whether the resource name
	 * rep- resents a valid resource name in the workbench. An error message is
	 * stored for future reference if the name does not represent a valid
	 * resource name.
	 * 
	 * @return <code>boolean</code> indicating validity of the resource name
	 */
	protected boolean validateResourceName( )
	{
		String resourceName = resourceNameField.getText( );

		if ( resourceName.equals( "" ) ) {//$NON-NLS-1$
			problemType = PROBLEM_RESOURCE_EMPTY;
			problemMessage = Messages.getFormattedString( "WizardSaveAsPage.EmptyName", new Object[]{resourceType} ); //$NON-NLS-1$
			return false;
		}

		if ( !( new Path( "" ) ).isValidPath( resourceName ) ) { //$NON-NLS-1$
			problemType = PROBLEM_NAME_INVALID;
			problemMessage = Messages.getFormattedString( "WizardSaveAsPage.InvalidFileName", new Object[]{resourceName} ); //$NON-NLS-1$
			return false;
		}
		return true;
	}

}

class ContainerSelectionGroup extends Composite
{

	// The listener to notify of events
	private Listener listener;

	// Enable user to type in new container name
	private boolean allowNewContainerName = true;

	// show all projects by default
	private boolean showClosedProjects = true;

	// Last selection made by user
	private IContainer selectedContainer;

	// handle on parts
	private Text containerNameField;

	TreeViewer treeViewer;

	// the message to display at the top of this dialog
	private static final String DEFAULT_MSG_NEW_ALLOWED = Messages.getString( "WizardSaveAsPage.ContainerGroup" ); //$NON-NLS-1$

	private static final String DEFAULT_MSG_SELECT_ONLY = Messages.getString( "WizardSaveAsPage.SelectFolder" ); //$NON-NLS-1$

	// sizing constants
	private static final int SIZING_SELECTION_PANE_WIDTH = 320;

	private static final int SIZING_SELECTION_PANE_HEIGHT = 300;

	/**
	 * Creates a new instance of the widget.
	 * 
	 * @param parent
	 *            The parent widget of the group.
	 * @param listener
	 *            A listener to forward events to. Can be null if no listener is
	 *            required.
	 * @param allowNewContainerName
	 *            Enable the user to type in a new container name instead of
	 *            just selecting from the existing ones.
	 */
	public ContainerSelectionGroup( Composite parent, Listener listener,
			boolean allowNewContainerName )
	{
		this( parent, listener, allowNewContainerName, null );
	}

	/**
	 * Creates a new instance of the widget.
	 * 
	 * @param parent
	 *            The parent widget of the group.
	 * @param listener
	 *            A listener to forward events to. Can be null if no listener is
	 *            required.
	 * @param allowNewContainerName
	 *            Enable the user to type in a new container name instead of
	 *            just selecting from the existing ones.
	 * @param message
	 *            The text to present to the user.
	 */
	public ContainerSelectionGroup( Composite parent, Listener listener,
			boolean allowNewContainerName, String message )
	{
		this( parent, listener, allowNewContainerName, message, true );
	}

	/**
	 * Creates a new instance of the widget.
	 * 
	 * @param parent
	 *            The parent widget of the group.
	 * @param listener
	 *            A listener to forward events to. Can be null if no listener is
	 *            required.
	 * @param allowNewContainerName
	 *            Enable the user to type in a new container name instead of
	 *            just selecting from the existing ones.
	 * @param message
	 *            The text to present to the user.
	 * @param showClosedProjects
	 *            Whether or not to show closed projects.
	 */
	public ContainerSelectionGroup( Composite parent, Listener listener,
			boolean allowNewContainerName, String message,
			boolean showClosedProjects )
	{
		this( parent,
				listener,
				allowNewContainerName,
				message,
				showClosedProjects,
				SIZING_SELECTION_PANE_HEIGHT );
	}

	/**
	 * Creates a new instance of the widget.
	 * 
	 * @param parent
	 *            The parent widget of the group.
	 * @param listener
	 *            A listener to forward events to. Can be null if no listener is
	 *            required.
	 * @param allowNewContainerName
	 *            Enable the user to type in a new container name instead of
	 *            just selecting from the existing ones.
	 * @param message
	 *            The text to present to the user.
	 * @param showClosedProjects
	 *            Whether or not to show closed projects.
	 * @param heightHint
	 *            height hint for the drill down composite
	 */
	public ContainerSelectionGroup( Composite parent, Listener listener,
			boolean allowNewContainerName, String message,
			boolean showClosedProjects, int heightHint )
	{
		super( parent, SWT.NONE );
		this.listener = listener;
		this.allowNewContainerName = allowNewContainerName;
		this.showClosedProjects = showClosedProjects;
		if ( message != null )
			createContents( message, heightHint );
		else if ( allowNewContainerName )
			createContents( DEFAULT_MSG_NEW_ALLOWED, heightHint );
		else
			createContents( DEFAULT_MSG_SELECT_ONLY, heightHint );
	}

	/**
	 * The container selection has changed in the tree view. Update the
	 * container name field value and notify all listeners.
	 */
	public void containerSelectionChanged( IContainer container )
	{
		selectedContainer = container;

		if ( allowNewContainerName )
		{
			if ( container == null )
				containerNameField.setText( "" );//$NON-NLS-1$
			else
				containerNameField.setText( container.getFullPath( )
						.makeRelative( )
						.toString( ) );
		}

		// fire an event so the parent can update its controls
		if ( listener != null )
		{
			Event changeEvent = new Event( );
			changeEvent.type = SWT.Selection;
			changeEvent.widget = this;
			listener.handleEvent( changeEvent );
		}
	}

	/**
	 * Creates the contents of the composite.
	 */
	public void createContents( String message )
	{
		createContents( message, SIZING_SELECTION_PANE_HEIGHT );
	}

	/**
	 * Creates the contents of the composite.
	 * 
	 * @param heightHint
	 *            height hint for the drill down composite
	 */
	public void createContents( String message, int heightHint )
	{
		GridLayout layout = new GridLayout( );
		layout.marginWidth = 0;
		setLayout( layout );
		setLayoutData( new GridData( SWT.FILL, SWT.FILL, true, true ) );

		Label label = new Label( this, SWT.WRAP );
		label.setText( message );
		label.setFont( this.getFont( ) );

		if ( allowNewContainerName )
		{
			containerNameField = new Text( this, SWT.SINGLE | SWT.BORDER );
			containerNameField.setLayoutData( new GridData( GridData.FILL_HORIZONTAL ) );
			containerNameField.addListener( SWT.Modify, listener );
			containerNameField.setFont( this.getFont( ) );
		}
		else
		{
			// filler...
			new Label( this, SWT.NONE );
		}

		createTreeViewer( heightHint );
		Dialog.applyDialogFont( this );
	}

	/**
	 * Returns a new drill down viewer for this dialog.
	 * 
	 * @param heightHint
	 *            height hint for the drill down composite
	 * @return a new drill down viewer
	 */
	protected void createTreeViewer( int heightHint )
	{
		// Create drill down.
		DrillDownComposite drillDown = new DrillDownComposite( this, SWT.BORDER );
		GridData spec = new GridData( SWT.FILL, SWT.FILL, true, true );
		spec.widthHint = SIZING_SELECTION_PANE_WIDTH;
		spec.heightHint = heightHint;
		drillDown.setLayoutData( spec );

		// Create tree viewer inside drill down.
		treeViewer = new TreeViewer( drillDown, SWT.NONE );
		drillDown.setChildTree( treeViewer );
		ContainerContentProvider cp = new ContainerContentProvider( );
		cp.showClosedProjects( showClosedProjects );
		treeViewer.setContentProvider( cp );
		treeViewer.setLabelProvider( WorkbenchLabelProvider.getDecoratingWorkbenchLabelProvider( ) );
		treeViewer.setSorter( new ViewerSorter( ) );
		treeViewer.addSelectionChangedListener( new ISelectionChangedListener( ) {

			public void selectionChanged( SelectionChangedEvent event )
			{
				IStructuredSelection selection = (IStructuredSelection) event.getSelection( );
				containerSelectionChanged( (IContainer) selection.getFirstElement( ) ); // allow
				// null
			}
		} );
		treeViewer.addDoubleClickListener( new IDoubleClickListener( ) {

			public void doubleClick( DoubleClickEvent event )
			{
				ISelection selection = event.getSelection( );
				if ( selection instanceof IStructuredSelection )
				{
					Object item = ( (IStructuredSelection) selection ).getFirstElement( );
					if ( treeViewer.getExpandedState( item ) )
						treeViewer.collapseToLevel( item, 1 );
					else
						treeViewer.expandToLevel( item, 1 );
				}
			}
		} );

		// This has to be done after the viewer has been laid out
		treeViewer.setInput( ResourcesPlugin.getWorkspace( ) );
	}

	/**
	 * Returns the currently entered container name. Null if the field is empty.
	 * Note that the container may not exist yet if the user entered a new
	 * container name in the field.
	 */
	public IPath getContainerFullPath( )
	{
		if ( allowNewContainerName )
		{
			String pathName = containerNameField.getText( );
			if ( pathName == null || pathName.length( ) < 1 )
				return null;
			else
				// The user may not have made this absolute so do it for them
				return ( new Path( pathName ) ).makeAbsolute( );
		}
		else
		{
			if ( selectedContainer == null )
				return null;
			else
				return selectedContainer.getFullPath( );
		}
	}

	/**
	 * Gives focus to one of the widgets in the group, as determined by the
	 * group.
	 */
	public void setInitialFocus( )
	{
		if ( allowNewContainerName )
			containerNameField.setFocus( );
		else
			treeViewer.getTree( ).setFocus( );
	}

	/**
	 * Sets the selected existing container.
	 */
	public void setSelectedContainer( IContainer container )
	{
		selectedContainer = container;

		// expand to and select the specified container
		List itemsToExpand = new ArrayList( );
		IContainer parent = container.getParent( );
		while ( parent != null )
		{
			itemsToExpand.add( 0, parent );
			parent = parent.getParent( );
		}
		treeViewer.setExpandedElements( itemsToExpand.toArray( ) );
		treeViewer.setSelection( new StructuredSelection( container ), true );
	}
}

class ContainerContentProvider implements ITreeContentProvider
{

	private boolean showClosedProjects = true;

	/**
	 * Creates a new ContainerContentProvider.
	 */
	public ContainerContentProvider( )
	{
	}

	/**
	 * The visual part that is using this content provider is about to be
	 * disposed. Deallocate all allocated SWT resources.
	 */
	public void dispose( )
	{
	}

	/*
	 * @see org.eclipse.jface.viewers.ITreeContentProvider#getChildren(java.lang.Object)
	 */
	public Object[] getChildren( Object element )
	{
		if ( element instanceof IWorkspace )
		{
			// check if closed projects should be shown
			IProject[] allProjects = ( (IWorkspace) element ).getRoot( )
					.getProjects( );
			if ( showClosedProjects )
				return allProjects;

			ArrayList accessibleProjects = new ArrayList( );
			for ( int i = 0; i < allProjects.length; i++ )
			{
				if ( allProjects[i].isOpen( ) )
				{
					accessibleProjects.add( allProjects[i] );
				}
			}
			return accessibleProjects.toArray( );
		}
		else if ( element instanceof IContainer )
		{
			IContainer container = (IContainer) element;
			if ( container.isAccessible( ) )
			{
				try
				{
					List children = new ArrayList( );
					IResource[] members = container.members( );
					for ( int i = 0; i < members.length; i++ )
					{
						if ( members[i].getType( ) != IResource.FILE )
						{
							children.add( members[i] );
						}
					}
					return children.toArray( );
				}
				catch ( CoreException e )
				{
					// this should never happen because we call #isAccessible
					// before invoking #members
				}
			}
		}
		return new Object[0];
	}

	/*
	 * @see org.eclipse.jface.viewers.IStructuredContentProvider#getElements(java.lang.Object)
	 */
	public Object[] getElements( Object element )
	{
		return getChildren( element );
	}

	/*
	 * @see org.eclipse.jface.viewers.ITreeContentProvider#getParent(java.lang.Object)
	 */
	public Object getParent( Object element )
	{
		if ( element instanceof IResource )
			return ( (IResource) element ).getParent( );
		return null;
	}

	/*
	 * @see org.eclipse.jface.viewers.ITreeContentProvider#hasChildren(java.lang.Object)
	 */
	public boolean hasChildren( Object element )
	{
		return getChildren( element ).length > 0;
	}

	/*
	 * @see org.eclipse.jface.viewers.IContentProvider#inputChanged
	 */
	public void inputChanged( Viewer viewer, Object oldInput, Object newInput )
	{
	}

	/**
	 * Specify whether or not to show closed projects in the tree viewer.
	 * Default is to show closed projects.
	 * 
	 * @param show
	 *            boolean if false, do not show closed projects in the tree
	 */
	public void showClosedProjects( boolean show )
	{
		showClosedProjects = show;
	}

}
