/*******************************************************************************
 * Copyright (c) 2000, 2008 IBM Corporation, Nokia 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 com.nokia.carbide.search.system.ui.text;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.ISafeRunnable;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.SafeRunner;
import org.eclipse.core.runtime.Status;

import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableItem;

import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.util.OpenStrategy;
import org.eclipse.jface.viewers.DecoratingLabelProvider;
import org.eclipse.jface.viewers.IBaseLabelProvider;
import org.eclipse.jface.viewers.IOpenListener;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.OpenEvent;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredViewer;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;

import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.Region;

import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IMemento;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.actions.ActionFactory;
import org.eclipse.ui.part.IPageSite;
import org.eclipse.ui.part.Page;
import org.eclipse.ui.part.PageBook;
import org.eclipse.ui.progress.UIJob;
import org.eclipse.ui.texteditor.IUpdate;
import org.eclipse.ui.texteditor.IWorkbenchActionDefinitionIds;

import com.nokia.carbide.search.system.internal.ui.CopyToClipboardAction;
import com.nokia.carbide.search.system.internal.ui.SearchPlugin;
import com.nokia.carbide.search.system.internal.ui.SearchPluginImages;
import com.nokia.carbide.search.system.internal.ui.SelectAllAction;
import com.nokia.carbide.search.system.ui.IContextMenuConstants;
import com.nokia.carbide.search.system.ui.IQueryListener;
import com.nokia.carbide.search.system.ui.ISearchQuery;
import com.nokia.carbide.search.system.ui.ISearchResult;
import com.nokia.carbide.search.system.ui.ISearchResultListener;
import com.nokia.carbide.search.system.ui.ISearchResultPage;
import com.nokia.carbide.search.system.ui.ISearchResultViewPart;
import com.nokia.carbide.search.system.ui.NewSearchUI;
import com.nokia.carbide.search.system.ui.SearchResultEvent;
import com.nokia.carbide.search.system2.internal.ui.InternalSearchUI;
import com.nokia.carbide.search.system2.internal.ui.MatchFilterAction;
import com.nokia.carbide.search.system2.internal.ui.MatchFilterSelectionAction;
import com.nokia.carbide.search.system2.internal.ui.SearchMessages;
import com.nokia.carbide.search.system2.internal.ui.SearchView;
import com.nokia.carbide.search.system2.internal.ui.basic.views.CollapseAllAction;
import com.nokia.carbide.search.system2.internal.ui.basic.views.ExpandAllAction;
import com.nokia.carbide.search.system2.internal.ui.basic.views.INavigate;
import com.nokia.carbide.search.system2.internal.ui.basic.views.RemoveAllMatchesAction;
import com.nokia.carbide.search.system2.internal.ui.basic.views.RemoveMatchAction;
import com.nokia.carbide.search.system2.internal.ui.basic.views.RemoveSelectedMatchesAction;
import com.nokia.carbide.search.system2.internal.ui.basic.views.SetLayoutAction;
import com.nokia.carbide.search.system2.internal.ui.basic.views.ShowNextResultAction;
import com.nokia.carbide.search.system2.internal.ui.basic.views.ShowPreviousResultAction;
import com.nokia.carbide.search.system2.internal.ui.basic.views.TableViewerNavigator;
import com.nokia.carbide.search.system2.internal.ui.basic.views.TreeViewerNavigator;
import com.nokia.carbide.search.system2.internal.ui.text.AnnotationManagers;
import com.nokia.carbide.search.system2.internal.ui.text.PositionTracker;



/**
 * An abstract base implementation for classes showing
 * <code>AbstractTextSearchResult</code> instances. This class assumes that
 * the input element set via {@link AbstractTextSearchViewPage#setInput(ISearchResult,Object)}
 * is a subclass of {@link AbstractTextSearchResult}.
 * This result page supports a tree and/or a table presentation of search
 * results. Subclasses can determine which presentations they want to support at
 * construction time by passing the appropriate flags.
 * Subclasses must customize the viewers for each presentation with a label
 * provider and a content provider. <br>
 * Changes in the search result are handled by updating the viewer in the
 * <code>elementsChanged()</code> and <code>clear()</code> methods.
 * 
 * @since 3.0
 */
public abstract class AbstractTextSearchViewPage extends Page implements ISearchResultPage {
	private class UpdateUIJob extends UIJob {
		
		public UpdateUIJob() {
			super(SearchMessages.AbstractTextSearchViewPage_update_job_name); 
			setSystem(true);
		}
		
		public IStatus runInUIThread(IProgressMonitor monitor) {
			Control control= getControl();
			if (control == null || control.isDisposed()) {
				// disposed the control while the UI was posted.
				return Status.OK_STATUS;
			}
			runBatchedClear();
			runBatchedUpdates();
			if (hasMoreUpdates() || isQueryRunning()) {
				schedule(500);
			} else {
				fIsUIUpdateScheduled= false;
				turnOnDecoration();
                updateBusyLabel();
                if (fScheduleEnsureSelection) {
                    fScheduleEnsureSelection= false;
                    AbstractTextSearchResult result = getInput();
                    if (result != null && fViewer.getSelection().isEmpty()) {
                        navigateNext(true);
                    }
                }
			}
			fViewPart.updateLabel();
			return Status.OK_STATUS;
		}
		
		/* 
		 * Undocumented for testing only. Used to find UpdateUIJobs.
		 */
		public boolean belongsTo(Object family) {
			return family == AbstractTextSearchViewPage.this;
		}
	
	}
	
	private class SelectionProviderAdapter implements ISelectionProvider, ISelectionChangedListener {
		private ArrayList fListeners= new ArrayList(5);
		
		public void addSelectionChangedListener(ISelectionChangedListener listener) {
			fListeners.add(listener);
		}

		public ISelection getSelection() {
			return fViewer.getSelection();
		}

		public void removeSelectionChangedListener(ISelectionChangedListener listener) {
			fListeners.remove(listener);
		}

		public void setSelection(ISelection selection) {
			fViewer.setSelection(selection);
		}

