blob: 8fe00e9d7fa55c579f1b436a2b0f1540d4a60bfe [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2001, 2007 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
* Jens Lukowski/Innoopract - initial renaming/restructuring
*
*******************************************************************************/
package org.eclipse.wst.sse.ui.internal.contentoutline;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.ListenerList;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.SafeRunner;
import org.eclipse.jface.action.GroupMarker;
import org.eclipse.jface.action.IContributionItem;
import org.eclipse.jface.action.IContributionManager;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.IStatusLineManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.util.DelegatingDragAdapter;
import org.eclipse.jface.util.DelegatingDropAdapter;
import org.eclipse.jface.util.SafeRunnable;
import org.eclipse.jface.util.TransferDragSourceListener;
import org.eclipse.jface.util.TransferDropTargetListener;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.IContentProvider;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.viewers.IPostSelectionProvider;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.DragSource;
import org.eclipse.swt.dnd.DropTarget;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.ISelectionListener;
import org.eclipse.ui.IWorkbenchActionConstants;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.part.IPageSite;
import org.eclipse.ui.part.IShowInSource;
import org.eclipse.ui.part.IShowInTarget;
import org.eclipse.ui.part.IShowInTargetList;
import org.eclipse.ui.part.ShowInContext;
import org.eclipse.ui.views.contentoutline.ContentOutlinePage;
import org.eclipse.ui.views.contentoutline.IContentOutlinePage;
import org.eclipse.wst.sse.ui.views.contentoutline.ContentOutlineConfiguration;
public class ConfigurableContentOutlinePage extends ContentOutlinePage implements IAdaptable {
/*
* Menu listener to create the additions group and add any menu items
* contributed by the configuration; required since the context menu is
* cleared every time it is shown
*/
class AdditionGroupAdder implements IMenuListener {
public void menuAboutToShow(IMenuManager manager) {
IContributionItem[] items = manager.getItems();
if (items.length > 0 && items[items.length - 1].getId() != null) {
manager.insertAfter(items[items.length - 1].getId(), new GroupMarker(IWorkbenchActionConstants.MB_ADDITIONS));
}
else {
manager.add(new GroupMarker(IWorkbenchActionConstants.MB_ADDITIONS));
}
// add configuration's menu items
IMenuListener listener = getConfiguration().getMenuListener(getTreeViewer());
if (listener != null) {
listener.menuAboutToShow(manager);
}
}
}
/**
* Provides double-click registration so it can be done before the Control
* is created.
*/
class DoubleClickProvider implements IDoubleClickListener {
private IDoubleClickListener[] listeners = null;
void addDoubleClickListener(IDoubleClickListener newListener) {
if (listeners == null) {
listeners = new IDoubleClickListener[]{newListener};
}
else {
IDoubleClickListener[] newListeners = new IDoubleClickListener[listeners.length + 1];
System.arraycopy(listeners, 0, newListeners, 0, listeners.length);
newListeners[listeners.length] = newListener;
listeners = newListeners;
}
}
public void doubleClick(DoubleClickEvent event) {
fireDoubleClickEvent(event);
}
private void fireDoubleClickEvent(final DoubleClickEvent event) {
IDoubleClickListener[] firingListeners = listeners;
for (int i = 0; i < firingListeners.length; ++i) {
final IDoubleClickListener l = firingListeners[i];
SafeRunner.run(new SafeRunnable() {
public void run() {
l.doubleClick(event);
}
});
}
}
void removeDoubleClickListener(IDoubleClickListener oldListener) {
if (listeners != null) {
if (listeners.length == 1 && listeners[0].equals(oldListener)) {
listeners = null;
}
else {
List newListeners = new ArrayList(Arrays.asList(listeners));
newListeners.remove(oldListener);
listeners = (IDoubleClickListener[]) newListeners.toArray(new IDoubleClickListener[listeners.length - 1]);
}
}
}
}
/**
* Listens to post selection from the selection service, applying it to
* the tree viewer.
*/
class PostSelectionServiceListener implements ISelectionListener {
public void selectionChanged(IWorkbenchPart part, ISelection selection) {
// from selection service
if (_DEBUG) {
_DEBUG_TIME = System.currentTimeMillis();
} /*
* Bug 136310, unless this page is that part's
* IContentOutlinePage, ignore the selection change
*/
if (part == null || part.getAdapter(IContentOutlinePage.class) == ConfigurableContentOutlinePage.this) {
ISelection validContentSelection = getConfiguration().getSelection(getTreeViewer(), selection);
boolean isLinked = getConfiguration().isLinkedWithEditor(getTreeViewer());
if (isLinked) {
getTreeViewer().setSelection(validContentSelection, true);
}
}
if (_DEBUG) {
System.out.println("(O:" + (System.currentTimeMillis() - _DEBUG_TIME) + "ms) " + part + " : " + selection); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
}
}
/**
* Forwards post-selection from the tree viewer to the listeners while
* acting as this page's selection provider.
*/
private class SelectionProvider implements IPostSelectionProvider {
private class PostSelectionChangedListener implements ISelectionChangedListener {
public void selectionChanged(SelectionChangedEvent event) {
if (!isFiringSelection()) {
fireSelectionChanged(event, postListeners);
updateStatusLine(getSite().getActionBars().getStatusLineManager(), event.getSelection());
}
}
}
private class SelectionChangedListener implements ISelectionChangedListener {
public void selectionChanged(SelectionChangedEvent event) {
if (!isFiringSelection()) {
fireSelectionChanged(event, listeners);
}
}
}
private boolean isFiringSelection = false;
private ListenerList listeners = new ListenerList();
private ListenerList postListeners = new ListenerList();
private ISelectionChangedListener postSelectionChangedListener = new PostSelectionChangedListener();
private ISelectionChangedListener selectionChangedListener = new SelectionChangedListener();
public void addPostSelectionChangedListener(ISelectionChangedListener listener) {
postListeners.add(listener);
}
public void addSelectionChangedListener(ISelectionChangedListener listener) {
listeners.add(listener);
}
public void fireSelectionChanged(final SelectionChangedEvent event, ListenerList listenerList) {
isFiringSelection = true;
Object[] listeners = listenerList.getListeners();
for (int i = 0; i < listeners.length; ++i) {
final ISelectionChangedListener l = (ISelectionChangedListener) listeners[i];
SafeRunner.run(new SafeRunnable() {
public void run() {
l.selectionChanged(event);
}
});
}
isFiringSelection = false;
}
public ISelectionChangedListener getPostSelectionChangedListener() {
return postSelectionChangedListener;
}
public ISelection getSelection() {
if (getTreeViewer() != null) {
return getTreeViewer().getSelection();
}
return StructuredSelection.EMPTY;
}
public ISelectionChangedListener getSelectionChangedListener() {
return selectionChangedListener;
}
public boolean isFiringSelection() {
return isFiringSelection;
}
public void removePostSelectionChangedListener(ISelectionChangedListener listener) {
postListeners.remove(listener);
}
public void removeSelectionChangedListener(ISelectionChangedListener listener) {
listeners.remove(listener);
}
public void setSelection(ISelection selection) {
if (!isFiringSelection) {
getTreeViewer().setSelection(selection);
}
}
}
private class ShowInTarget implements IShowInTarget {
/*
* @see org.eclipse.ui.part.IShowInTarget#show(org.eclipse.ui.part.ShowInContext)
*/
public boolean show(ShowInContext context) {
setSelection(context.getSelection());
return getTreeViewer().getSelection().equals(context.getSelection());
}
}
protected static final ContentOutlineConfiguration NULL_CONFIGURATION = new ContentOutlineConfiguration() {
public IContentProvider getContentProvider(TreeViewer viewer) {
return new ITreeContentProvider() {
public void dispose() {
}
public Object[] getChildren(Object parentElement) {
return null;
}
public Object[] getElements(Object inputElement) {
return null;
}
public Object getParent(Object element) {
return null;
}
public boolean hasChildren(Object element) {
return false;
}
public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
}
};
}
};
private static final String OUTLINE_CONTEXT_MENU_ID = "org.eclipse.wst.sse.ui.StructuredTextEditor.OutlineContext"; //$NON-NLS-1$
private static final String OUTLINE_CONTEXT_MENU_SUFFIX = ".source.OutlineContext"; //$NON-NLS-1$
private static final boolean _DEBUG = "true".equalsIgnoreCase(Platform.getDebugOption("org.eclipse.wst.sse.ui/contentOutline")); //$NON-NLS-1$ //$NON-NLS-2$;
private long _DEBUG_TIME = 0;
private TransferDragSourceListener[] fActiveDragListeners;
private TransferDropTargetListener[] fActiveDropListeners;
private ContentOutlineConfiguration fConfiguration;
private Menu fContextMenu;
private String fContextMenuId;
private MenuManager fContextMenuManager;
private DoubleClickProvider fDoubleClickProvider = null;
private DelegatingDragAdapter fDragAdapter;
private DragSource fDragSource;
private DelegatingDropAdapter fDropAdapter;
private DropTarget fDropTarget;
private IEditorPart fEditor;
private IMenuListener fGroupAdder = null;
private Object fInput = null;
private String fInputContentTypeIdentifier = null;
private ISelectionListener fSelectionListener = null;
SelectionProvider fSelectionProvider = null;
/**
* A ContentOutlinePage that abstract as much behavior as possible away
* from the Controls and varies it by content type.
*/
public ConfigurableContentOutlinePage() {
super();
fGroupAdder = new AdditionGroupAdder();
fSelectionProvider = new SelectionProvider();
}
/**
* Adds a listener to a list of those notified when someone double-clicks
* in the page.
*
* @param newListener -
* the listener to add
*/
public void addDoubleClickListener(IDoubleClickListener newListener) {
if (fDoubleClickProvider == null) {
fDoubleClickProvider = new DoubleClickProvider();
}
fDoubleClickProvider.addDoubleClickListener(newListener);
}
private String computeContextMenuID() {
String id = null;
if (fInputContentTypeIdentifier != null) {
id = fInputContentTypeIdentifier + OUTLINE_CONTEXT_MENU_SUFFIX;
}
return id;
}
/**
* @see ContentOutlinePage#createControl
*/
public void createControl(Composite parent) {
super.createControl(parent);
IWorkbenchPage page = getSite().getWorkbenchWindow().getActivePage();
if (page != null) {
fEditor = page.getActiveEditor();
}
fDragAdapter = new DelegatingDragAdapter();
fDragSource = new DragSource(getControl(), DND.DROP_COPY | DND.DROP_MOVE);
fDropAdapter = new DelegatingDropAdapter();
fDropTarget = new DropTarget(getControl(), DND.DROP_COPY | DND.DROP_MOVE);
setConfiguration(getConfiguration());
/*
* ContentOutlinePage only implements ISelectionProvider while the
* tree viewer implements both ISelectionProvider and
* IPostSelectionProvider. Use an ISelectionProvider that listens to
* post selection from the tree viewer and forward only post selection
* to the selection service.
*/
getTreeViewer().addPostSelectionChangedListener(fSelectionProvider.getPostSelectionChangedListener());
getTreeViewer().addSelectionChangedListener(fSelectionProvider.getSelectionChangedListener());
if (fDoubleClickProvider == null) {
fDoubleClickProvider = new DoubleClickProvider();
}
getTreeViewer().addDoubleClickListener(fDoubleClickProvider);
getSite().setSelectionProvider(fSelectionProvider);
}
public void dispose() {
getSite().getWorkbenchWindow().getSelectionService().removePostSelectionListener(getSelectionServiceListener());
if (fDoubleClickProvider != null) {
getTreeViewer().removeDoubleClickListener(fDoubleClickProvider);
}
// dispose menu controls
if (fContextMenu != null) {
fContextMenu.dispose();
}
if (fContextMenuManager != null) {
fContextMenuManager.removeMenuListener(fGroupAdder);
fContextMenuManager.removeAll();
fContextMenuManager.dispose();
}
fDropTarget.dispose();
fDragSource.dispose();
IStatusLineManager statusLineManager = getSite().getActionBars().getStatusLineManager();
if (statusLineManager != null) {
statusLineManager.setMessage(null);
}
setConfiguration(NULL_CONFIGURATION);
super.dispose();
}
/*
* (non-Javadoc)
*
* @see org.eclipse.core.runtime.IAdaptable#getAdapter(java.lang.Class)
*/
public Object getAdapter(Class key) {
Object adapter = null;
if (key.equals(IShowInTarget.class)) {
adapter = new ShowInTarget();
}
final IEditorPart editor = fEditor;
if (key.equals(IShowInSource.class) && editor != null) {
adapter = new IShowInSource() {
public ShowInContext getShowInContext() {
return new ShowInContext(editor.getEditorInput(), editor.getEditorSite().getSelectionProvider().getSelection());
}
};
}
else if (key.equals(IShowInTargetList.class) && editor != null) {
adapter = editor.getAdapter(key);
}
return adapter;
}
/**
* @return the currently used ContentOutlineConfiguration
*/
public ContentOutlineConfiguration getConfiguration() {
if (fConfiguration == null) {
fConfiguration = NULL_CONFIGURATION;
}
return fConfiguration;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.viewers.ISelectionProvider#getSelection()
*/
public ISelection getSelection() {
return fSelectionProvider.getSelection();
}
ISelectionListener getSelectionServiceListener() {
if (fSelectionListener == null) {
fSelectionListener = new PostSelectionServiceListener();
}
return fSelectionListener;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.ui.part.IPageBookViewPage#init(org.eclipse.ui.part.IPageSite)
*/
public void init(IPageSite pageSite) {
super.init(pageSite);
pageSite.getWorkbenchWindow().getSelectionService().addPostSelectionListener(getSelectionServiceListener());
}
/**
* Removes a listener to a list of those notified when someone
* double-clicks in the page.
*
* @param oldListener -
* the listener to remove
*/
public void removeDoubleClickListener(IDoubleClickListener oldListener) {
if (fDoubleClickProvider != null) {
fDoubleClickProvider.removeDoubleClickListener(oldListener);
}
}
/**
* Configures (or reconfigures) the page according to the given
* configuration.
*
* @param configuration
*/
public void setConfiguration(ContentOutlineConfiguration configuration) {
// intentionally do not check to see if the new configuration != old
// configuration
if (getTreeViewer() != null) {
// remove the key listeners
if (getControl() != null && !getControl().isDisposed()) {
KeyListener[] listeners = getConfiguration().getKeyListeners(getTreeViewer());
if (listeners != null) {
for (int i = 0; i < listeners.length; i++) {
getControl().removeKeyListener(listeners[i]);
}
}
}
IContributionManager toolbar = getSite().getActionBars().getToolBarManager();
if (toolbar != null) {
IContributionItem[] toolbarItems = getConfiguration().getToolbarContributions(getTreeViewer());
if (toolbarItems != null && toolbarItems.length > 0) {
for (int i = 0; i < toolbarItems.length; i++) {
toolbar.remove(toolbarItems[i]);
}
toolbar.update(false);
}
}
IContributionManager menubar = getSite().getActionBars().getMenuManager();
if (menubar != null) {
IContributionItem[] menuItems = getConfiguration().getMenuContributions(getTreeViewer());
if (menuItems != null && menuItems.length > 0) {
for (int i = 0; i < menuItems.length; i++) {
menubar.remove(menuItems[i]);
}
menubar.remove(IWorkbenchActionConstants.MB_ADDITIONS);
menubar.update(false);
}
}
// clear the DnD listeners and transfer types
if (fDragAdapter != null && !fDragAdapter.isEmpty() && fDragSource != null && !fDragSource.isDisposed() && fDragSource.getTransfer().length > 0) {
if (fActiveDragListeners != null) {
for (int i = 0; i < fActiveDragListeners.length; i++) {
fDragAdapter.removeDragSourceListener(fActiveDragListeners[i]);
}
}
fActiveDragListeners = null;
fDragSource.removeDragListener(fDragAdapter);
fDragSource.setTransfer(new Transfer[0]);
}
if (fDropAdapter != null && !fDropAdapter.isEmpty() && fDropTarget != null && !fDropTarget.isDisposed() && fDropTarget.getTransfer().length > 0) {
if (fActiveDropListeners != null) {
for (int i = 0; i < fActiveDropListeners.length; i++) {
fDropAdapter.removeDropTargetListener(fActiveDropListeners[i]);
}
}
fActiveDropListeners = null;
fDropTarget.removeDropListener(fDropAdapter);
fDropTarget.setTransfer(new Transfer[0]);
}
getConfiguration().getContentProvider(getTreeViewer()).inputChanged(getTreeViewer(), fInput, null);
// release any ties to this tree viewer
getConfiguration().unconfigure(getTreeViewer());
}
fConfiguration = configuration;
if (getTreeViewer() != null && getControl() != null && !getControl().isDisposed()) {
// (re)set the providers
getTreeViewer().setLabelProvider(getConfiguration().getLabelProvider(getTreeViewer()));
getTreeViewer().setContentProvider(getConfiguration().getContentProvider(getTreeViewer()));
// view toolbar
IContributionManager toolbar = getSite().getActionBars().getToolBarManager();
if (toolbar != null) {
IContributionItem[] toolbarItems = getConfiguration().getToolbarContributions(getTreeViewer());
if (toolbarItems != null) {
for (int i = 0; i < toolbarItems.length; i++) {
toolbar.add(toolbarItems[i]);
}
toolbar.update(true);
}
}
// view menu
IContributionManager menu = getSite().getActionBars().getMenuManager();
if (menu != null) {
IContributionItem[] menuItems = getConfiguration().getMenuContributions(getTreeViewer());
if (menuItems != null) {
for (int i = 0; i < menuItems.length; i++) {
menuItems[i].setVisible(true);
menu.add(menuItems[i]);
menuItems[i].update();
}
menu.update(true);
}
}
// add the allowed DnD listeners and types
TransferDragSourceListener[] dragListeners = getConfiguration().getTransferDragSourceListeners(getTreeViewer());
if (fDragAdapter != null && dragListeners.length > 0) {
for (int i = 0; i < dragListeners.length; i++) {
fDragAdapter.addDragSourceListener(dragListeners[i]);
}
fActiveDragListeners = dragListeners;
fDragSource.addDragListener(fDragAdapter);
fDragSource.setTransfer(fDragAdapter.getTransfers());
}
TransferDropTargetListener[] dropListeners = getConfiguration().getTransferDropTargetListeners(getTreeViewer());
if (fDropAdapter != null && dropListeners.length > 0) {
for (int i = 0; i < dropListeners.length; i++) {
fDropAdapter.addDropTargetListener(dropListeners[i]);
}
fActiveDropListeners = dropListeners;
fDropTarget.addDropListener(fDropAdapter);
fDropTarget.setTransfer(fDropAdapter.getTransfers());
}
// add the key listeners
KeyListener[] listeners = getConfiguration().getKeyListeners(getTreeViewer());
if (listeners != null) {
for (int i = 0; i < listeners.length; i++) {
getControl().addKeyListener(listeners[i]);
}
}
}
if (fInput != null) {
setInput(fInput);
}
}
/**
* @param editor
* The IEditorPart that "owns" this page. Used to support the
* "Show In..." menu.
*/
public void setEditorPart(IEditorPart editor) {
fEditor = editor;
}
/**
* @param newInput
* The input for the page's viewer. Should only be set after a
* configuration has been applied.
*/
public void setInput(Object newInput) {
fInput = newInput;
/*
* Intentionally not optimized for checking new input vs. old input so
* that any existing content providers can be updated
*/
if (getControl() != null && !getControl().isDisposed()) {
getTreeViewer().setInput(fInput);
updateContextMenuId();
}
}
/**
* @param id -
* the content type identifier to use for further extension
*/
public void setInputContentTypeIdentifier(String id) {
fInputContentTypeIdentifier = id;
}
/**
* Updates the outline page's context menu for the current input
*/
private void updateContextMenuId() {
String computedContextMenuId = null;
// update outline view's context menu control and ID
if (fEditor == null) {
IWorkbenchPage page = getSite().getWorkbenchWindow().getActivePage();
if (page != null) {
fEditor = page.getActiveEditor();
}
}
computedContextMenuId = computeContextMenuID();
if (computedContextMenuId == null) {
computedContextMenuId = OUTLINE_CONTEXT_MENU_ID;
}
/*
* Update outline context menu id if updating to a new id or if
* context menu is not already set up
*/
if (!computedContextMenuId.equals(fContextMenuId) || (fContextMenu == null)) {
fContextMenuId = computedContextMenuId;
if (getControl() != null && !getControl().isDisposed()) {
// dispose of previous context menu
if (fContextMenu != null) {
fContextMenu.dispose();
}
if (fContextMenuManager != null) {
fContextMenuManager.removeMenuListener(fGroupAdder);
fContextMenuManager.removeAll();
fContextMenuManager.dispose();
}
fContextMenuManager = new MenuManager(fContextMenuId, fContextMenuId);
fContextMenuManager.setRemoveAllWhenShown(true);
fContextMenuManager.addMenuListener(fGroupAdder);
fContextMenu = fContextMenuManager.createContextMenu(getControl());
getControl().setMenu(fContextMenu);
getSite().registerContextMenu(fContextMenuId, fContextMenuManager, this);
/*
* also register this menu for source page part and structured
* text outline view ids
*/
if (fEditor != null) {
String partId = fEditor.getSite().getId();
if (partId != null) {
getSite().registerContextMenu(partId + OUTLINE_CONTEXT_MENU_SUFFIX, fContextMenuManager, this);
}
}
getSite().registerContextMenu(OUTLINE_CONTEXT_MENU_ID, fContextMenuManager, this);
}
}
}
void updateStatusLine(IStatusLineManager mgr, ISelection selection) {
String text = null;
Image image = null;
ILabelProvider statusLineLabelProvider = getConfiguration().getStatusLineLabelProvider(getTreeViewer());
if (statusLineLabelProvider != null && selection instanceof IStructuredSelection && !selection.isEmpty()) {
Object firstElement = ((IStructuredSelection) selection).getFirstElement();
text = statusLineLabelProvider.getText(firstElement);
image = statusLineLabelProvider.getImage(firstElement);
}
if (image == null) {
mgr.setMessage(text);
}
else {
mgr.setMessage(image, text);
}
}
}