/*******************************************************************************
 * Copyright (c) 2000, 2010 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.part;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.eclipse.core.commands.AbstractHandler;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.commands.util.Tracing;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.ISafeRunnable;
import org.eclipse.core.runtime.ListenerList;
import org.eclipse.core.runtime.SafeRunner;
import org.eclipse.jface.dialogs.IPageChangeProvider;
import org.eclipse.jface.dialogs.IPageChangedListener;
import org.eclipse.jface.dialogs.PageChangedEvent;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.util.SafeRunnable;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CTabFolder;
import org.eclipse.swt.custom.CTabItem;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.TraverseEvent;
import org.eclipse.swt.events.TraverseListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.FillLayout;
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.Item;
import org.eclipse.ui.IEditorActionBarContributor;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IEditorSite;
import org.eclipse.ui.IKeyBindingService;
import org.eclipse.ui.INestableKeyBindingService;
import org.eclipse.ui.IPartService;
import org.eclipse.ui.IPropertyListener;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.IWorkbenchPartSite;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.handlers.IHandlerService;
import org.eclipse.ui.internal.PartSite;
import org.eclipse.ui.internal.WorkbenchPlugin;
import org.eclipse.ui.internal.misc.Policy;
import org.eclipse.ui.internal.services.INestable;
import org.eclipse.ui.internal.services.IServiceLocatorCreator;
import org.eclipse.ui.internal.util.Util;
import org.eclipse.ui.services.IDisposable;
import org.eclipse.ui.services.IServiceLocator;

/**
 * A multi-page editor is an editor with multiple pages, each of which may
 * contain an editor or an arbitrary SWT control.
 * <p>
 * Subclasses must implement the following methods:
 * <ul>
 * <li><code>createPages</code> - to create the required pages by calling one of
 * the <code>addPage</code> methods</li>
 * <li><code>IEditorPart.doSave</code> - to save contents of editor</li>
 * <li><code>IEditorPart.doSaveAs</code> - to save contents of editor</li>
 * <li><code>IEditorPart.isSaveAsAllowed</code> - to enable Save As</li>
 * <li><code>IEditorPart.gotoMarker</code> - to scroll to a marker</li>
 * </ul>
 * </p>
 * <p>
 * Multi-page editors have a single action bar contributor, which manages
 * contributions for all the pages. The contributor must be a subclass of
 * <code>MultiPageEditorActionBarContributor</code>. Note that since any nested
 * editors are created directly in code by callers of
 * <code>addPage(IEditorPart,IEditorInput)</code>, nested editors do not have
 * their own contributors.
 * </p>
 * <p>
 * As of 3.5 multi-page editors will post PageChangedEvents at the end of
 * {@link #pageChange(int)}. Subclasses may override {@link #getSelectedPage()}
 * to return a page appropriate to their multi-page editor. IPartListener2
 * listeners registered with the IPartService can implement IPageChangedListener
 * to be notified about all page change events within the workbench page or
 * workbench window.
 * </p>
 * 
 * @see org.eclipse.ui.part.MultiPageEditorActionBarContributor
 * @see org.eclipse.jface.dialogs.IPageChangeProvider
 * @see org.eclipse.jface.dialogs.IPageChangedListener
 * @see org.eclipse.ui.IPartService
 */
public abstract class MultiPageEditorPart extends EditorPart implements IPageChangeProvider {
	
	private static final String COMMAND_NEXT_SUB_TAB = "org.eclipse.ui.navigate.nextSubTab"; //$NON-NLS-1$
	private static final String COMMAND_PREVIOUS_SUB_TAB = "org.eclipse.ui.navigate.previousSubTab"; //$NON-NLS-1$
	
	/**
	 * Subclasses that override {@link #createPageContainer(Composite)} can use
	 * this constant to get a site for the container that can be active while
	 * the current page is deactivated.
	 * 
	 * @since 3.4
	 * @see #activateSite()
	 * @see #deactivateSite(boolean, boolean)
	 * @see #getPageSite(int)
	 */
	protected static final int PAGE_CONTAINER_SITE = 65535;

	/**
	 * Private tracing output.
	 */
	private static final String TRACING_COMPONENT = "MPE"; //$NON-NLS-1$

	/**
	 * The active service locator. This value may be <code>null</code> if
	 * there is no selected page, or if the selected page is a control with
	 * no site.
	 */
	private INestable activeServiceLocator;

	/**
	 * The container widget.
	 */
	private CTabFolder container;

	/**
	 * List of nested editors. Element type: IEditorPart. Need to hang onto them
	 * here, in addition to using get/setData on the items, because dispose()
	 * needs to access them, but widgetry has already been disposed at that
	 * point.
	 */
	private ArrayList nestedEditors = new ArrayList(3);
	
	private List pageSites = new ArrayList(3);

	private IServiceLocator pageContainerSite;
	
	private ListenerList pageChangeListeners = new ListenerList(
			ListenerList.IDENTITY);