		public void selectionChanged(SelectionChangedEvent event) {
			// forward to my listeners
			SelectionChangedEvent wrappedEvent= new SelectionChangedEvent(this, event.getSelection());
			for (Iterator listeners= fListeners.iterator(); listeners.hasNext();) {
				ISelectionChangedListener listener= (ISelectionChangedListener) listeners.next();
				listener.selectionChanged(wrappedEvent);
			}
		}

	}

	private volatile boolean fIsUIUpdateScheduled= false;
    private volatile boolean fScheduleEnsureSelection= false;
	private static final String KEY_LAYOUT = "com.nokia.carbide.search.system.resultpage.layout"; //$NON-NLS-1$
	
	/**
	 * An empty array.
	 */
	protected static final Match[] EMPTY_MATCH_ARRAY= new Match[0];
	
	private StructuredViewer fViewer;
	private Composite fViewerContainer;
	private Control fBusyLabel;
	private PageBook fPagebook;
	private boolean fIsBusyShown;
	private ISearchResultViewPart fViewPart;
	private Set fBatchedUpdates;
	private boolean fBatchedClearAll;
	
	private ISearchResultListener fListener;
	private IQueryListener fQueryListener;
	private MenuManager fMenu;
	private AbstractTextSearchResult fInput;
	// Actions
	private CopyToClipboardAction fCopyToClipboardAction;
	private Action fRemoveSelectedMatches;
	private Action fRemoveCurrentMatch;
	private Action fRemoveAllResultsAction;
	private Action fShowNextAction;
	private Action fShowPreviousAction;
	
	private ExpandAllAction fExpandAllAction;
	private CollapseAllAction fCollapseAllAction;
	
	private SetLayoutAction fFlatAction;
	private SetLayoutAction fHierarchicalAction;
	private int fCurrentLayout;
	private int fCurrentMatchIndex = 0;
	private String fId;
	private final int fSupportedLayouts;
	private SelectionProviderAdapter fViewerAdapter;
	private SelectAllAction fSelectAllAction;
	
	private IAction[] fFilterActions;
	private Integer fElementLimit;
	
	/**
	 * Flag (<code>value 1</code>) denoting flat list layout.
	 */
	public static final int FLAG_LAYOUT_FLAT = 1;
	/**
	 * Flag (<code>value 2</code>) denoting tree layout.
	 */
	public static final int FLAG_LAYOUT_TREE = 2;

	
	/**
	 * This constructor must be passed a combination of layout flags combined
	 * with bitwise or. At least one flag must be passed in (i.e. 0 is not a
	 * permitted value).
	 * 
	 * @param supportedLayouts
	 *            flags determining which layout options this page supports.
	 *            Must not be 0
	 * @see #FLAG_LAYOUT_FLAT
	 * @see #FLAG_LAYOUT_TREE
	 */
	protected AbstractTextSearchViewPage(int supportedLayouts) {
		fSupportedLayouts = supportedLayouts;
		initLayout();
		fRemoveAllResultsAction = new RemoveAllMatchesAction(this);
		fRemoveSelectedMatches = new RemoveSelectedMatchesAction(this);
		fRemoveCurrentMatch = new RemoveMatchAction(this);
		fShowNextAction = new ShowNextResultAction(this);
		fShowPreviousAction = new ShowPreviousResultAction(this);
		fCopyToClipboardAction = new CopyToClipboardAction();
		if ((supportedLayouts & FLAG_LAYOUT_TREE) != 0) {
			fExpandAllAction= new ExpandAllAction();
			fCollapseAllAction= new CollapseAllAction();
		}
		
		fSelectAllAction= new SelectAllAction();
		createLayoutActions();
		fBatchedUpdates = new HashSet();
		fBatchedClearAll= false;
		
		fListener = new ISearchResultListener() {
			public void searchResultChanged(SearchResultEvent e) {
				handleSearchResultChanged(e);
			}
		};
		fFilterActions= null;
		fElementLimit= null;
	}
	
	private void initLayout() {
		if (supportsTreeLayout())
			fCurrentLayout = FLAG_LAYOUT_TREE;
		else
			fCurrentLayout = FLAG_LAYOUT_FLAT;
	}

	/**
	 * Constructs this page with the default layout flags.
	 * 
	 * @see AbstractTextSearchViewPage#AbstractTextSearchViewPage(int)
	 */
	protected AbstractTextSearchViewPage() {
		this(FLAG_LAYOUT_FLAT);
	}
	

	private void createLayoutActions() {
		if (countBits(fSupportedLayouts) > 1) {
			fFlatAction = new SetLayoutAction(this, SearchMessages.AbstractTextSearchViewPage_flat_layout_label, SearchMessages.AbstractTextSearchViewPage_flat_layout_tooltip, FLAG_LAYOUT_FLAT); 
			fHierarchicalAction = new SetLayoutAction(this, SearchMessages.AbstractTextSearchViewPage_hierarchical_layout_label, SearchMessages.AbstractTextSearchViewPage_hierarchical_layout_tooltip, FLAG_LAYOUT_TREE); 
			SearchPluginImages.setImageDescriptors(fFlatAction, SearchPluginImages.T_LCL, SearchPluginImages.IMG_LCL_SEARCH_FLAT_LAYOUT);
			SearchPluginImages.setImageDescriptors(fHierarchicalAction, SearchPluginImages.T_LCL, SearchPluginImages.IMG_LCL_SEARCH_HIERARCHICAL_LAYOUT);
		}
	}
	
	private int countBits(int layoutFlags) {
		int bitCount = 0;
		for (int i = 0; i < 32; i++) {
			if (layoutFlags % 2 == 1)
				bitCount++;
			layoutFlags >>= 1;
		}
		return bitCount;
	}

	private boolean supportsTreeLayout() {
		return isLayoutSupported(FLAG_LAYOUT_TREE);
	}

	/**
	 * Returns a dialog settings object for this search result page. There will be
	 * one dialog settings object per search result page id.
	 * 
	 * @return the dialog settings for this search result page
	 * @see AbstractTextSearchViewPage#getID()
	 */
	protected IDialogSettings getSettings() {
		IDialogSettings parent = SearchPlugin.getDefault().getDialogSettings();
		IDialogSettings settings = parent.getSection(getID());
		if (settings == null)
			settings = parent.addNewSection(getID());
		return settings;
	}

