/*******************************************************************************
 * Copyright (c) 2001, 2020 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 * 
 * 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.action.Separator;
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.ColumnViewerToolTipSupport;
import org.eclipse.jface.viewers.DelegatingStyledCellLabelProvider;
import org.eclipse.jface.viewers.DelegatingStyledCellLabelProvider.IStyledLabelProvider;
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;
import org.eclipse.wst.sse.ui.views.contentoutline.ContentOutlineFilterProcessor;


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();
			// add configuration's menu items
			IMenuListener listener = getConfiguration().getMenuListener(getTreeViewer());
			if (listener != null) {
				listener.menuAboutToShow(manager);
				manager.add(new Separator());
			}
			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));
			}
		}
	}

	/**
	 * 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) {
					if (!getTreeViewer().getSelection().equals(validContentSelection)) {
						try {
							fIsReceivingSelection = true;
							getTreeViewer().setSelection(validContentSelection, true);
						}
						finally {
							fIsReceivingSelection = false;
						}
					}
				}
			}
			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() && !fIsReceivingSelection) {
					fireSelectionChanged(event, postListeners);
					updateStatusLine(getSite().getActionBars().getStatusLineManager(), event.getSelection());
				}
			}
		}

		private class SelectionChangedListener implements ISelectionChangedListener {
			public void selectionChanged(SelectionChangedEvent event) {
				if (!isFiringSelection() && !fIsReceivingSelection) {
					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 ShowInSource implements IShowInSource {
		/*
		 * Always return as an IShowInSource adapter, but a context only when
		 * valid.
		 * 
		 * @see org.eclipse.ui.part.IShowInSource#getShowInContext()
		 */
		public ShowInContext getShowInContext() {
			if (fEditor != null && fEditor.getEditorSite() != null) {
				return new ShowInContext(fEditor.getEditorInput(), fEditor.getEditorSite().getSelectionProvider().getSelection());
			}
			return null;
		}
	}

	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;

	boolean fIsReceivingSelection;

	/**
	 * 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);
		getTreeViewer().setUseHashlookup(true);
		
		ColumnViewerToolTipSupport.enableFor(getTreeViewer());

		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);
		}
		unconfigure();
		super.dispose();
	}

	@SuppressWarnings("unchecked")
	@Override
	public <T> T getAdapter(Class<T> key) {
		T adapter = null;
		if (key.equals(IShowInTarget.class)) {
			adapter = (T) new ShowInTarget();
		}
		else if (key.equals(IShowInSource.class)) {
			adapter = (T) new ShowInSource();
		}
		else if (key.equals(IShowInTargetList.class) && fEditor != null) {
			adapter = fEditor.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);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ui.views.contentoutline.ContentOutlinePage#selectionChanged(org.eclipse.jface.viewers.SelectionChangedEvent)
	 */
	public void selectionChanged(SelectionChangedEvent event) {
		if (!fIsReceivingSelection)
			super.selectionChanged(event);
	}

	/**
	 * 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
		unconfigure();

		fConfiguration = configuration;

		if (getTreeViewer() != null && getControl() != null && !getControl().isDisposed()) {
			// (re)set the providers
			ILabelProvider labelProvider = getConfiguration().getLabelProvider(getTreeViewer());
			if (labelProvider instanceof IStyledLabelProvider) {
				getTreeViewer().setLabelProvider(new DelegatingStyledCellLabelProvider((IStyledLabelProvider) labelProvider));
			}
			else {
				getTreeViewer().setLabelProvider(labelProvider);
			}
			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);
		}
	}

	/**
	 * Unconfigure the content outline page
	 */
	private void unconfigure() {
		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 && !toolbar.isEmpty()) {
				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 && !menubar.isEmpty()) {
				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());
		}
	}

	/**
	 * @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 && fEditor.getSite() != 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);
		}
	}

	public ContentOutlineFilterProcessor getOutlineFilterProcessor() {
		return getConfiguration().getOutlineFilterProcessor(getTreeViewer());
	}
}
