blob: 89bcb8d29b4526f509e178276849cb353f3288cb [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2003, 2015 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
* anton.leherbauer@windriver.com - bug 220599 make CommonNavigator a ShowInSource
* rob.stryker@jboss.com - bug 250198 Slight changes for easier subclassing
*******************************************************************************/
package org.eclipse.ui.navigator;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.PerformanceStats;
import org.eclipse.core.runtime.SafeRunner;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.ViewerFilter;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IMemento;
import org.eclipse.ui.ISaveablePart;
import org.eclipse.ui.ISaveablesLifecycleListener;
import org.eclipse.ui.ISaveablesSource;
import org.eclipse.ui.IViewSite;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.Saveable;
import org.eclipse.ui.SaveablesLifecycleEvent;
import org.eclipse.ui.actions.ActionGroup;
import org.eclipse.ui.internal.navigator.CommonNavigatorActionGroup;
import org.eclipse.ui.internal.navigator.NavigatorContentService;
import org.eclipse.ui.internal.navigator.NavigatorPlugin;
import org.eclipse.ui.internal.navigator.NavigatorSafeRunnable;
import org.eclipse.ui.part.ISetSelectionTarget;
import org.eclipse.ui.part.IShowInSource;
import org.eclipse.ui.part.IShowInTarget;
import org.eclipse.ui.part.ShowInContext;
import org.eclipse.ui.part.ViewPart;
/**
* <p>
* This class provides the IViewPart for the Common Navigator framework in the
* Eclipse workbench. This class also serves as the backbone for navigational
* viewers. The following types are used by this class to render the Common
* Navigator:
* <ul>
* <li>
* <p>
* {@link org.eclipse.ui.navigator.CommonViewer}: The viewer that renders the
* extensible tree. Creates and manages the lifecycle of the Navigator Content
* Service (described below).
* </p>
* </li>
* <li>
* <p>
* {@link org.eclipse.ui.navigator.NavigatorActionService}: Manages instances of
* {@link org.eclipse.ui.navigator.CommonActionProvider}s provided by individual
* extensions and content extensions.
* </p>
* </li>
* <li>
* <p>
* {@link org.eclipse.ui.navigator.INavigatorContentService}: Manages instances
* of Navigator Content Extensions. Instances are created as needed, and
* disposed of upon the disposal of the Navigator Content Service.
* </p>
* </li>
* </ul>
*
* <p>
* Clients that wish to define their own custom extensible navigator view using
* CommonNavigator need to specify an instance of the
* <b>org.eclipse.ui.views</b> extension point:
*
* <pre>
*
* &lt;extension
* point=&quot;org.eclipse.ui.views&quot;&gt;
* &lt;view
* name=&quot;My Custom View&quot;
* icon=&quot;relative/path/to/icon.gif&quot;
* category=&quot;org.acme.mycategory&quot;
* class=&quot;org.eclipse.ui.navigator.CommonNavigator&quot;
* id=&quot;org.acme.MyCustomNavigatorID&quot;&gt;
* &lt;/view&gt;
* &lt;/extension&gt;
*
* </pre>
*
* </p>
*
* <p>
* CommonNavigator gets its initial input (during initialization) from the
* Workbench by calling getSite().getPage().getInput(). This is done in
* {@link #getInitialInput()}. Clients may create a subclass of CommonNavigator
* to provide their own means of getting the initial input. Or they may access
* the {@link CommonViewer} and set its input directly after startup.
* </p>
*
* <p>
* In the IDE scenario, the default page input is IWorkspaceRoot, in the RCP
* scenario it is null and can be configured in the WorkbenchAdvisor.
* </p>
*
* <p>
* Clients that wish to extend the view menu provided via the
* <b>org.eclipse.ui.popupMenu</b>s extension may specify the the
* <i>popupMenuId</i> specified by <b>org.eclipse.ui.navigator.viewer</b> (or a
* nested <b>popupMenu</b> element) of their target viewer as their target menu
* id.
* </p>
*
* <p>
* This class may be instantiated or subclassed
* </p>
*
* @since 3.2
*/
public class CommonNavigator extends ViewPart implements ISetSelectionTarget, ISaveablePart, ISaveablesSource, IShowInTarget {
private static final String PERF_CREATE_PART_CONTROL= "org.eclipse.ui.navigator/perf/explorer/createPartControl"; //$NON-NLS-1$
private static final String HELP_CONTEXT = NavigatorPlugin.PLUGIN_ID + ".common_navigator"; //$NON-NLS-1$
/**
* <p>
* Used to track changes to the {@link #isLinkingEnabled}&nbsp;property.
* </p>
*
* Make sure this does not conflict with anything in IWorkbenchPartConstants.
*
*/
public static final int IS_LINKING_ENABLED_PROPERTY = 0x10000;
private CommonViewer commonViewer;
private CommonNavigatorManager commonManager;
private ActionGroup commonActionGroup;
/**
* To allow {@link #createCommonViewer(Composite)} to be subclassed
*
* @since 3.4
*/
protected IMemento memento;
private boolean isLinkingEnabled;
private String LINKING_ENABLED = "CommonNavigator.LINKING_ENABLED"; //$NON-NLS-1$
private LinkHelperService linkService;
/**
*
*/
public CommonNavigator() {
super();
}
/**
* <p>
* Create the CommonViewer part control and setup the default providers as
* necessary.
* </p>
*
*
* @see org.eclipse.ui.part.WorkbenchPart#createPartControl(org.eclipse.swt.widgets.Composite)
*/
@Override
public void createPartControl(Composite aParent) {
final PerformanceStats stats= PerformanceStats.getStats(PERF_CREATE_PART_CONTROL, this);
stats.startRun();
commonViewer = createCommonViewer(aParent);
commonViewer.setCommonNavigator(this);
try {
commonViewer.getControl().setRedraw(false);
INavigatorFilterService filterService = commonViewer
.getNavigatorContentService().getFilterService();
ViewerFilter[] visibleFilters = filterService.getVisibleFilters(true);
for (int i = 0; i < visibleFilters.length; i++) {
commonViewer.addFilter(visibleFilters[i]);
}
commonViewer.setSorter(new CommonViewerSorter());
/*
* make sure input is set after sorters and filters to avoid unnecessary
* refreshes
*/
commonViewer.setInput(getInitialInput());
getSite().setSelectionProvider(commonViewer);
setPartName(getConfigurationElement().getAttribute("name")); //$NON-NLS-1$
} finally {
commonViewer.getControl().setRedraw(true);
}
commonViewer.createFrameList();
/*
* Create the CommonNavigatorManager last because information about the
* state of the CommonNavigator is required for the initialization of
* the CommonNavigatorManager
*/
commonManager = createCommonManager();
if (memento != null) {
commonViewer.getNavigatorContentService().restoreState(memento);
}
commonActionGroup = createCommonActionGroup();
commonActionGroup.fillActionBars(getViewSite().getActionBars());
if (memento != null && commonActionGroup instanceof IMementoAware) {
((IMementoAware) commonActionGroup).restoreState(memento);
}
ISaveablesLifecycleListener saveablesLifecycleListener = new ISaveablesLifecycleListener() {
ISaveablesLifecycleListener siteSaveablesLifecycleListener = getSite()
.getService(ISaveablesLifecycleListener.class);
@Override
public void handleLifecycleEvent(SaveablesLifecycleEvent event) {
if (event.getEventType() == SaveablesLifecycleEvent.DIRTY_CHANGED) {
firePropertyChange(PROP_DIRTY);
}
siteSaveablesLifecycleListener.handleLifecycleEvent(event);
}
};
commonViewer.getNavigatorContentService()
.getSaveablesService().init(this, getCommonViewer(),
saveablesLifecycleListener);
commonViewer.addSelectionChangedListener(new ISelectionChangedListener() {
@Override
public void selectionChanged(SelectionChangedEvent event) {
firePropertyChange(PROP_DIRTY);
}});
String helpContext = commonViewer.getNavigatorContentService().getViewerDescriptor().getHelpContext();
if (helpContext == null)
helpContext = HELP_CONTEXT;
PlatformUI.getWorkbench().getHelpSystem().setHelp(commonViewer.getControl(), helpContext);
stats.endRun();
}
/**
* <p>
* Updates the title text and title tool tip. Called whenever the input of
* the viewer changes.
* </p>
* @since 3.4
*/
public void updateTitle() {
Object input = commonViewer.getInput();
if (input == null) {
setTitleToolTip(""); //$NON-NLS-1$
} else {
String inputToolTip = getFrameToolTipText(input);
setTitleToolTip(inputToolTip);
}
}
/**
* <p>
* Returns the tool tip text for the given element. Used as the tool tip
* text for the current frame, and for the view title tooltip.
* </p>
* @param anElement
* @return the tool tip text
* @since 3.4
*
*/
public String getFrameToolTipText(Object anElement) {
if (commonViewer == null)
return ""; //$NON-NLS-1$
return ((ILabelProvider) commonViewer.getLabelProvider())
.getText(anElement);
}
/**
* <p>
* Note: This method is for internal use only. Clients should not call this
* method.
* </p>
* <p>
* This method will be invoked when the DisposeListener is notified of the
* disposal of the Eclipse view part.
* </p>
*
* @see org.eclipse.ui.part.WorkbenchPart#dispose()
*/
@Override
public void dispose() {
if (commonManager != null) {
commonManager.dispose();
}
if (commonActionGroup != null) {
commonActionGroup.dispose();
}
super.dispose();
}
/**
* <p>
* Note: This method is for internal use only. Clients should not call this
* method.
* </p>
* @see org.eclipse.ui.part.ViewPart#init(org.eclipse.ui.IViewSite,
* org.eclipse.ui.IMemento)
*/
@Override
public void init(IViewSite aSite, IMemento aMemento)
throws PartInitException {
super.init(aSite, aMemento);
memento = aMemento;
if (memento != null) {
Integer linkingEnabledInteger = memento.getInteger(LINKING_ENABLED);
setLinkingEnabled(((linkingEnabledInteger != null) ? linkingEnabledInteger
.intValue() == 1
: false));
}
}
/**
*
* <p>
* Note: This method is for internal use only. Clients should not call this
* method.
* </p>
*
* @see org.eclipse.ui.part.ViewPart#saveState(org.eclipse.ui.IMemento)
*/
@Override
public void saveState(IMemento aMemento) {
aMemento.putInteger(LINKING_ENABLED, (isLinkingEnabled) ? 1 : 0);
super.saveState(aMemento);
commonManager.saveState(aMemento);
commonViewer.getNavigatorContentService().saveState(aMemento);
if (commonActionGroup instanceof IMementoAware) {
((IMementoAware) commonActionGroup).saveState(aMemento);
}
}
/**
* <p>
* Force the workbench to focus on the Common Navigator tree.
* </p>
*
* @see org.eclipse.ui.part.WorkbenchPart#setFocus()
*/
@Override
public void setFocus() {
if (commonViewer != null) {
commonViewer.getTree().setFocus();
}
}
/**
* <p>
* Set the selection to the Common Navigator tree, and expand nodes if
* necessary. Use caution when invoking this method as it can cause
* Navigator Content Extensions to load, thus causing plugin activation.
* </p>
*
* @see org.eclipse.ui.part.ISetSelectionTarget#selectReveal(org.eclipse.jface.viewers.ISelection)
*/
@Override
public void selectReveal(ISelection selection) {
if (commonViewer != null) {
commonViewer.setSelection(selection, true);
}
}
/**
* <p>
* Linking is handled by an action which listens for
* changes to the {@link CommonNavigator#IS_LINKING_ENABLED_PROPERTY}.
* Custom implementations that wish to override this functionality, need to
* override the action used by the default ActionGroup and listen for
* changes to the above property.
*
* @param toEnableLinking
* True enables linking the current selection with open editors
*/
public final void setLinkingEnabled(boolean toEnableLinking) {
isLinkingEnabled = toEnableLinking;
firePropertyChange(IS_LINKING_ENABLED_PROPERTY);
}
/**
* @return Whether linking the current selection with open editors is
* enabled.
*/
public final boolean isLinkingEnabled() {
return isLinkingEnabled;
}
/**
* <p>
* Provides access to the commonViewer used by the current CommonNavigator.
* The field will not be valid until after
* {@link #init(IViewSite, IMemento)}&nbsp;has been called by the
* Workbench.
* </p>
*
* @return The (already created) instance of Common Viewer.
*/
public CommonViewer getCommonViewer() {
return commonViewer;
}
/**
* @return The Navigator Content Service which populates this instance of
* Common Navigator
*/
public INavigatorContentService getNavigatorContentService() {
return getCommonViewer().getNavigatorContentService();
}
/**
* Returns an object which is an instance of the given class
* associated with this object. Returns <code>null</code> if
* no such object can be found.
*
* @param adapter the adapter class to look up
* @return a object castable to the given class,
* or <code>null</code> if this object does not
* have an adapter for the given class
*/
@Override
@SuppressWarnings("unchecked")
public <T> T getAdapter(Class<T> adapter) {
if (adapter == CommonViewer.class) {
return (T) getCommonViewer();
} else if (adapter == INavigatorContentService.class) {
return (T) getCommonViewer().getNavigatorContentService();
} else if (adapter == IShowInTarget.class) {
return (T) this;
} else if (adapter == IShowInSource.class) {
return (T) getShowInSource();
}
return super.getAdapter(adapter);
}
/**
* Returns the <code>IShowInSource</code> for this view.
*/
private IShowInSource getShowInSource() {
return new IShowInSource() {
@Override
public ShowInContext getShowInContext() {
return new ShowInContext(getCommonViewer().getInput(), getCommonViewer().getSelection());
}
};
}
/**
* @return The Navigator Content Service which populates this instance of
* Common Navigator
*/
public NavigatorActionService getNavigatorActionService() {
return commonManager.getNavigatorActionService();
}
/**
* Creates and initializes an instance of {@link CommonViewer}. The ID of
* the Eclipse view part will be used to create the viewer. The ID is
* important as some extensions indicate they should only be used with a
* particular viewer ID.
*
* @param aParent
* A composite parent to contain the Common Viewer
* @return An initialized instance of CommonViewer
*/
protected CommonViewer createCommonViewer(Composite aParent) {
CommonViewer aViewer = createCommonViewerObject(aParent);
initListeners(aViewer);
aViewer.getNavigatorContentService().restoreState(memento);
return aViewer;
}
/**
* Constructs and returns an instance of {@link CommonViewer}. The ID of
* the Eclipse view part will be used to create the viewer.
*
* Override this method if you want a subclass of the CommonViewer
*
* @param aParent
* A composite parent to contain the CommonViewer
* @return An instance of CommonViewer
* @since 3.4
*/
protected CommonViewer createCommonViewerObject(Composite aParent) {
return new CommonViewer(getViewSite().getId(), aParent,
SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);
}
/**
* <p>
* Adds the listeners to the Common Viewer.
* </p>
*
* @param viewer
* The viewer
* @since 2.0
*/
protected void initListeners(TreeViewer viewer) {
viewer.addDoubleClickListener(new IDoubleClickListener() {
@Override
public void doubleClick(final DoubleClickEvent event) {
SafeRunner.run(new NavigatorSafeRunnable() {
@Override
public void run() throws Exception {
handleDoubleClick(event);
}
});
}
});
}
/**
* <p>
* Note: This method is for internal use only. Clients should not call this
* method.
* </p>
*
* @param anEvent
* Supplied by the DoubleClick listener.
*/
protected void handleDoubleClick(DoubleClickEvent anEvent) {
IAction openHandler = getViewSite().getActionBars().getGlobalActionHandler(ICommonActionConstants.OPEN);
if(openHandler == null) {
IStructuredSelection selection = (IStructuredSelection) anEvent
.getSelection();
Object element = selection.getFirstElement();
TreeViewer viewer = getCommonViewer();
if (viewer.isExpandable(element)) {
viewer.setExpandedState(element, !viewer.getExpandedState(element));
}
}
}
/**
* <p>
* The Common Navigator Manager handles the setup of the Common Navigator
* Menu, manages updates to the ActionBars from
* {@link CommonActionProvider}&nbsp; extensions as the user's selection
* changes, and also updates the status bar based on the current selection.
*
* @return The Common Navigator Manager class which handles menu population
* and ActionBars
*
* @since 3.4
*/
protected CommonNavigatorManager createCommonManager() {
return new CommonNavigatorManager(this, memento);
}
/**
* <p>
* The ActionGroup is used to populate the ActionBars of Common Navigator
* View Part, and the returned implementation will have an opportunity to
* fill the ActionBars of the view as soon as it is created. ({@link ActionGroup#fillActionBars(org.eclipse.ui.IActionBars)}.
* </p>
* <p>
* The default implementation returns an action group which will add the
* following actions:
* <ul>
* <li>
* <p>
* Link with editor support. Allows the user to toggling linking the current
* selection with the active editors.
* </p>
* <li>
* <p>
* Collapse all. Collapses all expanded nodes.
* </p>
* <li>
* <p>
* Select Filters. Provides access to the "Select Filters" dialog that
* allows users to enable/disable filters and also the Content Extension
* activations.
* </p>
* </ul>
*
* @return The Action Group to be associated with the Common Navigator View
* Part.
*/
protected ActionGroup createCommonActionGroup() {
return new CommonNavigatorActionGroup(this, commonViewer, getLinkHelperService());
}
/**
* Used to provide the initial input for the {@link CommonViewer}. By default
* getSite().getPage().getInput() is used. Subclass this to return your desired
* input.
*
* @return The initial input for the viewer. Defaults to
* getSite().getPage().getInput()
* @since 3.4
*/
protected Object getInitialInput() {
return getSite().getPage().getInput();
}
@Override
public Saveable[] getSaveables() {
return getNavigatorContentService().getSaveablesService().getSaveables();
}
@Override
public Saveable[] getActiveSaveables() {
return getNavigatorContentService().getSaveablesService().getActiveSaveables();
}
@Override
public void doSave(IProgressMonitor monitor) {
// Ignore. This method is not called because CommonNavigator implements
// ISaveablesSource. All saves will go through the ISaveablesSource /
// Saveable protocol.
}
@Override
public void doSaveAs() {
// ignore
}
@Override
public boolean isDirty() {
Saveable[] saveables = getSaveables();
for (int i = 0; i < saveables.length; i++) {
if(saveables[i].isDirty()) {
return true;
}
}
return false;
}
@Override
public boolean isSaveAsAllowed() {
return false;
}
@Override
public boolean isSaveOnCloseNeeded() {
return isDirty();
}
@Override
public boolean show(ShowInContext context) {
IStructuredSelection selection = getSelection(context);
if (selection != null && !selection.isEmpty()) {
selectReveal(selection);
return true;
}
return false;
}
private IStructuredSelection getSelection(ShowInContext context) {
if (context == null)
return StructuredSelection.EMPTY;
ISelection selection = context.getSelection();
if (selection != null && !selection.isEmpty() && selection instanceof IStructuredSelection)
return (IStructuredSelection)selection;
Object input = context.getInput();
if (input instanceof IEditorInput) {
LinkHelperService lhs = getLinkHelperService();
return lhs.getSelectionFor((IEditorInput) input);
}
if (input != null) {
return new StructuredSelection(input);
}
return StructuredSelection.EMPTY;
}
/**
* @since 3.4
*/
protected synchronized LinkHelperService getLinkHelperService() {
if (linkService == null)
linkService = new LinkHelperService((NavigatorContentService)getCommonViewer().getNavigatorContentService());
return linkService;
}
/**
* @since 3.4
*/
protected IMemento getMemento() {
return memento;
}
/**
* @param mode
* @noreference This method is not intended to be referenced by clients.
* @nooverride This method is not intended to be re-implemented or extended by clients.
* @since 3.4
*/
public void setRootMode(int mode) {
// For subclassing
}
/**
* @return the root mode
* @noreference This method is not intended to be referenced by clients.
* @nooverride This method is not intended to be re-implemented or extended by clients.
* @since 3.4
*/
public int getRootMode() {
// For subclassing
return 0;
}
/**
* @param label
* @noreference This method is not intended to be referenced by clients.
* @nooverride This method is not intended to be re-implemented or extended by clients.
* @since 3.4
*/
public void setWorkingSetLabel(String label) {
// For subclassing
}
/**
* @return the working set label
* @noreference This method is not intended to be referenced by clients.
* @nooverride This method is not intended to be re-implemented or extended by clients.
* @since 3.4
*/
public String getWorkingSetLabel() {
// For subclassing
return null;
}
}