	/**
	 * {@inheritDoc}
	 */
	public void setID(String id) {
		fId = id;
	}

	/**
	 * {@inheritDoc}
	 */
	public String getID() {
		return fId;
	}
	
	/**
	 * {@inheritDoc}
	 */
	public String getLabel() {
		AbstractTextSearchResult result= getInput();
		if (result == null)
			return ""; //$NON-NLS-1$
		return result.getLabel();
	}

	/**
	 * Opens an editor on the given element and selects the given range of text.
	 * If a search results implements a <code>IFileMatchAdapter</code>, match
	 * locations will be tracked and the current match range will be passed into
	 * this method.
	 * 
	 * @param match
	 *            the match to show
	 * @param currentOffset
	 *            the current start offset of the match
	 * @param currentLength
	 *            the current length of the selection
	 * @throws PartInitException
	 *             if an editor can't be opened
	 * 
	 * @see org.eclipse.core.filebuffers.ITextFileBufferManager
	 * @see IFileMatchAdapter
	 * @deprecated
	 */
	protected void showMatch(Match match, int currentOffset, int currentLength) throws PartInitException {
	}

	/**
	 * Opens an editor on the given element and selects the given range of text.
	 * If a search results implements a <code>IFileMatchAdapter</code>, match
	 * locations will be tracked and the current match range will be passed into
	 * this method.
	 * If the <code>activate</code> parameter is <code>true</code> the opened editor
	 * should have be activated. Otherwise the focus should not be changed.
	 * 
	 * @param match
	 *            the match to show
	 * @param currentOffset
	 *            the current start offset of the match
	 * @param currentLength
	 *            the current length of the selection
	 * @param activate 
	 * 			  whether to activate the editor.
	 * @throws PartInitException
	 *             if an editor can't be opened
	 * 
	 * @see org.eclipse.core.filebuffers.ITextFileBufferManager
	 * @see IFileMatchAdapter
	 */
	protected void showMatch(Match match, int currentOffset, int currentLength, boolean activate) throws PartInitException {
		showMatch(match, currentOffset, currentLength);
	}

	/**
	 * This method is called whenever the set of matches for the given elements
	 * changes. This method is guaranteed to be called in the UI thread. Note
	 * that this notification is asynchronous. i.e. further changes may have
	 * occurred by the time this method is called. They will be described in a
	 * future call.
	 * <p>The changed elements are evaluated by {@link #evaluateChangedElements(Match[], Set)}.</p>
	 * 
	 * @param objects
	 *            array of objects that has to be refreshed
	 */
	protected abstract void elementsChanged(Object[] objects);

	/**
	 * This method is called whenever all elements have been removed from the
	 * shown <code>AbstractSearchResult</code>. This method is guaranteed to
	 * be called in the UI thread. Note that this notification is asynchronous.
	 * i.e. further changes may have occurred by the time this method is called.
	 * They will be described in a future call.
	 */
	protected abstract void clear();

	/**
	 * Configures the given viewer. Implementers have to set at least a content
	 * provider and a label provider. This method may be called if the page was
	 * constructed with the flag <code>FLAG_LAYOUT_TREE</code>.
	 * 
	 * @param viewer the viewer to be configured
	 */
	protected abstract void configureTreeViewer(TreeViewer viewer);

	/**
	 * Configures the given viewer. Implementers have to set at least a content
	 * provider and a label provider. This method may be called if the page was
	 * constructed with the flag <code>FLAG_LAYOUT_FLAT</code>.
	 * 
	 * @param viewer the viewer to be configured
	 */
	protected abstract void configureTableViewer(TableViewer viewer);

	/**
	 * Fills the context menu for this page. Subclasses may override this
	 * method.
	 * 
	 * @param mgr the menu manager representing the context menu
	 */
	protected void fillContextMenu(IMenuManager mgr) {
		mgr.appendToGroup(IContextMenuConstants.GROUP_SHOW, fShowNextAction);
		mgr.appendToGroup(IContextMenuConstants.GROUP_SHOW, fShowPreviousAction);
		mgr.appendToGroup(IContextMenuConstants.GROUP_EDIT, fCopyToClipboardAction);
		if (getCurrentMatch() != null)
			mgr.appendToGroup(IContextMenuConstants.GROUP_REMOVE_MATCHES, fRemoveCurrentMatch);
		if (canRemoveMatchesWith(getViewer().getSelection()))
			mgr.appendToGroup(IContextMenuConstants.GROUP_REMOVE_MATCHES, fRemoveSelectedMatches);
		mgr.appendToGroup(IContextMenuConstants.GROUP_REMOVE_MATCHES, fRemoveAllResultsAction);
		
		if (getLayout() == FLAG_LAYOUT_TREE) {
			mgr.appendToGroup(IContextMenuConstants.GROUP_SHOW, fExpandAllAction);
		}
	}

	/**
	 * Determines whether the provided selection can be used to remove matches from the result.
	 * @param selection the selection to test
	 * @return returns <code>true</code> if the elements in the current selection can be removed.
	 * @since 3.2
	 */
	protected boolean canRemoveMatchesWith(ISelection selection) {
		return !selection.isEmpty();
	}

	/**
	 * {@inheritDoc}
	 */
	public void createControl(Composite parent) {
		fQueryListener = createQueryListener();
		fMenu = new MenuManager("#PopUp"); //$NON-NLS-1$
		fMenu.setRemoveAllWhenShown(true);
		fMenu.setParent(getSite().getActionBars().getMenuManager());
		fMenu.addMenuListener(new IMenuListener() {
			public void menuAboutToShow(IMenuManager mgr) {
				SearchView.createContextMenuGroups(mgr);
				fillContextMenu(mgr);
				fViewPart.fillContextMenu(mgr);
			}
		});
		fPagebook = new PageBook(parent, SWT.NULL);
		fPagebook.setLayoutData(new GridData(GridData.FILL_BOTH));
		fBusyLabel = createBusyControl();
		fViewerContainer = new Composite(fPagebook, SWT.NULL);
		fViewerContainer.setLayoutData(new GridData(GridData.FILL_BOTH));
		fViewerContainer.setSize(100, 100);
		fViewerContainer.setLayout(new FillLayout());

		fViewerAdapter= new SelectionProviderAdapter();
		getSite().setSelectionProvider(fViewerAdapter);
		// Register menu
		getSite().registerContextMenu(fViewPart.getViewSite().getId(), fMenu, fViewerAdapter);

		
		createViewer(fViewerContainer, fCurrentLayout);
		showBusyLabel(fIsBusyShown);
		NewSearchUI.addQueryListener(fQueryListener);

	}