	/**
	 * Creates an empty multi-page editor with no pages.
	 */
	protected MultiPageEditorPart() {
		super();
	}

	/**
	 * Creates and adds a new page containing the given control to this
	 * multi-page editor. The control may be <code>null</code>, allowing it
	 * to be created and set later using <code>setControl</code>.
	 * 
	 * @param control
	 *            the control, or <code>null</code>
	 * @return the index of the new page
	 * 
	 * @see MultiPageEditorPart#setControl(int, Control)
	 */
	public int addPage(Control control) {
		int index = getPageCount();
		addPage(index, control);
		return index;
	}

	/**
	 * Creates and adds a new page containing the given control to this
	 * multi-page editor. The page is added at the given index. The control may
	 * be <code>null</code>, allowing it to be created and set later using
	 * <code>setControl</code>.
	 * 
	 * @param index
	 *            the index at which to add the page (0-based)
	 * @param control
	 *            the control, or <code>null</code>
	 * 
	 * @see MultiPageEditorPart#setControl(int, Control)
	 */
	public void addPage(int index, Control control) {
		createItem(index, control);
	}

	/**
	 * Creates and adds a new page containing the given editor to this
	 * multi-page editor. This also hooks a property change listener on the
	 * nested editor.
	 * 
	 * @param editor
	 *            the nested editor
	 * @param input
	 *            the input for the nested editor
	 * @return the index of the new page
	 * @exception PartInitException
	 *                if a new page could not be created
	 * 
	 * @see MultiPageEditorPart#handlePropertyChange(int) the handler for
	 *      property change events from the nested editor
	 */
	public int addPage(IEditorPart editor, IEditorInput input)
			throws PartInitException {
		int index = getPageCount();
		addPage(index, editor, input);
		return index;
	}

	/**
	 * Creates and adds a new page containing the given editor to this
	 * multi-page editor. The page is added at the given index. This also hooks
	 * a property change listener on the nested editor.
	 * 
	 * @param index
	 *            the index at which to add the page (0-based)
	 * @param editor
	 *            the nested editor
	 * @param input
	 *            the input for the nested editor
	 * @exception PartInitException
	 *                if a new page could not be created
	 * 
	 * @see MultiPageEditorPart#handlePropertyChange(int) the handler for
	 *      property change events from the nested editor
	 */
	public void addPage(int index, IEditorPart editor, IEditorInput input)
			throws PartInitException {
		IEditorSite site = createSite(editor);
		// call init first so that if an exception is thrown, we have created no
		// new widgets
		editor.init(site, input);
		Composite parent2 = new Composite(getContainer(),
				getOrientation(editor));
		parent2.setLayout(new FillLayout());
		editor.createPartControl(parent2);
		editor.addPropertyListener(new IPropertyListener() {
			public void propertyChanged(Object source, int propertyId) {
				MultiPageEditorPart.this.handlePropertyChange(propertyId);
			}
		});
		// create item for page only after createPartControl has succeeded
		Item item = createItem(index, parent2);
		// remember the editor, as both data on the item, and in the list of
		// editors (see field comment)
		item.setData(editor);
		nestedEditors.add(editor);
	}

	/**
	 * Get the orientation of the editor.
	 * 
	 * @param editor
	 * @return int the orientation flag
	 * @see SWT#RIGHT_TO_LEFT
	 * @see SWT#LEFT_TO_RIGHT
	 * @see SWT#NONE
	 */
	private int getOrientation(IEditorPart editor) {
		if (editor instanceof IWorkbenchPartOrientation) {
			return ((IWorkbenchPartOrientation) editor).getOrientation();
		}
		return getOrientation();
	}

