blob: 70c287a5643c22157b0d1474a9933fef38fd34a4 [file] [log] [blame]
/**********************************************************************
Copyright (c) 2000, 2002 IBM Corp. and others.
All rights reserved. This program and the accompanying materials
are made available under the terms of the Common Public License v1.0
which accompanies this distribution, and is available at
http://www.eclipse.org/legal/cpl-v10.html
Contributors:
IBM Corporation - Initial implementation
**********************************************************************/
package org.eclipse.ui.part;
import java.util.*;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.util.*;
import org.eclipse.jface.viewers.*;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.ui.*;
import org.eclipse.ui.internal.SubActionBars;
import org.eclipse.ui.internal.WorkbenchPlugin;
/**
* Abstract superclass of all multi-page workbench views.
* <p>
* Within the workbench there are many views which track the active part. If a
* part is activated these views display some properties for the active part. A
* simple example is the <code>Outline View</code>, which displays the outline for the
* active editor. To avoid loss of context when part activation changes, these
* views may implement a multi-page approach. A separate page is maintained within
* the view for each source view. If a part is activated the associated page for the
* part is brought to top. If a part is closed the associated page is disposed.
* <code>PageBookView</code> is a base implementation for multi page views.
* </p>
* <p>
* <code>PageBookView</code>s provide an <code>IPageSite</code> for each of
* their pages. This site is supplied during the page's initialization. The page
* may supply a selection provider for this site. <code>PageBookView</code>s deal
* with these selection providers in a similar way to a workbench page's
* <code>SelectionService</code>. When a page is made visible, if its site
* has a selection provider, then changes in the selection are listened for
* and the current selection is obtained and fired as a selection change event.
* Selection changes are no longer listened for when a page is made invisible.
* </p>
* <p>
* This class should be subclassed by clients wishing to define new
* multi-page views.
* </p>
* <p>
* When a <code>PageBookView</code> is created the following methods are
* invoked. Subclasses must implement these.
* <ul>
* <li><code>createDefaultPage</code> - called to create a default page for the
* view. This page is displayed when the active part in the workbench does not
* have a page.</li>
* <li><code>getBootstrapPart</code> - called to determine the active part in the
* workbench. A page will be created for this part</li>
* </ul>
* </p>
* <p>
* When a part is activated the base implementation does not know if a page should
* be created for the part. Therefore, it delegates creation to the subclass.
* <ul>
* <li><code>isImportant</code> - called when a workbench part is activated.
* Subclasses return whether a page should be created for the new part.</li>
* <li><code>doCreatePage</code> - called to create a page for a particular part
* in the workbench. This is only invoked when <code>isImportant</code> returns
* </code>true</code>.</li>
* </ul>
* </p>
* <p>
* When a part is closed the base implementation will destroy the page associated with
* the particular part. The page was created by a subclass, so the subclass must also
* destroy it. Subclasses must implement these.
* <ul>
* <li><code>doDestroyPage</code> - called to destroy a page for a particular
* part in the workbench.</li>
* </ul>
* </p>
*/
public abstract class PageBookView extends ViewPart implements IPartListener {
/**
* The pagebook control, or <code>null</code> if not initialized.
*/
private PageBook book;
/**
* The page record for the default page.
*/
private PageRec defaultPageRec;
/**
* Map from parts to part records (key type: <code>IWorkbenchPart</code>;
* value type: <code>PartRec</code>).
*/
private Map mapPartToRec = new HashMap();
/**
* Map from pages to view sites
* Note that view sites were not added to page recs to
* avoid breaking binary compatibility with previous builds
*/
private Map mapPageToSite = new HashMap();
/**
* The page rec which provided the current page or
* <code>null</code>
*/
private PageRec activeRec;
/**
* The action bar property listener.
*/
private IPropertyChangeListener actionBarPropListener =
new IPropertyChangeListener() {
public void propertyChange(PropertyChangeEvent event) {
if (event.getProperty().equals(SubActionBars.P_ACTION_HANDLERS)
&& activeRec != null
&& event.getSource() == activeRec.subActionBars) {
refreshGlobalActionHandlers();
}
}
};
/**
* Selection change listener to listen for page selection changes
*/
private ISelectionChangedListener selectionChangedListener =
new ISelectionChangedListener() {
public void selectionChanged(SelectionChangedEvent event) {
pageSelectionChanged(event);
}
};
/**
* Selection provider for this view's site
*/
private SelectionProvider selectionProvider = new SelectionProvider();
/**
* A data structure used to store the information about a single page
* within a pagebook view.
*/
protected static class PageRec {
/**
* The part.
*/
public IWorkbenchPart part;
/**
* The page.
*/
public IPage page;
/**
* The page's action bars
*/
public SubActionBars subActionBars;
/**
* Creates a new page record initialized to the given part and page.
*/
public PageRec(IWorkbenchPart part, IPage page) {
this.part = part;
this.page = page;
}
/**
* Disposes of this page record by <code>null</code>ing its fields.
*/
public void dispose() {
part = null;
page = null;
}
}
/**
* A selection provider/listener for this view.
* It is a selection provider fo this view's site.
*/
protected class SelectionProvider implements ISelectionProvider {
/**
* Selection change listeners.
*/
private ListenerList selectionChangedListeners = new ListenerList();
/* (non-Javadoc)
* Method declared on ISelectionProvider.
*/
public void addSelectionChangedListener(ISelectionChangedListener listener) {
selectionChangedListeners.add(listener);
}
/* (non-Javadoc)
* Method declared on ISelectionProvider.
*/
public ISelection getSelection() {
// get the selection provider from the current page
IPage currentPage = getCurrentPage();
// during workbench startup we may be in a state when
// there is no current page
if (currentPage == null)
return StructuredSelection.EMPTY;
IPageSite site = getPageSite(currentPage);
if (site == null)
return StructuredSelection.EMPTY;
ISelectionProvider selProvider = site.getSelectionProvider();
if (selProvider != null)
return selProvider.getSelection();
else
return StructuredSelection.EMPTY;
}
/* (non-Javadoc)
* Method declared on ISelectionProvider.
*/
public void removeSelectionChangedListener(ISelectionChangedListener listener) {
selectionChangedListeners.remove(listener);
}
/* (non-Javadoc)
* Method declared on ISelectionChangedListener.
*/
public void selectionChanged(SelectionChangedEvent event) {
// pass on the notification to listeners
Object[] listeners = selectionChangedListeners.getListeners();
for (int i = 0; i < listeners.length; i++) {
((ISelectionChangedListener) listeners[i]).selectionChanged(event);
}
}
/* (non-Javadoc)
* Method declared on ISelectionProvider.
*/
public void setSelection(ISelection selection) {
// get the selection provider from the current page
IPage currentPage = getCurrentPage();
// during workbench startup we may be in a state when
// there is no current page
if (currentPage == null)
return;
IPageSite site = getPageSite(currentPage);
if (site == null)
return;
ISelectionProvider selProvider = site.getSelectionProvider();
// and set its selection
if (selProvider != null)
selProvider.setSelection(selection);
}
}
/**
* Creates a new pagebook view.
*/
protected PageBookView() {
super();
}
/**
* Creates and returns the default page for this view.
* <p>
* Subclasses must implement this method.
* </p>
* <p>
* Subclasses must call initPage with the new page (if it is an
* <code>IPageBookViewPage</code>) before calling createControl
* on the page.
* </p>
*
* @param book the pagebook control
* @return the default page
*/
protected abstract IPage createDefaultPage(PageBook book);
/**
* Creates a page for a given part. Adds it to the pagebook but does
* not show it.
*/
private PageRec createPage(IWorkbenchPart part) {
PageRec rec = doCreatePage(part);
if (rec != null) {
mapPartToRec.put(part, rec);
preparePage(rec);
}
return rec;
}
/**
* Prepares the page in the given page rec for use
* in this view.
*/
private void preparePage(PageRec rec) {
IPageSite site = null;
if (rec.page instanceof IPageBookViewPage) {
site = ((IPageBookViewPage)rec.page).getSite();
}
if (site == null) {
// We will create a site for our use
site = new PageSite(getViewSite());
}
mapPageToSite.put(rec.page, site);
rec.subActionBars = (SubActionBars)site.getActionBars();
rec.subActionBars.addPropertyChangeListener(actionBarPropListener);
// for backward compability with IPage
rec.page.setActionBars(rec.subActionBars);
}
/**
* Initializes the given page with a page site.
* <p>
* Subclasses should call this method after
* the page is created but before creating its
* controls.
* </p>
* <p>
* Subclasses may override
* </p>
* @param the page to initialize
*/
protected void initPage(IPageBookViewPage page) {
try {
page.init(new PageSite(getViewSite()));
} catch (PartInitException e) {
WorkbenchPlugin.log(e.getMessage());
}
}
/**
* The <code>PageBookView</code> implementation of this <code>IWorkbenchPart</code>
* method creates a <code>PageBook</code> control with its default page showing.
* Subclasses may extend.
*/
public void createPartControl(Composite parent) {
// Create the page book.
book = new PageBook(parent, SWT.NONE);
// Create the default page rec.
IPage defaultPage = createDefaultPage(book);
defaultPageRec = new PageRec(null, defaultPage);
preparePage(defaultPageRec);
// Show the default page
showPageRec(defaultPageRec);
// Listen to part activation events.
getSite().getPage().addPartListener(this);
showBootstrapPart();
}
/**
* The <code>PageBookView</code> implementation of this
* <code>IWorkbenchPart</code> method cleans up all the pages.
* Subclasses may extend.
*/
public void dispose() {
// stop listening to part activation
getSite().getPage().removePartListener(this);
// Deref all of the pages.
activeRec = null;
if (defaultPageRec != null) {
// check for null since the default page may not have
// been created (ex. perspective never visible)
defaultPageRec.page.dispose();
defaultPageRec = null;
}
Map clone = (Map)((HashMap)mapPartToRec).clone();
Iterator enum = clone.values().iterator();
while (enum.hasNext()) {
PageRec rec = (PageRec) enum.next();
// Fix for bug 25818 -- to be cleaned up
if (rec.subActionBars != null) {
rec.subActionBars.dispose();
}
removePage(rec);
}
// Run super.
super.dispose();
}
/**
* Creates a new page in the pagebook for a particular part. This
* page will be made visible whenever the part is active, and will be
* destroyed with a call to <code>doDestroyPage</code>.
* <p>
* Subclasses must implement this method.
* </p>
* <p>
* Subclasses must call initPage with the new page (if it is an
* <code>IPageBookViewPage</code>) before calling createControl
* on the page.
* </p>
* @param part the input part
* @return the record describing a new page for this view
* @see #doDestroyPage
*/
protected abstract PageRec doCreatePage(IWorkbenchPart part);
/**
* Destroys a page in the pagebook for a particular part. This page
* was returned as a result from <code>doCreatePage</code>.
* <p>
* Subclasses must implement this method.
* </p>
*
* @param part the input part
* @param pageRecord a page record for the part
* @see #doCreatePage
*/
protected abstract void doDestroyPage(IWorkbenchPart part, PageRec pageRecord);
/**
* Returns the active, important workbench part for this view.
* <p>
* When the page book view is created it has no idea which part within
* the workbook should be used to generate the first page. Therefore, it
* delegates the choice to subclasses of <code>PageBookView</code>.
* </p><p>
* Implementors of this method should return an active, important part
* in the workbench or <code>null</code> if none found.
* </p><p>
* Subclasses must implement this method.
* </p>
*
* @return the active important part, or <code>null</code> if none
*/
protected abstract IWorkbenchPart getBootstrapPart();
/**
* Returns the part which contributed the current
* page to this view.
*
* @return the part which contributed the current page
* or <code>null</code> if no part contributed the current page
*/
protected IWorkbenchPart getCurrentContributingPart() {
if (activeRec == null)
return null;
return activeRec.part;
}
/**
* Returns the currently visible page for this view or
* <code>null</code> if no page is currently visible.
*
* @return the currently visible page
*/
public IPage getCurrentPage() {
if (activeRec == null)
return null;
return activeRec.page;
}
/**
* Returns the view site for the given page of this view.
*
* @param page the page
* @return the corresponding site, or <code>null</code> if not found
*/
protected PageSite getPageSite(IPage page) {
return (PageSite)mapPageToSite.get(page);
}
/**
* Returns the default page for this view.
*
* @return the default page
*/
public IPage getDefaultPage() {
return defaultPageRec.page;
}
/**
* Returns the pagebook control for this view.
*
* @return the pagebook control, or <code>null</code> if not initialized
*/
protected PageBook getPageBook() {
return book;
}
/**
* Returns the page record for the given part.
*
* @param part the part
* @return the corresponding page record, or <code>null</code> if not found
*/
protected PageRec getPageRec(IWorkbenchPart part) {
return (PageRec) mapPartToRec.get(part);
}
/**
* Returns the page record for the given page of this view.
*
* @param page the page
* @return the corresponding page record, or <code>null</code> if not found
*/
protected PageRec getPageRec(IPage page) {
Iterator enum = mapPartToRec.values().iterator();
while (enum.hasNext()) {
PageRec rec = (PageRec)enum.next();
if (rec.page == page)
return rec;
}
return null;
}
/**
* Returns whether the given part should be added to this view.
* <p>
* Subclasses must implement this method.
* </p>
*
* @param part the input part
* @return <code>true</code> if the part is relevant, and <code>false</code>
* otherwise
*/
protected abstract boolean isImportant(IWorkbenchPart part);
/* (non-Javadoc)
* Method declared on IViewPart.
*/
public void init(IViewSite site) throws PartInitException {
site.setSelectionProvider(selectionProvider);
super.init(site);
}
/**
* The <code>PageBookView</code> implementation of this <code>IPartListener</code>
* method shows the page when the given part is activated. Subclasses may extend.
*/
public void partActivated(IWorkbenchPart part) {
// Is this an important part? If not just return.
if (!isImportant(part))
return;
// Create a page for the part.
PageRec rec = getPageRec(part);
if (rec == null)
rec = createPage(part);
// Show the page.
if (rec != null) {
showPageRec(rec);
} else {
showPageRec(defaultPageRec);
}
}
/**
* The <code>PageBookView</code> implementation of this <code>IPartListener</code>
* method does nothing. Subclasses may extend.
*/
public void partBroughtToTop(IWorkbenchPart part) {
}
/**
* The <code>PageBookView</code> implementation of this <code>IPartListener</code>
* method deal with the closing of the active part. Subclasses may extend.
*/
public void partClosed(IWorkbenchPart part) {
PageRec rec = getPageRec(part);
// Update the active part.
if (activeRec != null && activeRec.part == part) {
activeRec.subActionBars.dispose();
// remove our selection listener
ISelectionProvider provider = ((PageSite)mapPageToSite.get(activeRec.page)).getSelectionProvider();
if (provider != null)
provider.removeSelectionChangedListener(selectionChangedListener);
activeRec = null;
showPageRec(defaultPageRec);
}
else {
// Fix for bug 25818 -- to be cleaned up
if (rec != null && rec.subActionBars != null) {
rec.subActionBars.dispose();
}
}
// Find and remove the part page.
if (rec != null)
removePage(rec);
}
/**
* The <code>PageBookView</code> implementation of this <code>IPartListener</code>
* method does nothing. Subclasses may extend.
*/
public void partDeactivated(IWorkbenchPart part) {
// Do nothing.
}
/**
* The <code>PageBookView</code> implementation of this <code>IPartListener</code>
* method does nothing. Subclasses may extend.
*/
public void partOpened(IWorkbenchPart part) {
}
/* (non-Javadoc)
* Refreshes the global actions for the active page.
*/
private void refreshGlobalActionHandlers() {
// Clear old actions.
IActionBars bars = getViewSite().getActionBars();
bars.clearGlobalActionHandlers();
// Set new actions.
Map newActionHandlers = activeRec.subActionBars.getGlobalActionHandlers();
if (newActionHandlers != null) {
Set keys = newActionHandlers.entrySet();
Iterator iter = keys.iterator();
while (iter.hasNext()) {
Map.Entry entry = (Map.Entry)iter.next();
bars.setGlobalActionHandler((String)entry.getKey(),
(IAction)entry.getValue());
}
}
}
/**
* Removes a page.
*/
private void removePage(PageRec rec) {
mapPageToSite.remove(rec.page);
mapPartToRec.remove(rec.part);
Control control = rec.page.getControl();
if (control != null && !control.isDisposed()) {
// Dispose the page's control so pages don't have to do this in their
// dispose method.
// The page's control is a child of this view's control so if this view
// is closed, the page's control will already be disposed.
control.dispose();
}
// free the page
doDestroyPage(rec.part, rec);
}
/* (non-Javadoc)
* Method declared on IWorkbenchPart.
*/
public void setFocus() {
if (activeRec == null)
book.setFocus();
else
activeRec.page.getControl().setFocus();
}
/**
* Handle page selection changes.
*/
private void pageSelectionChanged(SelectionChangedEvent event) {
// forward this change from a page to our site's selection provider
SelectionProvider provider = (SelectionProvider)getSite().getSelectionProvider();
if (provider != null)
provider.selectionChanged(event);
}
/**
* Shows a page for the active workbench part.
*/
private void showBootstrapPart() {
IWorkbenchPart part = getBootstrapPart();
if (part != null)
partActivated(part);
}
/**
* Shows page contained in the given page record in this view. The page record must
* be one from this pagebook view.
* <p>
* The <code>PageBookView</code> implementation of this method asks the
* pagebook control to show the given page's control, and records that the
* given page is now current. Subclasses may extend.
* </p>
*
* @param pageRec the page record containing the page to show
*/
protected void showPageRec(PageRec pageRec) {
// If already showing do nothing
if (activeRec == pageRec)
return;
// Hide old page.
if (activeRec != null) {
activeRec.subActionBars.deactivate();
// remove our selection listener
ISelectionProvider provider = ((PageSite)mapPageToSite.get(activeRec.page)).getSelectionProvider();
if (provider != null)
provider.removeSelectionChangedListener(selectionChangedListener);
}
// Show new page.
activeRec = pageRec;
Control pageControl = activeRec.page.getControl();
if (pageControl != null && !pageControl.isDisposed()) {
// Verify that the page control is not disposed
// If we are closing, it may have already been disposed
book.showPage(pageControl);
activeRec.subActionBars.activate();
refreshGlobalActionHandlers();
// add our selection listener
ISelectionProvider provider =
((PageSite)mapPageToSite.get(activeRec.page)).getSelectionProvider();
if (provider != null)
provider.addSelectionChangedListener(selectionChangedListener);
// Update action bars.
getViewSite().getActionBars().updateActionBars();
}
}
/**
* Returns the selectionProvider for this page book view.
*
* @return a SelectionProvider
*/
protected SelectionProvider getSelectionProvider() {
return selectionProvider;
}
}