	private Control createBusyControl() {
		Table busyLabel = new Table(fPagebook, SWT.NONE);
		TableItem item = new TableItem(busyLabel, SWT.NONE);
		item.setText(SearchMessages.AbstractTextSearchViewPage_searching_label); 
		busyLabel.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
		return busyLabel;
	}

	private synchronized void scheduleUIUpdate() {
		if (!fIsUIUpdateScheduled) {
			fIsUIUpdateScheduled= true;
			new UpdateUIJob().schedule();
		}
	}

	private IQueryListener createQueryListener() {
		return new IQueryListener() {
			public void queryAdded(ISearchQuery query) {
				// ignore
			}

			public void queryRemoved(ISearchQuery query) {
				// ignore
			}

			public void queryStarting(final ISearchQuery query) {
				final Runnable runnable1 = new Runnable() {
					public void run() {
						updateBusyLabel();
						AbstractTextSearchResult result = getInput();

						if (result == null || !result.getQuery().equals(query)) {
							return;
						}
						turnOffDecoration();
						scheduleUIUpdate();
					}


				};
				asyncExec(runnable1);
			}

			public void queryFinished(final ISearchQuery query) {
                // handle the end of the query in the UIUpdateJob, as ui updates
                // may not be finished here.
                postEnsureSelection();
			}
		};
	}

	/**
	 * Posts a  UI update to make sure an element is selected.
	 * @since 3.2
	 */
    protected void postEnsureSelection() {
        fScheduleEnsureSelection= true;
        scheduleUIUpdate();
    }


	private void updateBusyLabel() {
		AbstractTextSearchResult result = getInput();
		boolean shouldShowBusy = result != null && NewSearchUI.isQueryRunning(result.getQuery()) && result.getMatchCount() == 0;
		if (shouldShowBusy == fIsBusyShown)
			return;
		fIsBusyShown = shouldShowBusy;
		showBusyLabel(fIsBusyShown);
	}

	private void showBusyLabel(boolean shouldShowBusy) {
		if (shouldShowBusy)
			fPagebook.showPage(fBusyLabel);
		else
			fPagebook.showPage(fViewerContainer);
	}

	/**
	 * Determines whether a certain layout is supported by this search result
	 * page.
	 * 
	 * @param layout the layout to test for
	 * @return whether the given layout is supported or not
	 * 
	 * @see AbstractTextSearchViewPage#AbstractTextSearchViewPage(int)
	 */
	public boolean isLayoutSupported(int layout) {
		return (layout & fSupportedLayouts) == layout;
	}

	/**
	 * Sets the layout of this search result page. The layout must be on of
	 * <code>FLAG_LAYOUT_FLAT</code> or <code>FLAG_LAYOUT_TREE</code> and
	 * it must be one of the values passed during construction of this search 
	 * result page.
	 * @param layout the new layout
	 * 
	 * @see AbstractTextSearchViewPage#isLayoutSupported(int)
	 */
	public void setLayout(int layout) {
		Assert.isTrue(countBits(layout) == 1);
		Assert.isTrue(isLayoutSupported(layout));
		if (countBits(fSupportedLayouts) < 2)
			return;
		if (fCurrentLayout == layout)
			return;
		fCurrentLayout = layout;
		ISelection selection = fViewer.getSelection();
		disconnectViewer();
		disposeViewer();
		createViewer(fViewerContainer, layout);
		fViewerContainer.layout(true);
		connectViewer(fInput);
		fViewer.setSelection(selection, true);
		getSettings().put(KEY_LAYOUT, layout);
		getViewPart().updateLabel();
	}

	private void disposeViewer() {
		fViewer.removeSelectionChangedListener(fViewerAdapter);
		fViewer.getControl().dispose();
		fViewer = null;
	}

	private void updateLayoutActions() {
		if (fFlatAction != null)
			fFlatAction.setChecked(fCurrentLayout == fFlatAction.getLayout());
		if (fHierarchicalAction != null)
			fHierarchicalAction.setChecked(fCurrentLayout == fHierarchicalAction.getLayout());
	}

	/**
	 * Return the layout this page is currently using.
	 * 
	 * @return the layout this page is currently using
	 * 
	 * @see #FLAG_LAYOUT_FLAT
	 * @see #FLAG_LAYOUT_TREE
	 */
	public int getLayout() {
		return fCurrentLayout;
	}

	private void createViewer(Composite parent, int layout) {
		if ((layout & FLAG_LAYOUT_FLAT) != 0) {
			TableViewer viewer = createTableViewer(parent);
			fViewer = viewer;
			configureTableViewer(viewer);
		} else if ((layout & FLAG_LAYOUT_TREE) != 0) {
			TreeViewer viewer = createTreeViewer(parent);
			fViewer = viewer;
			configureTreeViewer(viewer);
			fCollapseAllAction.setViewer(viewer);
			fExpandAllAction.setViewer(viewer);
		}
		
		fCopyToClipboardAction.setViewer(fViewer);
		fSelectAllAction.setViewer(fViewer);
		
		IToolBarManager tbm = getSite().getActionBars().getToolBarManager();
		tbm.removeAll();
		SearchView.createToolBarGroups(tbm);
		fillToolbar(tbm);
		tbm.update(false);
		
		fViewer.addOpenListener(new IOpenListener() {
			public void open(OpenEvent event) {
				handleOpen(event);
			}
		});
		fViewer.addSelectionChangedListener(new ISelectionChangedListener() {
			public void selectionChanged(SelectionChangedEvent event) {
				fCurrentMatchIndex = -1;
				fRemoveSelectedMatches.setEnabled(canRemoveMatchesWith(event.getSelection()));
			}
		});
		
		fViewer.addSelectionChangedListener(fViewerAdapter);
		
		Menu menu = fMenu.createContextMenu(fViewer.getControl());
		fViewer.getControl().setMenu(menu);
		
		updateLayoutActions();
		getViewPart().updateLabel();
	}

