content: /*******************************************************************************
   * Copyright (c) 2000, 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
   *     Rodrigo Kumpera <kumpera AT gmail.com> - bug 95232
   *     
   *******************************************************************************/
  package org.eclipse.wst.jsdt.internal.ui.workingsets;
  
  import java.util.ArrayList;
  import java.util.HashSet;
  import java.util.Iterator;
  import java.util.List;
  
  import org.eclipse.core.resources.IContainer;
  import org.eclipse.core.resources.IProject;
  import org.eclipse.core.resources.IResource;
  import org.eclipse.core.resources.ResourcesPlugin;
  import org.eclipse.core.runtime.Assert;
  import org.eclipse.core.runtime.IAdaptable;
  import org.eclipse.jface.dialogs.Dialog;
  import org.eclipse.jface.viewers.CheckStateChangedEvent;
  import org.eclipse.jface.viewers.CheckboxTreeViewer;
  import org.eclipse.jface.viewers.ICheckStateListener;
  import org.eclipse.jface.viewers.ITreeContentProvider;
  import org.eclipse.jface.viewers.ITreeViewerListener;
  import org.eclipse.jface.viewers.TreeExpansionEvent;
  import org.eclipse.jface.wizard.WizardPage;
  import org.eclipse.swt.SWT;
  import org.eclipse.swt.custom.BusyIndicator;
  import org.eclipse.swt.events.ModifyEvent;
  import org.eclipse.swt.events.ModifyListener;
  import org.eclipse.swt.events.SelectionAdapter;
  import org.eclipse.swt.events.SelectionEvent;
  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.Label;
  import org.eclipse.swt.widgets.Text;
  import org.eclipse.swt.widgets.TreeItem;
  import org.eclipse.ui.IWorkbenchPage;
  import org.eclipse.ui.IWorkbenchPart;
  import org.eclipse.ui.IWorkingSet;
  import org.eclipse.ui.IWorkingSetManager;
  import org.eclipse.ui.PlatformUI;
  import org.eclipse.ui.dialogs.IWorkingSetPage;
  import org.eclipse.wst.jsdt.core.IJavaScriptElement;
  import org.eclipse.wst.jsdt.core.IJavaScriptModel;
  import org.eclipse.wst.jsdt.core.IJavaScriptProject;
  import org.eclipse.wst.jsdt.core.IPackageFragment;
  import org.eclipse.wst.jsdt.core.IPackageFragmentRoot;
  import org.eclipse.wst.jsdt.core.JavaScriptCore;
  import org.eclipse.wst.jsdt.core.JavaScriptModelException;
  import org.eclipse.wst.jsdt.internal.ui.IJavaHelpContextIds;
  import org.eclipse.wst.jsdt.internal.ui.JavaScriptPlugin;
  import org.eclipse.wst.jsdt.internal.ui.JavaPluginImages;
  import org.eclipse.wst.jsdt.internal.ui.actions.SelectionConverter;
  import org.eclipse.wst.jsdt.internal.ui.filters.EmptyInnerPackageFilter;
  import org.eclipse.wst.jsdt.internal.ui.util.JavaUIHelp;
  import org.eclipse.wst.jsdt.internal.ui.util.SWTUtil;
  import org.eclipse.wst.jsdt.internal.ui.viewsupport.AppearanceAwareLabelProvider;
  import org.eclipse.wst.jsdt.internal.ui.viewsupport.ColoredViewersManager;
  import org.eclipse.wst.jsdt.internal.ui.viewsupport.DecoratingJavaLabelProvider;
  import org.eclipse.wst.jsdt.internal.ui.viewsupport.JavaElementImageProvider;
  import org.eclipse.wst.jsdt.ui.JavaScriptElementComparator;
  import org.eclipse.wst.jsdt.ui.JavaScriptElementLabels;
  
  /**
   * The Java working set page allows the user to create
   * and edit a Java working set.
   * <p>
   * Working set elements are presented as a Java element tree.
   * </p>
   * 
   * 
   */
  public class JavaWorkingSetPage extends WizardPage implements IWorkingSetPage {
  
  	final private static String PAGE_TITLE= WorkingSetMessages.JavaWorkingSetPage_title; 
  	final private static String PAGE_ID= "javaWorkingSetPage"; //$NON-NLS-1$
  	
  	private Text fWorkingSetName;
  	private CheckboxTreeViewer fTree;
  	private ITreeContentProvider fTreeContentProvider;
  	
  	private boolean fFirstCheck;
  	private IWorkingSet fWorkingSet;
  
  	/**
  	 * Default constructor.
  	 */
  	public JavaWorkingSetPage() {
  		super(PAGE_ID, PAGE_TITLE, JavaPluginImages.DESC_WIZBAN_JAVA_WORKINGSET);
  		setDescription(WorkingSetMessages.JavaWorkingSetPage_workingSet_description); 
  		fFirstCheck= true;
  	}
  
  	/*
  	 * @see org.eclipse.jface.dialogs.IDialogPage#createControl(org.eclipse.swt.widgets.Composite)
  	 */
  	public void createControl(Composite parent) {
  		initializeDialogUnits(parent);
  		
  		Composite composite= new Composite(parent, SWT.NONE);
  		composite.setLayout(new GridLayout());
  		composite.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_FILL));
  		setControl(composite);
  
  		Label label= new Label(composite, SWT.WRAP);
  		label.setText(WorkingSetMessages.JavaWorkingSetPage_workingSet_name); 
  		GridData gd= new GridData(GridData.GRAB_HORIZONTAL | GridData.HORIZONTAL_ALIGN_FILL | GridData.VERTICAL_ALIGN_CENTER);
  		label.setLayoutData(gd);
  
  		fWorkingSetName= new Text(composite, SWT.SINGLE | SWT.BORDER);
  		fWorkingSetName.setLayoutData(new GridData(GridData.GRAB_HORIZONTAL | GridData.HORIZONTAL_ALIGN_FILL));
  		fWorkingSetName.addModifyListener(
  			new ModifyListener() {
  				public void modifyText(ModifyEvent e) {
  					validateInput();
  				}
  			}
  		);
  		fWorkingSetName.setFocus();
  		
  		label= new Label(composite, SWT.WRAP);
  		label.setText(WorkingSetMessages.JavaWorkingSetPage_workingSet_content); 
  		gd= new GridData(GridData.GRAB_HORIZONTAL | GridData.HORIZONTAL_ALIGN_FILL | GridData.VERTICAL_ALIGN_CENTER);
  		label.setLayoutData(gd);
  
  		fTree= new CheckboxTreeViewer(composite, SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL);
  		ColoredViewersManager.install(fTree);
  		gd= new GridData(GridData.FILL_BOTH | GridData.GRAB_VERTICAL);
  		gd.heightHint= convertHeightInCharsToPixels(15);
  		fTree.getControl().setLayoutData(gd);
  		
  		fTreeContentProvider= new JavaWorkingSetPageContentProvider();
  		fTree.setContentProvider(fTreeContentProvider);
  		
  		AppearanceAwareLabelProvider fJavaElementLabelProvider= 
  			new AppearanceAwareLabelProvider(
  				AppearanceAwareLabelProvider.DEFAULT_TEXTFLAGS | JavaScriptElementLabels.P_COMPRESSED,
  				AppearanceAwareLabelProvider.DEFAULT_IMAGEFLAGS | JavaElementImageProvider.SMALL_ICONS
  			);
  		
  		fTree.setLabelProvider(new DecoratingJavaLabelProvider(fJavaElementLabelProvider));
  		fTree.setComparator(new JavaScriptElementComparator());
  		fTree.addFilter(new EmptyInnerPackageFilter());
  		fTree.setUseHashlookup(true);
  		
  		fTree.setInput(JavaScriptCore.create(ResourcesPlugin.getWorkspace().getRoot()));
  
  		fTree.addCheckStateListener(new ICheckStateListener() {
  			public void checkStateChanged(CheckStateChangedEvent event) {
  				handleCheckStateChange(event);
  			}
  		});
  
  		fTree.addTreeListener(new ITreeViewerListener() {
  			public void treeCollapsed(TreeExpansionEvent event) {
  			}
  			public void treeExpanded(TreeExpansionEvent event) {
  				final Object element= event.getElement();
  				if (fTree.getGrayed(element) == false)
  					BusyIndicator.showWhile(getShell().getDisplay(), new Runnable() {
  					public void run() {
  						setSubtreeChecked(element, fTree.getChecked(element), false);
  					}
  				});
  			}
  		});
  
  		// Add select / deselect all buttons for bug 46669
  		Composite buttonComposite = new Composite(composite, SWT.NONE);
  		GridLayout layout= new GridLayout(2, false);
  		layout.marginWidth= 0; layout.marginHeight= 0;
  		buttonComposite.setLayout(layout);
  		buttonComposite.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_FILL));
  		
  		Button selectAllButton = new Button(buttonComposite, SWT.PUSH);
  		selectAllButton.setText(WorkingSetMessages.JavaWorkingSetPage_selectAll_label);
  		selectAllButton.setToolTipText(WorkingSetMessages.JavaWorkingSetPage_selectAll_toolTip);
  		selectAllButton.addSelectionListener(new SelectionAdapter() {
  			public void widgetSelected(SelectionEvent selectionEvent) {
  				fTree.setCheckedElements(fTreeContentProvider.getElements(fTree.getInput()));
  				validateInput();
  			}
  		});
  		selectAllButton.setLayoutData(new GridData());
  		SWTUtil.setButtonDimensionHint(selectAllButton);
  
  		Button deselectAllButton = new Button(buttonComposite, SWT.PUSH);
  		deselectAllButton.setText(WorkingSetMessages.JavaWorkingSetPage_deselectAll_label);
  		deselectAllButton.setToolTipText(WorkingSetMessages.JavaWorkingSetPage_deselectAll_toolTip);
  		deselectAllButton.addSelectionListener(new SelectionAdapter() {
  			public void widgetSelected(SelectionEvent selectionEvent) {
  				fTree.setCheckedElements(new Object[0]);
  				validateInput();
  			}
  		});
  		deselectAllButton.setLayoutData(new GridData());
  		SWTUtil.setButtonDimensionHint(deselectAllButton);
  		
  		if (fWorkingSet != null)
  			fWorkingSetName.setText(fWorkingSet.getName());
  		initializeCheckedState();
  		validateInput();
  
  		Dialog.applyDialogFont(composite);
  		// Set help for the page 
  		JavaUIHelp.setHelp(fTree, IJavaHelpContextIds.JAVA_WORKING_SET_PAGE);
  	}
  
  	/*
  	 * Implements method from IWorkingSetPage
  	 */
  	public IWorkingSet getSelection() {
  		return fWorkingSet;
  	}
  
  	/*
  	 * Implements method from IWorkingSetPage
  	 */
  	public void setSelection(IWorkingSet workingSet) {
  		Assert.isNotNull(workingSet, "Working set must not be null"); //$NON-NLS-1$
  		fWorkingSet= workingSet;
  		if (getContainer() != null && getShell() != null && fWorkingSetName != null) {
  			fFirstCheck= false;
  			fWorkingSetName.setText(fWorkingSet.getName());
  			initializeCheckedState();
  			validateInput();
  		}
  	}
  
  	/*
  	 * Implements method from IWorkingSetPage
  	 */
  	public void finish() {
  		String workingSetName= fWorkingSetName.getText();
  		ArrayList elements= new ArrayList(10);
  		findCheckedElements(elements, fTree.getInput());
  		if (fWorkingSet == null) {
  			IWorkingSetManager workingSetManager= PlatformUI.getWorkbench().getWorkingSetManager();
  			fWorkingSet= workingSetManager.createWorkingSet(workingSetName, (IAdaptable[])elements.toArray(new IAdaptable[elements.size()]));
  		} else {
  			// Add inaccessible resources
  			IAdaptable[] oldItems= fWorkingSet.getElements();
  			HashSet closedWithChildren= new HashSet(elements.size());
  			for (int i= 0; i < oldItems.length; i++) {
  				IResource oldResource= null;
  				if (oldItems[i] instanceof IResource) {
  					oldResource= (IResource)oldItems[i];
  				} else {
  					oldResource= (IResource)oldItems[i].getAdapter(IResource.class);
  				}
  				if (oldResource != null && oldResource.isAccessible() == false) {
  					IProject project= oldResource.getProject();
  					if (closedWithChildren.contains(project) || elements.contains(project)) {
  						elements.add(oldItems[i]);
  						elements.remove(project);
  						closedWithChildren.add(project);
  					}
  				}
  			}
  			fWorkingSet.setName(workingSetName);
  			fWorkingSet.setElements((IAdaptable[]) elements.toArray(new IAdaptable[elements.size()]));
  		}
  	}
  
  	private void validateInput() {
  		String errorMessage= null; 
  		String infoMessage= null;
  		String newText= fWorkingSetName.getText();
  
  		if (newText.equals(newText.trim()) == false)
  			errorMessage = WorkingSetMessages.JavaWorkingSetPage_warning_nameWhitespace; 
  		if (newText.equals("")) { //$NON-NLS-1$
  			if (fFirstCheck) {
  				setPageComplete(false);
  				fFirstCheck= false;
  				return;
  			}
  			else				
  				errorMessage= WorkingSetMessages.JavaWorkingSetPage_warning_nameMustNotBeEmpty; 
  		}
  
  		fFirstCheck= false;
  
  		if (errorMessage == null && (fWorkingSet == null || newText.equals(fWorkingSet.getName()) == false)) {
  			IWorkingSet[] workingSets= PlatformUI.getWorkbench().getWorkingSetManager().getWorkingSets();
  			for (int i= 0; i < workingSets.length; i++) {
  				if (newText.equals(workingSets[i].getName())) {
  					errorMessage= WorkingSetMessages.JavaWorkingSetPage_warning_workingSetExists; 
  				}
  			}
  		}
  		
  		if (!hasCheckedElement())
  			infoMessage= WorkingSetMessages.JavaWorkingSetPage_warning_resourceMustBeChecked;
  
  		setMessage(infoMessage, INFORMATION);
  		setErrorMessage(errorMessage);
  		setPageComplete(errorMessage == null);
  	}
  	
  	private boolean hasCheckedElement() {
  		TreeItem[] items= fTree.getTree().getItems();
  		for (int i= 0; i < items.length; i++) {
  			if (items[i].getChecked())
  				return true;
  		}
  		return false;
  	}
  	
  	private void findCheckedElements(List checkedResources, Object parent) {
  		Object[] children= fTreeContentProvider.getChildren(parent);
  		for (int i= 0; i < children.length; i++) {
  			if (fTree.getGrayed(children[i]))
  				findCheckedElements(checkedResources, children[i]);
  			else if (fTree.getChecked(children[i]))
  				checkedResources.add(children[i]);
  		}
  	}
  
  	void handleCheckStateChange(final CheckStateChangedEvent event) {
  		BusyIndicator.showWhile(getShell().getDisplay(), new Runnable() {
  			public void run() {
  				IAdaptable element= (IAdaptable)event.getElement();
  				boolean state= event.getChecked();		
  				fTree.setGrayed(element, false);
  				if (isExpandable(element))
  					setSubtreeChecked(element, state, state); // only check subtree if state is set to true
  					
  				updateParentState(element, state);
  				validateInput();
  			}
  		});
  	}
  
  	private void setSubtreeChecked(Object parent, boolean state, boolean checkExpandedState) {
  		if (!(parent instanceof IAdaptable))
  			return;
  		IContainer container= (IContainer)((IAdaptable)parent).getAdapter(IContainer.class);
  		if ((!fTree.getExpandedState(parent) && checkExpandedState) || (container != null && !container.isAccessible()))
  			return;
  		
  		Object[] children= fTreeContentProvider.getChildren(parent);
  		for (int i= children.length - 1; i >= 0; i--) {
  			Object element= children[i];
  			if (state) {
  				fTree.setChecked(element, true);
  				fTree.setGrayed(element, false);
  			}
  			else
  				fTree.setGrayChecked(element, false);
  			if (isExpandable(element))
  				setSubtreeChecked(element, state, true);
  		}
  	}
  
  	private void updateParentState(Object child, boolean baseChildState) {
  		if (child == null)
  			return;
  		if (child instanceof IAdaptable) {
  			IResource resource= (IResource)((IAdaptable)child).getAdapter(IResource.class);
  			if (resource != null && !resource.isAccessible())
  				return;
  		}
  		Object parent= fTreeContentProvider.getParent(child);
  		if (parent == null)
  			return;
  		
  		updateObjectState(parent, baseChildState);
  	}
  
  	private void updateObjectState(Object element, boolean baseChildState) {		
  
  		boolean allSameState= true;
  		Object[] children= fTreeContentProvider.getChildren(element);
  
  		for (int i= children.length -1; i >= 0; i--) {
  			if (fTree.getChecked(children[i]) != baseChildState || fTree.getGrayed(children[i])) {
  				allSameState= false;
  				break;
  			}
  		}
  	
  		fTree.setGrayed(element, !allSameState);
  		fTree.setChecked(element, !allSameState || baseChildState);
  		
  		updateParentState(element, baseChildState);
  	}
  
  	private void initializeCheckedState() {
  
  		BusyIndicator.showWhile(getShell().getDisplay(), new Runnable() {
  			public void run() {
  				Object[] elements;
  				if (fWorkingSet == null) {
  					// Use current part's selection for initialization
  					IWorkbenchPage page= JavaScriptPlugin.getActivePage();
  					if (page == null)
  						return;
  					
  					IWorkbenchPart part= JavaScriptPlugin.getActivePage().getActivePart();
  					if (part == null)
  						return;
  					
  					try {
  						elements= SelectionConverter.getStructuredSelection(part).toArray();
  						for (int i= 0; i < elements.length; i++) {
  							if (elements[i] instanceof IResource) {
  								IJavaScriptElement je= (IJavaScriptElement)((IResource)elements[i]).getAdapter(IJavaScriptElement.class);
  								if (je != null && je.exists() &&  je.getJavaScriptProject().isOnIncludepath((IResource)elements[i]))
  									elements[i]= je;
  							}
  						}
  					} catch (JavaScriptModelException e) {
  						return;
  					}
  				}
  				else
  					elements= fWorkingSet.getElements();
  
  				// Use closed project for elements in closed project
  				for (int i= 0; i < elements.length; i++) {
  					Object element= elements[i];
  					if (element instanceof IResource) {
  						IProject project= ((IResource)element).getProject();
  						if (!project.isAccessible())
  							elements[i]= project;
  					}
  					if (element instanceof IJavaScriptElement) {
  						IJavaScriptProject jProject= ((IJavaScriptElement)element).getJavaScriptProject();
  						if (jProject != null && !jProject.getProject().isAccessible()) 
  							elements[i]= jProject.getProject();
  					}
  				}
  
  				fTree.setCheckedElements(elements);
  				HashSet parents = new HashSet();
  				for (int i= 0; i < elements.length; i++) {
  					Object element= elements[i];
  					if (isExpandable(element))
  						setSubtreeChecked(element, true, true);
  						
  					if (element instanceof IAdaptable) {
  						IResource resource= (IResource) ((IAdaptable)element).getAdapter(IResource.class);
  						if (resource != null && !resource.isAccessible())
  							continue;
  					}
  					Object parent= fTreeContentProvider.getParent(element);
  					if (parent != null)
  						parents.add(parent);
  				}
  				
  				for (Iterator i = parents.iterator(); i.hasNext();)
  					updateObjectState(i.next(), true);
  			}
  		});
  	}
  	
  	private boolean isExpandable(Object element) {
  		return (
  			element instanceof IJavaScriptProject
  			||
  			element instanceof IPackageFragmentRoot
  			||
  			element instanceof IPackageFragment
  			||
  			element instanceof IJavaScriptModel
  			||
  			element instanceof IContainer);
  	}
  }
  