| /******************************************************************************* |
| * Copyright (c) 2000, 2009 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.e4.core.services.context.EclipseContextFactory; |
| import org.eclipse.e4.core.services.context.IEclipseContext; |
| import org.eclipse.e4.workbench.ui.internal.UISchedulerStrategy; |
| 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.Item; |
| import org.eclipse.swt.widgets.Shell; |
| 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.EditorSite; |
| 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.services.ServiceLocator; |
| 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(); |
| do { |
| if (control.traverse(detail)) |
| return; |
| if (control.getListeners(SWT.Traverse).length != 0) |
| return; |
| if (control instanceof Shell) |
| return; |
| control = control.getParent(); |
| } while (control != null); |
| } |
| } |
| }); |
| 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(); |
| } |
| } |
| }); |
| IEclipseContext e4Context = EclipseContextFactory.create( |
| ((EditorSite) getSite()).getContext(), |
| UISchedulerStrategy.getInstance()); |
| ((ServiceLocator) sl).setContext(e4Context); |
| 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(); |
| } |
| } |
| }); |
| IEclipseContext e4Context = EclipseContextFactory.create( |
| ((EditorSite) getSite()).getContext(), UISchedulerStrategy |
| .getInstance()); |
| ((ServiceLocator) pageContainerSite).setContext(e4Context); |
| } |
| 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); |
| } |
| }); |
| } |
| } |
| } |