	/**
	 * Creates the tree viewer to be shown on this page. Clients may override
	 * this method.
	 * 
	 * @param parent the parent widget
	 * @return returns a newly created <code>TreeViewer</code>.
	 */
	protected TreeViewer createTreeViewer(Composite parent) {
		return new TreeViewer(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);
	}

	/**
	 * Creates the table viewer to be shown on this page. Clients may override
	 * this method.
	 * 
	 * @param parent the parent widget
	 * @return returns a newly created <code>TableViewer</code>
	 */
	protected TableViewer createTableViewer(Composite parent) {
		return new TableViewer(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL | SWT.FULL_SELECTION);
	}

	/**
	 * {@inheritDoc}
	 */
	public void setFocus() {
		Control control = fViewer.getControl();
		if (control != null && !control.isDisposed())
			control.setFocus();
	}

	/**
	 * {@inheritDoc}
	 */
	public Control getControl() {
		return fPagebook;
	}

	/**
	 * {@inheritDoc}
	 */
	public void setInput(ISearchResult newSearch, Object viewState) {
		if (newSearch != null && !(newSearch instanceof AbstractTextSearchResult))
			return; // ignore
		
		AbstractTextSearchResult oldSearch= fInput;
		if (oldSearch != null) {
			disconnectViewer();
			removeFilterActionsFromViewMenu(fFilterActions);
			oldSearch.removeListener(fListener);
			AnnotationManagers.removeSearchResult(getSite().getWorkbenchWindow(), oldSearch);
		}
		fInput= (AbstractTextSearchResult) newSearch;
		
		if (fInput != null) {
			AnnotationManagers.addSearchResult(getSite().getWorkbenchWindow(), fInput);
			
			fInput.addListener(fListener);
			connectViewer(fInput);
			if (viewState instanceof ISelection)
				fViewer.setSelection((ISelection) viewState, true);
			else
				navigateNext(true);
			
			updateBusyLabel();
			turnOffDecoration();
			scheduleUIUpdate();
			
			fFilterActions= addFilterActionsToViewMenu();
		} else {
			getViewPart().updateLabel();
		}
	}	

	private void removeFilterActionsFromViewMenu(IAction[] filterActions) {
		IActionBars bars= getSite().getActionBars();
		IMenuManager menu= bars.getMenuManager();

		if (filterActions != null) {
			for (int i= 0; i < filterActions.length; i++) {
				menu.remove(filterActions[i].getId());
			}
		}
		menu.remove(MatchFilterSelectionAction.ACTION_ID);
	}
	
	private IAction[] addFilterActionsToViewMenu() {
		AbstractTextSearchResult input= getInput();
		if (input == null) {
			return null;
		}
		
		MatchFilter[] allMatchFilters= input.getAllMatchFilters();
		if (allMatchFilters == null && getElementLimit() == null) {
			return null;
		}
		
		IActionBars bars= getSite().getActionBars();
		IMenuManager menu= bars.getMenuManager();
		
		menu.prependToGroup(IContextMenuConstants.GROUP_FILTERING, new MatchFilterSelectionAction(this));
		
		if (allMatchFilters != null) {
			MatchFilterAction[] actions= new MatchFilterAction[allMatchFilters.length];
			for (int i= allMatchFilters.length - 1; i >= 0; i--) {
				MatchFilterAction filterAction= new MatchFilterAction(this, allMatchFilters[i]);
				actions[i]= filterAction;
				menu.prependToGroup(IContextMenuConstants.GROUP_FILTERING, filterAction);
			}
			return actions;
		}
		return null;
	}
	
	private void updateFilterActions(IAction[] filterActions) {
		if (filterActions != null) {
			for (int i= 0; i < filterActions.length; i++) {
				IAction curr=  filterActions[i];
				if (curr instanceof IUpdate) {
					((IUpdate) curr).update();
				}
			}
		}
	}

	/**
	 * {@inheritDoc}
	 */
	public Object getUIState() {
		return fViewer.getSelection();
	}

	private void connectViewer(AbstractTextSearchResult search) {
		fViewer.setInput(search);
	}

	private void disconnectViewer() {
		fViewer.setInput(null);
	}

	/**
	 * Returns the viewer currently used in this page.
	 * 
	 * @return the currently used viewer or <code>null</code> if none has been
	 *         created yet.
	 */
	protected StructuredViewer getViewer() {
		return fViewer;
	}

	private void showMatch(final Match match, final boolean activateEditor) {
		ISafeRunnable runnable = new ISafeRunnable() {
			public void handleException(Throwable exception) {
				if (exception instanceof PartInitException) {
					PartInitException pie = (PartInitException) exception;
					ErrorDialog.openError(getSite().getShell(), SearchMessages.DefaultSearchViewPage_show_match, SearchMessages.DefaultSearchViewPage_error_no_editor, pie.getStatus()); 
				}
			}

			public void run() throws Exception {
				IRegion location= getCurrentMatchLocation(match);
				showMatch(match, location.getOffset(), location.getLength(), activateEditor);
			}
		};
		SafeRunner.run(runnable);
	}

	/**
	 * Returns the currently shown result.
	 * 
	 * @return the previously set result or <code>null</code>
	 * 
	 * @see AbstractTextSearchViewPage#setInput(ISearchResult, Object)
	 */
	public AbstractTextSearchResult getInput() {
		return fInput;
	}

	/**
	 * Selects the element corresponding to the next match and shows the match
	 * in an editor. Note that this will cycle back to the first match after the
	 * last match.
	 */
	public void gotoNextMatch() {
		gotoNextMatch(false);
	}

	private void gotoNextMatch(boolean activateEditor) {
		fCurrentMatchIndex++;
		Match nextMatch = getCurrentMatch();
		if (nextMatch == null) {
			navigateNext(true);
			fCurrentMatchIndex = 0;
		}
		showCurrentMatch(activateEditor);
	}

	/**
	 * Selects the element corresponding to the previous match and shows the
	 * match in an editor. Note that this will cycle back to the last match
	 * after the first match.
	 */
	public void gotoPreviousMatch() {
		gotoPreviousMatch(false);
	}