	/**
	 * Creates an empty container. Creates a CTabFolder with no style bits set,
	 * and hooks a selection listener which calls <code>pageChange()</code>
	 * whenever the selected tab changes.
	 * 
	 * @param parent
	 *            The composite in which the container tab folder should be
	 *            created; must not be <code>null</code>.
	 * @return a new container
	 */
	private CTabFolder createContainer(Composite parent) {
		// use SWT.FLAT style so that an extra 1 pixel border is not reserved
		// inside the folder
		parent.setLayout(new FillLayout());
		final CTabFolder newContainer = new CTabFolder(parent, SWT.BOTTOM
				| SWT.FLAT);
		newContainer.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				int newPageIndex = newContainer.indexOf((CTabItem) e.item);
				pageChange(newPageIndex);
			}
		});
		newContainer.addTraverseListener(new TraverseListener() { 
			// see https://bugs.eclipse.org/bugs/show_bug.cgi?id=199499 : Switching tabs by Ctrl+PageUp/PageDown must not be caught on the inner tab set
			public void keyTraversed(TraverseEvent e) {
				switch (e.detail) {
					case SWT.TRAVERSE_PAGE_NEXT:
					case SWT.TRAVERSE_PAGE_PREVIOUS:
						int detail = e.detail;
						e.doit = true;
						e.detail = SWT.TRAVERSE_NONE;
						Control control = newContainer.getParent();
						control.traverse(detail, new Event());
				}
			}
		});
		return newContainer;
	}

	/**
	 * Creates a tab item at the given index and places the given control in the
	 * new item. The item is a CTabItem with no style bits set.
	 * 
	 * @param index
	 *            the index at which to add the control
	 * @param control
	 *            is the control to be placed in an item
	 * @return a new item
	 */
	private CTabItem createItem(int index, Control control) {
		CTabItem item = new CTabItem(getTabFolder(), SWT.NONE, index);
		item.setControl(control);
		return item;
	}

	/**
	 * Creates the pages of this multi-page editor.
	 * <p>
	 * Subclasses must implement this method.
	 * </p>
	 */
	protected abstract void createPages();

	/**
	 * The <code>MultiPageEditor</code> implementation of this
	 * <code>IWorkbenchPart</code> method creates the control for the
	 * multi-page editor by calling <code>createContainer</code>, then
	 * <code>createPages</code>. Subclasses should implement
	 * <code>createPages</code> rather than overriding this method.
	 * 
	 * @param parent
	 *            The parent in which the editor should be created; must not be
	 *            <code>null</code>.
	 */
	public final void createPartControl(Composite parent) {
		Composite pageContainer = createPageContainer(parent);
		this.container = createContainer(pageContainer);
		createPages();
		// set the active page (page 0 by default), unless it has already been
		// done
		if (getActivePage() == -1) {
			setActivePage(0);
			IEditorPart part = getEditor(0);
			if (part!=null) {
				final IServiceLocator serviceLocator = part.getEditorSite();
				if (serviceLocator instanceof INestable) {
					activeServiceLocator = (INestable) serviceLocator;
					activeServiceLocator.activate();
				}
			}
		}
		initializePageSwitching();
		initializeSubTabSwitching();
	}

	/**
	 * Initialize the MultiPageEditorPart to use the page switching command.
	 * Clients can override this method with an empty body if they wish to
	 * opt-out.
	 * 
	 * @since 3.4
	 */
	protected void initializePageSwitching() {
		new PageSwitcher(this) {
			public Object[] getPages() {
				int pageCount = getPageCount();
				Object[] result = new Object[pageCount];
				for (int i = 0; i < pageCount; i++) {
					result[i] = new Integer(i);
				}
				return result;
			}

			public String getName(Object page) {
				return getPageText(((Integer) page).intValue());
			}

			public ImageDescriptor getImageDescriptor(Object page) {
				Image image = getPageImage(((Integer) page).intValue());
				if (image == null)
					return null;

				return ImageDescriptor.createFromImage(image);
			}

			public void activatePage(Object page) {
				setActivePage(((Integer) page).intValue());
			}

			public int getCurrentPageIndex() {
				return getActivePage();
			}
		};
	}

	/**
	 * Initialize the MultiPageEditorPart to use the sub-tab switching commands.
	 * 
	 * @since 3.5
	 */
	private void initializeSubTabSwitching() {
		IHandlerService service = (IHandlerService) getSite().getService(IHandlerService.class);
		service.activateHandler(COMMAND_NEXT_SUB_TAB, new AbstractHandler() {
			/**
			 * {@inheritDoc}
			 * @throws ExecutionException
			 *             if an exception occurred during execution
			 */
			public Object execute(ExecutionEvent event) throws ExecutionException {
				int n= getPageCount();
				if (n == 0)
					return null;
				
				int i= getActivePage() + 1;
				if (i >= n)
					i= 0;
				setActivePage(i);
				return null;
			}
		});
		
		service.activateHandler(COMMAND_PREVIOUS_SUB_TAB, new AbstractHandler() {
			/**
			 * {@inheritDoc}
			 * @throws ExecutionException
			 *             if an exception occurred during execution
			 */
			public Object execute(ExecutionEvent event) throws ExecutionException {
				int n= getPageCount();
				if (n == 0)
					return null;
				
				int i= getActivePage() - 1;
				if (i < 0)
					i= n - 1;
				setActivePage(i);
				return null;
			}
		});
	}
	
	/**
	 * Creates the parent control for the container returned by
	 * {@link #getContainer() }.
	 * 
	 * <p>
	 * Subclasses may extend and must call super implementation first.
	 * </p>
	 * 
	 * @param parent
	 *            the parent for all of the editors contents.
	 * @return the parent for this editor's container. Must not be
	 *         <code>null</code>.
	 * 
	 * @since 3.2
	 */
	protected Composite createPageContainer(Composite parent) {
		return parent;
	}

	/**
	 * Creates the site for the given nested editor. The
	 * <code>MultiPageEditorPart</code> implementation of this method creates
	 * an instance of <code>MultiPageEditorSite</code>. Subclasses may
	 * reimplement to create more specialized sites.
	 * 
	 * @param editor
	 *            the nested editor
	 * @return the editor site
	 */
	protected IEditorSite createSite(IEditorPart editor) {
		return new MultiPageEditorSite(this, editor);
	}

	/**
	 * The <code>MultiPageEditorPart</code> implementation of this
	 * <code>IWorkbenchPart</code> method disposes all nested editors.
	 * Subclasses may extend.
	 */
	public void dispose() {
		pageChangeListeners.clear();
		for (int i = 0; i < nestedEditors.size(); ++i) {
			IEditorPart editor = (IEditorPart) nestedEditors.get(i);
			disposePart(editor);
		}
		nestedEditors.clear();
		if (pageContainerSite instanceof IDisposable) {
			((IDisposable) pageContainerSite).dispose();
			pageContainerSite = null;
		}
		for (int i = 0; i < pageSites.size(); i++) {
			IServiceLocator sl = (IServiceLocator) pageSites.get(i);
			if (sl instanceof IDisposable) {
				((IDisposable) sl).dispose();
			}
		}
		pageSites.clear();
		super.dispose();
	}

	/**
	 * Returns the active nested editor if there is one.
	 * <p>
	 * Subclasses should not override this method
	 * </p>
	 * 
	 * @nooverride
	 * @return the active nested editor, or <code>null</code> if none
	 */
	protected IEditorPart getActiveEditor() {
		int index = getActivePage();
		if (index != -1) {
			return getEditor(index);
		}
		return null;
	}

	/**
	 * Returns the index of the currently active page, or -1 if there is no
	 * active page.
	 * <p>
	 * Subclasses should not override this method
	 * </p>
	 * 
	 * @nooverride
	 * 
	 * @return the index of the active page, or -1 if there is no active page
	 * @since 3.5
	 */
	public int getActivePage() {
		CTabFolder tabFolder = getTabFolder();
		if (tabFolder != null && !tabFolder.isDisposed()) {
			return tabFolder.getSelectionIndex();
		}
		return -1;
	}

	/**
	 * Returns the composite control containing this multi-page editor's pages.
	 * This should be used as the parent when creating controls for the
	 * individual pages. That is, when calling <code>addPage(Control)</code>,
	 * the passed control should be a child of this container.
	 * <p>
	 * Warning: Clients should not assume that the container is any particular
	 * subclass of Composite. The actual class used may change in order to
	 * improve the look and feel of multi-page editors. Any code making
	 * assumptions on the particular subclass would thus be broken.
	 * </p>
	 * <p>
	 * Subclasses should not override this method
	 * </p>
	 * 
	 * @return the composite, or <code>null</code> if
	 *         <code>createPartControl</code> has not been called yet
	 */
	protected Composite getContainer() {
		return container;
	}

	/**
	 * Returns the control for the given page index, or <code>null</code> if
	 * no control has been set for the page. The page index must be valid.
	 * <p>
	 * Subclasses should not override this method
	 * </p>
	 * 
	 * @param pageIndex
	 *            the index of the page
	 * @return the control for the specified page, or <code>null</code> if
	 *         none has been set
	 */
	protected Control getControl(int pageIndex) {
		return getItem(pageIndex).getControl();
	}

	/**
	 * Returns the editor for the given page index. The page index must be
	 * valid.
	 * 
	 * @param pageIndex
	 *            the index of the page
	 * @return the editor for the specified page, or <code>null</code> if the
	 *         specified page was not created with
	 *         <code>addPage(IEditorPart,IEditorInput)</code>
	 */
	protected IEditorPart getEditor(int pageIndex) {
		Item item = getItem(pageIndex);
		if (item != null) {
			Object data = item.getData();
			if (data instanceof IEditorPart) {
				return (IEditorPart) data;
			}
		}
		return null;
	}
	
	/**
	 * Returns the service locator for the given page index. This method can be
	 * used to create service locators for pages that are just controls. The
	 * page index must be valid.
	 * <p>
	 * This will return the editor site service locator for an editor, and
	 * create one for a page that is just a control.
	 * </p>
	 * 
	 * @param pageIndex
	 *            the index of the page
	 * @return the editor for the specified page, or <code>null</code> if the
	 *         specified page was not created with
	 *         <code>addPage(IEditorPart,IEditorInput)</code>
	 * @since 3.4
	 */
	protected final IServiceLocator getPageSite(int pageIndex) {
		if (pageIndex == PAGE_CONTAINER_SITE) {
			return getPageContainerSite();
		}
		
		Item item = getItem(pageIndex);
		if (item != null) {
			Object data = item.getData();
			if (data instanceof IEditorPart) {
				return ((IEditorPart) data).getSite();
			} else if (data instanceof IServiceLocator) {
				return (IServiceLocator) data;
			} else if (data == null) {
				IServiceLocatorCreator slc = (IServiceLocatorCreator) getSite()
						.getService(IServiceLocatorCreator.class);
				IServiceLocator sl = slc.createServiceLocator(getSite(), null, new IDisposable(){
					public void dispose() {
						final Control control = ((PartSite)getSite()).getPane().getControl();
						if (control != null && !control.isDisposed()) {
							((PartSite)getSite()).getPane().doHide();
						}
					}
				});
				item.setData(sl);
				pageSites.add(sl);
				return sl;
			}
		}
		return null;
	}

	/**
	 * @return A site that can be used with a header.
	 * @since 3.4
	 * @see #createPageContainer(Composite)
	 * @see #PAGE_CONTAINER_SITE
	 * @see #getPageSite(int)
	 */
	private IServiceLocator getPageContainerSite() {
		if (pageContainerSite == null) {
			IServiceLocatorCreator slc = (IServiceLocatorCreator) getSite()
					.getService(IServiceLocatorCreator.class);
			pageContainerSite = slc.createServiceLocator(getSite(), null, new IDisposable(){
				public void dispose() {
					final Control control = ((PartSite)getSite()).getPane().getControl();
					if (control != null && !control.isDisposed()) {
						((PartSite)getSite()).getPane().doHide();
					}
				}
			});
		}
		return pageContainerSite;
	}

	/**
	 * Returns the tab item for the given page index (page index is 0-based).
	 * The page index must be valid.
	 * 
	 * @param pageIndex
	 *            the index of the page
	 * @return the tab item for the given page index
	 */
	private CTabItem getItem(int pageIndex) {
		return getTabFolder().getItem(pageIndex);
	}

	/**
	 * Returns the number of pages in this multi-page editor.
	 * 
	 * @return the number of pages
	 */
	protected int getPageCount() {
		CTabFolder folder = getTabFolder();
		// May not have been created yet, or may have been disposed.
		if (folder != null && !folder.isDisposed()) {
			return folder.getItemCount();
		}
		return 0;
	}

	/**
	 * Returns the image for the page with the given index, or <code>null</code>
	 * if no image has been set for the page. The page index must be valid.
	 * 
	 * @param pageIndex
	 *            the index of the page
	 * @return the image, or <code>null</code> if none
	 */
	protected Image getPageImage(int pageIndex) {
		return getItem(pageIndex).getImage();
	}

	/**
	 * Returns the text label for the page with the given index. Returns the
	 * empty string if no text label has been set for the page. The page index
	 * must be valid.
	 * 
	 * @param pageIndex
	 *            the index of the page
	 * @return the text label for the page
	 */
	protected String getPageText(int pageIndex) {
		return getItem(pageIndex).getText();
	}

	/**
	 * Returns the tab folder containing this multi-page editor's pages.
	 * 
	 * @return the tab folder, or <code>null</code> if
	 *         <code>createPartControl</code> has not been called yet
	 */
	private CTabFolder getTabFolder() {
		return container;
	}

	/**
	 * Handles a property change notification from a nested editor. The default
	 * implementation simply forwards the change to listeners on this multi-page
	 * editor by calling <code>firePropertyChange</code> with the same
	 * property id. For example, if the dirty state of a nested editor changes
	 * (property id <code>IEditorPart.PROP_DIRTY</code>), this method handles
	 * it by firing a property change event for
	 * <code>IEditorPart.PROP_DIRTY</code> to property listeners on this
	 * multi-page editor.
	 * <p>
	 * Subclasses may extend or reimplement this method.
	 * </p>
	 * 
	 * @param propertyId
	 *            the id of the property that changed
	 */
	protected void handlePropertyChange(int propertyId) {
		firePropertyChange(propertyId);
	}

	/**
	 * The <code>MultiPageEditorPart</code> implementation of this
	 * <code>IEditorPart</code> method sets its site to the given site, its
	 * input to the given input, and the site's selection provider to a
	 * <code>MultiPageSelectionProvider</code>. Subclasses may extend this
	 * method.
	 * 
	 * @param site
	 *            The site for which this part is being created; must not be
	 *            <code>null</code>.
	 * @param input
	 *            The input on which this editor should be created; must not be
	 *            <code>null</code>.
	 * @throws PartInitException
	 *             If the initialization of the part fails -- currently never.
	 */
	public void init(IEditorSite site, IEditorInput input)
			throws PartInitException {
		setSite(site);
		setInput(input);
		site.setSelectionProvider(new MultiPageSelectionProvider(this));
	}

	/**
	 * The <code>MultiPageEditorPart</code> implementation of this
	 * <code>IEditorPart</code> method returns whether the contents of any of
	 * this multi-page editor's nested editors have changed since the last save.
	 * Pages created with <code>addPage(Control)</code> are ignored.
	 * <p>
	 * Subclasses may extend or reimplement this method.
	 * </p>
	 * 
	 * @return <code>true</code> if any of the nested editors are dirty;
	 *         <code>false</code> otherwise.
	 */
	public boolean isDirty() {
		// use nestedEditors to avoid SWT requests; see bug 12996
		for (Iterator i = nestedEditors.iterator(); i.hasNext();) {
			IEditorPart editor = (IEditorPart) i.next();
			if (editor.isDirty()) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Notifies this multi-page editor that the page with the given id has been
	 * activated. This method is called when the user selects a different tab.
	 * <p>
	 * The <code>MultiPageEditorPart</code> implementation of this method sets
	 * focus to the new page, and notifies the action bar contributor (if there
	 * is one). This checks whether the action bar contributor is an instance of
	 * <code>MultiPageEditorActionBarContributor</code>, and, if so, calls
	 * <code>setActivePage</code> with the active nested editor. This also
	 * fires a selection change event if required.
	 * </p>
	 * <p>
	 * Subclasses may extend this method.
	 * </p>
	 * 
	 * @param newPageIndex
	 *            the index of the activated page
	 */
	protected void pageChange(int newPageIndex) {
		deactivateSite(false, false);

		IPartService partService = (IPartService) getSite().getService(
				IPartService.class);
		if (partService.getActivePart() == this) {
			setFocus();
		}

		IEditorPart activeEditor = getEditor(newPageIndex);

		IEditorActionBarContributor contributor = getEditorSite()
				.getActionBarContributor();
		if (contributor != null
				&& contributor instanceof MultiPageEditorActionBarContributor) {
			((MultiPageEditorActionBarContributor) contributor)
					.setActivePage(activeEditor);
		}

		if (activeEditor != null) {
			ISelectionProvider selectionProvider = activeEditor.getSite()
					.getSelectionProvider();
			if (selectionProvider != null) {
				ISelectionProvider outerProvider = getSite()
						.getSelectionProvider();
				if (outerProvider instanceof MultiPageSelectionProvider) {
					SelectionChangedEvent event = new SelectionChangedEvent(
							selectionProvider, selectionProvider.getSelection());

					MultiPageSelectionProvider provider = (MultiPageSelectionProvider) outerProvider;
					provider.fireSelectionChanged(event);
					provider.firePostSelectionChanged(event);
				} else {
					if (Policy.DEBUG_MPE) {
						Tracing.printTrace(TRACING_COMPONENT,
								"MultiPageEditorPart " + getTitle() //$NON-NLS-1$
										+ " did not propogate selection for " //$NON-NLS-1$
										+ activeEditor.getTitle());
					}
				}
			}
		}

		activateSite();
		Object selectedPage = getSelectedPage();
		if (selectedPage != null) {
			firePageChanged(new PageChangedEvent(this, selectedPage));
		}
	}
	
	/**
	 * This method can be used by implementors of
	 * {@link MultiPageEditorPart#createPageContainer(Composite)} to deactivate
	 * the active inner editor services while their header has focus. A
	 * deactivateSite() must have a matching call to activateSite() when
	 * appropriate.
	 * <p>
	 * An new inner editor will have its site activated on a
	 * {@link MultiPageEditorPart#pageChange(int)}.
	 * </p>
	 * <p>
	 * <b>Note:</b> This API is evolving in 3.4 and this might not be its final
	 * form.
	 * </p>
	 * 
	 * @param immediate
	 *            immediately deactivate the legacy keybinding service
	 * @param containerSiteActive
	 *            Leave the page container site active.
	 * @since 3.4
	 * @see #activateSite()
	 * @see #createPageContainer(Composite)
	 * @see #getPageSite(int)
	 * @see #PAGE_CONTAINER_SITE
	 */
	protected final void deactivateSite(boolean immediate,
			boolean containerSiteActive) {
		// Deactivate the nested services from the last active service locator.
		if (activeServiceLocator != null) {
			activeServiceLocator.deactivate();
			activeServiceLocator = null;
		}

		final int pageIndex = getActivePage();
		final IKeyBindingService service = getSite().getKeyBindingService();
		if (pageIndex < 0 || pageIndex >= getPageCount() || immediate) {
			// There is no selected page, so deactivate the active service.
			if (service instanceof INestableKeyBindingService) {
				final INestableKeyBindingService nestableService = (INestableKeyBindingService) service;
				nestableService.activateKeyBindingService(null);
			} else {
				WorkbenchPlugin
						.log("MultiPageEditorPart.deactivateSite()   Parent key binding service was not an instance of INestableKeyBindingService.  It was an instance of " + service.getClass().getName() + " instead."); //$NON-NLS-1$ //$NON-NLS-2$
			}
		}
		
		if (containerSiteActive) {
			IServiceLocator containerSite = getPageContainerSite();
			if (containerSite instanceof INestable) {
				activeServiceLocator = (INestable) containerSite;
				activeServiceLocator.activate();
			}
		}
	}
	
	/**
	 * This method can be used by implementors of
	 * {@link #createPageContainer(Composite)} to activate the active inner
	 * editor services when their header loses focus.
	 * <p>
	 * An new inner editor will have its site activated on a
	 * {@link #pageChange(int)}.
	 * </p>
	 * <p>
	 * <b>Note:</b> This API is evolving in 3.4 and this might not be its final
	 * form.
	 * </p>
	 * 
	 * @since 3.4
	 * @see #deactivateSite(boolean,boolean)
	 * @see #createPageContainer(Composite)
	 * @see #getPageSite(int)
	 */
	protected final void activateSite() {
		if (activeServiceLocator != null) {
			activeServiceLocator.deactivate();
			activeServiceLocator = null;
		}

		final IKeyBindingService service = getSite().getKeyBindingService();
		final int pageIndex = getActivePage();
		final IEditorPart editor = getEditor(pageIndex);

		if (editor != null) {
			// active the service for this inner editor
			if (service instanceof INestableKeyBindingService) {
				final INestableKeyBindingService nestableService = (INestableKeyBindingService) service;
				nestableService.activateKeyBindingService(editor
						.getEditorSite());

			} else {
				WorkbenchPlugin
						.log("MultiPageEditorPart.activateSite()   Parent key binding service was not an instance of INestableKeyBindingService.  It was an instance of " + service.getClass().getName() + " instead."); //$NON-NLS-1$ //$NON-NLS-2$
			}
			// Activate the services for the new service locator.
			final IServiceLocator serviceLocator = editor.getEditorSite();
			if (serviceLocator instanceof INestable) {
				activeServiceLocator = (INestable) serviceLocator;
				activeServiceLocator.activate();
			}

		} else {
			Item item = getItem(pageIndex);

			// There is no selected editor, so deactivate the active service.
			if (service instanceof INestableKeyBindingService) {
				final INestableKeyBindingService nestableService = (INestableKeyBindingService) service;
				nestableService.activateKeyBindingService(null);
			} else {
				WorkbenchPlugin
						.log("MultiPageEditorPart.activateSite()   Parent key binding service was not an instance of INestableKeyBindingService.  It was an instance of " + service.getClass().getName() + " instead."); //$NON-NLS-1$ //$NON-NLS-2$
			}

			if (item.getData() instanceof INestable) {
				activeServiceLocator = (INestable) item.getData();
				activeServiceLocator.activate();
			}
		}
	}

	/**
	 * Disposes the given part and its site.
	 * 
	 * @param part
	 *            The part to dispose; must not be <code>null</code>.
	 */
	private void disposePart(final IWorkbenchPart part) {
		SafeRunner.run(new ISafeRunnable() {
			public void run() {
				IWorkbenchPartSite partSite = part.getSite();
				part.dispose();
				if (partSite instanceof MultiPageEditorSite) {
					((MultiPageEditorSite) partSite).dispose();
				}
			}

			public void handleException(Throwable e) {
				// Exception has already being logged by Core. Do nothing.
			}
		});
	}

	/**
	 * Removes the page with the given index from this multi-page editor. The
	 * controls for the page are disposed of; if the page has an editor, it is
	 * disposed of too. The page index must be valid.
	 * 
	 * @param pageIndex
	 *            the index of the page
	 * @see MultiPageEditorPart#addPage(Control)
	 * @see MultiPageEditorPart#addPage(IEditorPart, IEditorInput)
	 */
	public void removePage(int pageIndex) {
		Assert.isTrue(pageIndex >= 0 && pageIndex < getPageCount());
		// get editor (if any) before disposing item
		IEditorPart editor = getEditor(pageIndex);

		// get control for the item if it's not an editor
		CTabItem item = getItem(pageIndex);
		IServiceLocator pageLocator = null;
		if (item.getData() instanceof IServiceLocator) {
			pageLocator = (IServiceLocator) item.getData();
		}
		Control pageControl = item.getControl();

		// dispose item before disposing editor, in case there's an exception
		// in editor's dispose
		item.dispose();

		if (pageControl != null) {
			pageControl.dispose();
		}

		// dispose editor (if any)
		if (editor != null) {
			nestedEditors.remove(editor);
			disposePart(editor);
		}
		if (pageLocator != null) {
			pageSites.remove(pageLocator);
			if (pageLocator instanceof IDisposable) {
				((IDisposable) pageLocator).dispose();
			}
		}
	}

	/**
	 * Sets the currently active page.
	 * 
	 * @param pageIndex
	 *            the index of the page to be activated; the index must be valid
	 */
	protected void setActivePage(int pageIndex) {
		Assert.isTrue(pageIndex >= 0 && pageIndex < getPageCount());
		getTabFolder().setSelection(pageIndex);
		pageChange(pageIndex);
	}

	/**
	 * Sets the control for the given page index. The page index must be valid.
	 * 
	 * @param pageIndex
	 *            the index of the page
	 * @param control
	 *            the control for the specified page, or <code>null</code> to
	 *            clear the control
	 */
	protected void setControl(int pageIndex, Control control) {
		getItem(pageIndex).setControl(control);
	}

	/**
	 * The <code>MultiPageEditor</code> implementation of this
	 * <code>IWorkbenchPart</code> method sets focus on the active nested
	 * editor, if there is one.
	 * <p>
	 * Subclasses may extend or reimplement.
	 * </p>
	 */
	public void setFocus() {
		setFocus(getActivePage());
	}

	/**
	 * Sets focus to the control for the given page. If the page has an editor,
	 * this calls its <code>setFocus()</code> method. Otherwise, this calls
	 * <code>setFocus</code> on the control for the page.
	 * 
	 * @param pageIndex
	 *            the index of the page
	 */
	private void setFocus(int pageIndex) {
		if (pageIndex < 0 || pageIndex >= getPageCount()) {
			// page index out of bounds, don't set focus.
			return;
		}
		final IEditorPart editor = getEditor(pageIndex);
		if (editor != null) {
			editor.setFocus();

		} else {
			// Give the page's control focus.
			final Control control = getControl(pageIndex);
			if (control != null) {
				control.setFocus();
			}
		}
	}

	/**
	 * Sets the image for the page with the given index, or <code>null</code>
	 * to clear the image for the page. The page index must be valid.
	 * 
	 * @param pageIndex
	 *            the index of the page
	 * @param image
	 *            the image, or <code>null</code>
	 */
	protected void setPageImage(int pageIndex, Image image) {
		getItem(pageIndex).setImage(image);
	}

	/**
	 * Sets the text label for the page with the given index. The page index
	 * must be valid. The text label must not be null.
	 * 
	 * @param pageIndex
	 *            the index of the page
	 * @param text
	 *            the text label
	 */
	protected void setPageText(int pageIndex, String text) {
		getItem(pageIndex).setText(text);
	}

	/**
	 * If there is an adapter registered against the subclass of
	 * MultiPageEditorPart return that. Otherwise, delegate to the internal
	 * editor.
	 * 
	 * @see org.eclipse.ui.part.WorkbenchPart#getAdapter(java.lang.Class)
	 */
	public Object getAdapter(Class adapter) {
		Object result = super.getAdapter(adapter);
		// restrict delegating to the UI thread for bug 144851
		if (result == null && Display.getCurrent()!=null) {
			IEditorPart innerEditor = getActiveEditor();
			// see bug 138823 - prevent some subclasses from causing
			// an infinite loop
			if (innerEditor != null && innerEditor != this) {
				result = Util.getAdapter(innerEditor, adapter);
			}
		}
		return result;
	}
	
	/**
	 * Find the editors contained in this multi-page editor
	 * whose editor input match the provided input.
	 * @param input the editor input
	 * @return the editors contained in this multi-page editor
	 * whose editor input match the provided input
	 * @since 3.3
	 */
	public final IEditorPart[] findEditors(IEditorInput input) {
		List result = new ArrayList();
		int count = getPageCount();
		for (int i = 0; i < count; i++) {
			IEditorPart editor = getEditor(i);
			if (editor != null 
					&& editor.getEditorInput() != null
					&& editor.getEditorInput().equals(input)) {
				result.add(editor);
			}
		}
		return (IEditorPart[]) result.toArray(new IEditorPart[result.size()]);
	}
	
	/**
	 * Set the active page of this multi-page editor to the
	 * page that contains the given editor part. This method has
	 * no effect of the given editor part is not contained in this
	 * multi-page editor.
	 * @param editorPart the editor part
	 * @since 3.3
	 */
	public final void setActiveEditor(IEditorPart editorPart) {
		int count = getPageCount();
		for (int i = 0; i < count; i++) {
			IEditorPart editor = getEditor(i);
			if (editor == editorPart) {
				setActivePage(i);
				break;
			}
		}
	}

	/**
	 * Returns the selected page for the current active page index, either the
	 * IEditorPart for editors or the Control for other pages.
	 * <p>
	 * <b>Note:</b> clients may override this method to return a page
	 * appropriate for their editors. Maybe be <code>null</code>.
	 * </p>
	 * 
	 * @return The IEditorPart or Control representing the current active page,
	 *         or <code>null</code> if there are no active pages.
	 * @since 3.5
	 * @see #getActivePage()
	 */
	public Object getSelectedPage() {
		int index = getActivePage();
		if (index == -1) {
			return null;
		}
		IEditorPart editor = getEditor(index);
		if (editor != null) {
			return editor;
		}
		return getControl(index);
	}
	
	/**
	 * Add the page change listener to be notified when the page changes. The
	 * newly selected page will be the Object returned from
	 * {@link #getSelectedPage()}. In the default case, this will be the active
	 * page Control, IEditorPart, or <code>null</code>.
	 * <p>
	 * This method has no effect if the listener has already been added.
	 * </p>
	 * 
	 * @nooverride
	 * 
	 * @since 3.5
	 */
	public void addPageChangedListener(IPageChangedListener listener) {
		pageChangeListeners.add(listener);
	}

	/**
	 * Remove the page change listener.
	 * <p>
	 * This method has no effect if the listener is not in the list.
	 * </p>
	 * 
	 * @nooverride
	 * 
	 * @since 3.5
	 */
	public void removePageChangedListener(IPageChangedListener listener) {
		pageChangeListeners.remove(listener);
	}

	private void firePageChanged(final PageChangedEvent event) {
		Object[] listeners = pageChangeListeners.getListeners();
		for (int i = 0; i < listeners.length; ++i) {
			final IPageChangedListener l = (IPageChangedListener) listeners[i];
			SafeRunnable.run(new SafeRunnable() {
				public void run() {
					l.pageChanged(event);
				}
			});
		}
	}
}