	private void gotoPreviousMatch(boolean activateEditor) {
		fCurrentMatchIndex--;
		Match nextMatch = getCurrentMatch();
		if (nextMatch == null) {
			navigateNext(false);
			fCurrentMatchIndex = getDisplayedMatchCount(getFirstSelectedElement()) - 1;
		}
		showCurrentMatch(activateEditor);
	}
    
	private void navigateNext(boolean forward) {
		INavigate navigator = null;
		if (fViewer instanceof TableViewer) {
			navigator = new TableViewerNavigator((TableViewer) fViewer);
		} else {
			navigator = new TreeViewerNavigator(this, (TreeViewer) fViewer);
		}
		navigator.navigateNext(forward);
	}

	private boolean showCurrentMatch(boolean activateEditor) {
		Match currentMatch = getCurrentMatch();
		if (currentMatch != null) {
			showMatch(currentMatch, activateEditor);
			return true;
		}
		return false;
	}

	/**
	 * Returns the currently selected match.
	 * 
	 * @return the selected match or <code>null</code> if none are selected
	 */
	public Match getCurrentMatch() {
		Object element = getFirstSelectedElement();
		if (element != null) {
			Match[] matches = getDisplayedMatches(element);
			if (fCurrentMatchIndex >= 0 && fCurrentMatchIndex < matches.length)
				return matches[fCurrentMatchIndex];
		}
		return null;
	}
	
	/**
	 * Returns the matches that are currently displayed for the given element.
	 * If {@link AbstractTextSearchResult#getActiveMatchFilters()} is not null, only matches are returned
	 * that are not filtered by the match filters. If {@link AbstractTextSearchResult#getActiveMatchFilters()} is
	 * null all matches of the given element are returned.
	 * Any action operating on the visible matches in the search
	 * result page should use this method to get the matches for a search
	 * result (instead of asking the search result directly).
	 * 
	 * @param element
	 *            The element to get the matches for
	 * @return The matches displayed for the given element. If the current input
	 *         of this page is <code>null</code>, an empty array is returned
	 * @see AbstractTextSearchResult#getMatches(Object)
	 */
	public Match[] getDisplayedMatches(Object element) {
		AbstractTextSearchResult result= getInput();
		if (result == null)
			return EMPTY_MATCH_ARRAY;
		Match[] matches= result.getMatches(element);
		if (result.getActiveMatchFilters() == null) // default behaviour: filter state not used, all matches shown
			return matches;

		int count= 0;
		for (int i= 0; i < matches.length; i++) {
			if (matches[i].isFiltered())
				matches[i]= null;
			else 
				count++;
		}
		if (count == matches.length)
			return matches;
		
		Match[] filteredMatches= new Match[count];
		for (int i= 0, k= 0; i < matches.length; i++) {
			if (matches[i] != null)
				filteredMatches[k++]= matches[i];
		}
		return filteredMatches;
	}
	
	/** 
	 * Returns the current location of the match. This takes possible
	 * modifications of the file into account. Therefore the result may
	 * differ from the position information that can be obtained directly 
	 * off the match.
	 * @param match the match to get the position for.
	 * @return the current position of the match.
	 * 
	 * @since 3.2
	 */
	public IRegion getCurrentMatchLocation(Match match) {
		PositionTracker tracker= InternalSearchUI.getInstance().getPositionTracker();
		
		int offset, length;
		Position pos= tracker.getCurrentPosition(match);
		if (pos == null) {
			offset= match.getOffset();
			length= match.getLength();
		} else {
			offset= pos.getOffset();
			length= pos.getLength();
		}
		return new Region(offset, length);
	}
	
	/**
	 * Returns the number of matches that are currently displayed for the given
	 * element. If {@link AbstractTextSearchResult#getActiveMatchFilters()} is not null, only matches
	 * are returned that are not filtered by the match filters.
	 * Any action operating on the visible matches in the
	 * search result page should use this method to get the match count for a
	 * search result (instead of asking the search result directly).
	 * 
	 * @param element
	 *            The element to get the matches for
	 * @return The number of matches displayed for the given element. If the
	 *         current input of this page is <code>null</code>, 0 is
	 *         returned
	 * @see AbstractTextSearchResult#getMatchCount(Object)
	 */
	public int getDisplayedMatchCount(Object element) {
		AbstractTextSearchResult result= getInput();
		if (result == null)
			return 0;
		if (result.getActiveMatchFilters() == null) // default behaviour: filter state not used, all matches shown
			return result.getMatchCount(element);

		int count= 0;
		Match[] matches= result.getMatches(element);
		for (int i= 0; i < matches.length; i++) {
			if (!matches[i].isFiltered())
				count++;
		}
		return count;
	}

	private Object getFirstSelectedElement() {
		IStructuredSelection selection = (IStructuredSelection) fViewer.getSelection();
		if (selection.size() > 0)
			return selection.getFirstElement();
		return null;
	}

	/**
	 * {@inheritDoc}
	 */
	public void dispose() {
		AbstractTextSearchResult oldSearch = getInput();
		if (oldSearch != null)
			AnnotationManagers.removeSearchResult(getSite().getWorkbenchWindow(), oldSearch);
		super.dispose();
		NewSearchUI.removeQueryListener(fQueryListener);
	}

	/**
	 * {@inheritDoc}
	 */
	public void init(IPageSite pageSite) {
		super.init(pageSite);
		IMenuManager menuManager= pageSite.getActionBars().getMenuManager();
		addLayoutActions(menuManager);
		initActionDefinitionIDs();
		menuManager.updateAll(true);
		pageSite.getActionBars().updateActionBars();
	}

	private void initActionDefinitionIDs() {
		fCopyToClipboardAction.setActionDefinitionId(IWorkbenchActionDefinitionIds.COPY);
		fRemoveSelectedMatches.setActionDefinitionId(IWorkbenchActionDefinitionIds.DELETE);
		//using manifest strings to work around https://bugs.eclipse.org/bugs/show_bug.cgi?id=54581 :
		fShowNextAction.setActionDefinitionId("org.eclipse.ui.navigate.next");  //$NON-NLS-1$
		fShowPreviousAction.setActionDefinitionId("org.eclipse.ui.navigate.previous"); //$NON-NLS-1$
		fSelectAllAction.setActionDefinitionId(IWorkbenchActionDefinitionIds.SELECT_ALL);
	}

	/**
	 * Fills the toolbar contribution for this page. Subclasses may override
	 * this method.
	 * 
	 * @param tbm the tool bar manager representing the view's toolbar
	 */
	protected void fillToolbar(IToolBarManager tbm) {
		tbm.appendToGroup(IContextMenuConstants.GROUP_SHOW, fShowNextAction);
		tbm.appendToGroup(IContextMenuConstants.GROUP_SHOW, fShowPreviousAction);
		tbm.appendToGroup(IContextMenuConstants.GROUP_REMOVE_MATCHES, fRemoveSelectedMatches);
		tbm.appendToGroup(IContextMenuConstants.GROUP_REMOVE_MATCHES, fRemoveAllResultsAction);
		IActionBars actionBars = getSite().getActionBars();
		if (actionBars != null) {
			actionBars.setGlobalActionHandler(ActionFactory.NEXT.getId(), fShowNextAction);
			actionBars.setGlobalActionHandler(ActionFactory.PREVIOUS.getId(), fShowPreviousAction);
			actionBars.setGlobalActionHandler(ActionFactory.DELETE.getId(), fRemoveSelectedMatches);
			actionBars.setGlobalActionHandler(ActionFactory.COPY.getId(), fCopyToClipboardAction);
			actionBars.setGlobalActionHandler(ActionFactory.SELECT_ALL.getId(), fSelectAllAction);
		}
		if (getLayout() == FLAG_LAYOUT_TREE) {
			tbm.appendToGroup(IContextMenuConstants.GROUP_VIEWER_SETUP, fExpandAllAction);
			tbm.appendToGroup(IContextMenuConstants.GROUP_VIEWER_SETUP, fCollapseAllAction);
		}
	}

	private void addLayoutActions(IMenuManager menuManager) {
		if (fFlatAction != null)
			menuManager.appendToGroup(IContextMenuConstants.GROUP_VIEWER_SETUP, fFlatAction);
		if (fHierarchicalAction != null)
			menuManager.appendToGroup(IContextMenuConstants.GROUP_VIEWER_SETUP, fHierarchicalAction);
	}

	/**
	 * Sets the view part
	 * @param part View part to set
	 */
	public void setViewPart(ISearchResultViewPart part) {
		fViewPart = part;
	}

	/**
	 * Returns the view part set with
	 * <code>setViewPart(ISearchResultViewPart)</code>.
	 * 
	 * @return The view part or <code>null</code> if the view part hasn't been
	 *         set yet (or set to null).
	 */
	protected ISearchResultViewPart getViewPart() {
		return fViewPart;
	}

	// multi-threaded update handling.
	
	/**
	 * Handles a search result event for the current search result.
	 * @param e the event to handle
	 * 
	 * @since 3.2
	 */
	protected void handleSearchResultChanged(final SearchResultEvent e) {
		if (e instanceof MatchEvent) {
			postUpdate(((MatchEvent) e).getMatches());
		} else if (e instanceof RemoveAllEvent) {
			postClear();
		} else if (e instanceof FilterUpdateEvent) {
			postUpdate(((FilterUpdateEvent) e).getUpdatedMatches());
			updateFilterActions(fFilterActions);
		}
	}
	
	/**
	 * Evaluates the elements to that are later passed to {@link #elementsChanged(Object[])}. By default
	 * the element to change are the elements received by ({@link Match#getElement()}). Client implementations
	 * can modify this behavior.
	 *  
	 * @param matches the matches that were added or removed
	 * @param changedElements the set that collects the elements to change. Clients should only add elements to the set.
	 * @since 3.4
	 */
	protected void evaluateChangedElements(Match[] matches, Set changedElements) {
		for (int i = 0; i < matches.length; i++) {
			changedElements.add(matches[i].getElement());
		}
	}
	
	private synchronized void postUpdate(Match[] matches) {
		evaluateChangedElements(matches, fBatchedUpdates);
		scheduleUIUpdate();
	}

	private synchronized void runBatchedUpdates() {
		if (false /*fBatchedUpdates.size() > 50*/) {
			Object[] hundredUpdates= new Object[50];
			Iterator elements= fBatchedUpdates.iterator();
			for (int i= 0; i < hundredUpdates.length; i++) {
				hundredUpdates[i]= elements.next();
				elements.remove();
			}
			elementsChanged(hundredUpdates);
		} else {
			elementsChanged(fBatchedUpdates.toArray());
			fBatchedUpdates.clear();
		}
		updateBusyLabel();
	}

	private synchronized void postClear() {
		fBatchedClearAll= true;
		fBatchedUpdates.clear();
		scheduleUIUpdate();
	}

	private synchronized boolean hasMoreUpdates() {
		return fBatchedClearAll || fBatchedUpdates.size() > 0;
	}

	private boolean isQueryRunning() {
		AbstractTextSearchResult result= getInput();
		if (result != null) {
			return NewSearchUI.isQueryRunning(result.getQuery());
		}
		return false;
	}

	private void runBatchedClear() {
		synchronized(this) {
			if (!fBatchedClearAll) {
				return;
			}
			fBatchedClearAll= false;
			updateBusyLabel();
		}
		getViewPart().updateLabel();
		clear();
	}

	private void asyncExec(final Runnable runnable) {
		final Control control = getControl();
		if (control != null && !control.isDisposed()) {
			Display currentDisplay = Display.getCurrent();
			if (currentDisplay == null || !currentDisplay.equals(control.getDisplay()))
				// meaning we're not executing on the display thread of the
				// control
				control.getDisplay().asyncExec(new Runnable() {
					public void run() {
						if (!control.isDisposed())
							runnable.run();
					}
				});
			else
				runnable.run();
		}
	}

	/**
	 * {@inheritDoc}
	 * Subclasses may extend this method.
	 */
	public void restoreState(IMemento memento) {
		if (countBits(fSupportedLayouts) > 1) {
			try {
				fCurrentLayout = getSettings().getInt(KEY_LAYOUT);
				// workaround because the saved value may be 0
				if (fCurrentLayout == 0)
					initLayout();
			} catch (NumberFormatException e) {
				// ignore, signals no value stored.
			}
			if (memento != null) {
				Integer layout = memento.getInteger(KEY_LAYOUT);
				if (layout != null) {
					fCurrentLayout = layout.intValue();
					// workaround because the saved value may be 0
					if (fCurrentLayout == 0)
						initLayout();
				}
			}
		}
	}

	/* (non-Javadoc)
	 * @see com.nokia.carbide.search.system.ui.ISearchResultPage#saveState(org.eclipse.ui.IMemento)
	 */
	public void saveState(IMemento memento) {
		if (countBits(fSupportedLayouts) > 1) {
			memento.putInteger(KEY_LAYOUT, fCurrentLayout);
		}
	}

	/**
	 * Note: this is internal API and should not be called from clients outside
	 * of the search plug-in.
	 * <p>
	 * Removes the currently selected match. Does nothing if no match is
	 * selected.
	 * </p>
	 * 
	 * @noreference This method is not intended to be referenced by clients.
	 */
	public void internalRemoveSelected() {
		AbstractTextSearchResult result = getInput();
		if (result == null)
			return;
		StructuredViewer viewer = getViewer();
		IStructuredSelection selection = (IStructuredSelection) viewer.getSelection();
		
		HashSet set = new HashSet();
		if (viewer instanceof TreeViewer) {
			ITreeContentProvider cp = (ITreeContentProvider) viewer.getContentProvider();
			collectAllMatchesBelow(result, set, cp, selection.toArray());
		} else {
			collectAllMatches(set, selection.toArray());
		}
		navigateNext(true);
		
		Match[] matches = new Match[set.size()];
		set.toArray(matches);
		result.removeMatches(matches);
	}	

	private void collectAllMatches(HashSet set, Object[] elements) {
		for (int j = 0; j < elements.length; j++) {
			Match[] matches = getDisplayedMatches(elements[j]);
			for (int i = 0; i < matches.length; i++) {
				set.add(matches[i]);
			}
		}
	}

	private void collectAllMatchesBelow(AbstractTextSearchResult result, Set set, ITreeContentProvider cp, Object[] elements) {
		for (int j = 0; j < elements.length; j++) {
			Match[] matches = getDisplayedMatches(elements[j]);
			for (int i = 0; i < matches.length; i++) {
				set.add(matches[i]);
			}
			Object[] children = cp.getChildren(elements[j]);
			collectAllMatchesBelow(result, set, cp, children);
		}
	}
	
	private void turnOffDecoration() {
		IBaseLabelProvider lp= fViewer.getLabelProvider();
		if (lp instanceof DecoratingLabelProvider) {
			((DecoratingLabelProvider)lp).setLabelDecorator(null);			
		}
	}

	private void turnOnDecoration() {
		IBaseLabelProvider lp= fViewer.getLabelProvider();
		if (lp instanceof DecoratingLabelProvider) {
			((DecoratingLabelProvider)lp).setLabelDecorator(PlatformUI.getWorkbench().getDecoratorManager().getLabelDecorator());			
			
		}
	}

	/**
	 * <p>This method is called when the search page gets an open even from it's
	 * underlying viewer (for example on double click). The default
	 * implementation will open the first match on any element that has matches.
	 * If the element to be opened is an inner node in the tree layout, the node
	 * will be expanded if it's collapsed and vice versa. Subclasses are allowed
	 * to override this method.
	 * </p>
	 * @param event
	 *            the event sent for the currently shown viewer
	 * 
	 * @see IOpenListener
	 */
	protected void handleOpen(OpenEvent event) {
		Viewer viewer= event.getViewer();
		boolean hasCurrentMatch = showCurrentMatch(OpenStrategy.activateOnOpen());
		ISelection sel= event.getSelection();
		if (viewer instanceof TreeViewer && sel instanceof IStructuredSelection) {
			IStructuredSelection selection= (IStructuredSelection) sel;
			TreeViewer tv = (TreeViewer) getViewer();
			Object element = selection.getFirstElement();
			if (element != null) {
				if (!hasCurrentMatch && getDisplayedMatchCount(element) > 0)
					gotoNextMatch(OpenStrategy.activateOnOpen());
				else 
					tv.setExpandedState(element, !tv.getExpandedState(element));
			}
			return;
		} else if (!hasCurrentMatch) {
			gotoNextMatch(OpenStrategy.activateOnOpen());
		}
	}
	
	/**
	 * Sets the maximal number of top level elements to be shown in a viewer.
	 * If <code>null</code> is set, the view page does not support to limit the elements and will not provide
	 * UI to configure it. If a non-null value is set, configuration UI will be provided. The limit value must be a positive
	 * number or <code>-1</code> to not limit top level element.
	 * If enabled, the element limit has to be enforced by the content provider that is implemented by the client. The view
	 * page just manages the value and configuration.
	 *
	 * @param limit the element limit. Valid values are:
	 * <dl>
	 *   <li><code>null</code> to not limit and not provide configuration UI</li>
	 *   <li><code>-1</code> to not limit and provide configuration UI</li>
	 *   <li><code>positive integer</code> to limit by the given value and provide configuration UI</li>
	 *  </dl>
	 *  
	 *  @since 3.3
	 */
	public void setElementLimit(Integer limit) {
		fElementLimit= limit;
		
		if (fViewer != null) {
			fViewer.refresh();
		}
		if (fViewPart != null) {
			fViewPart.updateLabel();
		}
	}
	
	/**
	 * Gets the maximal number of top level elements to be shown in a viewer.
	 * <code>null</code> means the view page does not limit the elements and will not provide
	 * UI to configure it. If a non-null value is set, configuration UI will be provided. The limit value must be a positive
	 * number or <code>-1</code> to not limit top level element.
	 *
	 * @return returns the element limit. Valid values are:
	 * <dl>
	 *   <li><code>null</code> to not limit and not provide configuration UI (default value)</li>
	 *   <li><code>-1</code> to not limit and provide configuration UI</li>
	 *   <li><code>positive integer</code> to limit by the given value and provide configuration UI</li>
	 *  </dl>
	 *  
	 *  @since 3.3
	 */
	public Integer getElementLimit() {
		return fElementLimit;
	}
	
	
}
