/*******************************************************************************
 * 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
 *     
 *******************************************************************************/
package org.eclipse.wst.sse.ui.internal.ui;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentExtension3;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.text.ITypedRegion;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.TextUtilities;
import org.eclipse.jface.text.reconciler.IReconcileStep;
import org.eclipse.jface.text.source.Annotation;
import org.eclipse.jface.text.source.IAnnotationModel;
import org.eclipse.jface.viewers.ArrayContentProvider;
import org.eclipse.jface.viewers.CellEditor;
import org.eclipse.jface.viewers.ColumnWeightData;
import org.eclipse.jface.viewers.ILabelProviderListener;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITableLabelProvider;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.TableLayout;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerComparator;
import org.eclipse.jface.viewers.ViewerSorter;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.FormAttachment;
import org.eclipse.swt.layout.FormData;
import org.eclipse.swt.layout.FormLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Sash;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.TabFolder;
import org.eclipse.swt.widgets.TabItem;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorSite;
import org.eclipse.ui.ISelectionService;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.texteditor.ITextEditor;
import org.eclipse.ui.texteditor.MarkerAnnotation;
import org.eclipse.ui.texteditor.MarkerUtilities;
import org.eclipse.ui.texteditor.SimpleMarkerAnnotation;
import org.eclipse.ui.texteditor.StatusLineContributionItem;
import org.eclipse.ui.views.properties.IPropertyDescriptor;
import org.eclipse.ui.views.properties.IPropertySource;
import org.eclipse.ui.views.properties.IPropertySourceProvider;
import org.eclipse.ui.views.properties.PropertySheetPage;
import org.eclipse.ui.views.properties.TextPropertyDescriptor;
import org.eclipse.wst.sse.core.StructuredModelManager;
import org.eclipse.wst.sse.core.internal.provisional.INodeAdapter;
import org.eclipse.wst.sse.core.internal.provisional.INodeNotifier;
import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion;
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion;
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredPartitioning;
import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegion;
import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegionCollection;
import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegionContainer;
import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegionList;
import org.eclipse.wst.sse.core.internal.util.Utilities;
import org.eclipse.wst.sse.core.utils.StringUtils;
import org.eclipse.wst.sse.ui.internal.SSEUIMessages;
import org.eclipse.wst.sse.ui.internal.contentoutline.IJFaceNodeAdapter;
import org.eclipse.wst.sse.ui.internal.reconcile.ReconcileAnnotationKey;
import org.eclipse.wst.sse.ui.internal.reconcile.TemporaryAnnotation;

import com.ibm.icu.text.NumberFormat;

/**
 * @author nsd A Status Line contribution intended to display the selected
 *         offsets in an editor. Double-clicking shows information about
 *         partitions, document regions, annotations, and selection.
 */
public class OffsetStatusLineContributionItem extends StatusLineContributionItem {

	class AnnotationPropertySource implements IPropertySource {
		Annotation fAnnotation = null;
		IPropertyDescriptor[] fDescriptors = null;
		String[] TEMPORARY_ANNOTATION_KEYS = new String[]{"Partition Type", "Step", "Scope", "Offset", "Length", "Description"}; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$

		public AnnotationPropertySource(Annotation annotation) {
			super();
			fAnnotation = annotation;
		}

		public Object getEditableValue() {
			return null;
		}

		public IPropertyDescriptor[] getPropertyDescriptors() {
			if (fDescriptors == null) {
				try {
					if (fAnnotation instanceof SimpleMarkerAnnotation) {
						Map attrs = ((SimpleMarkerAnnotation) fAnnotation).getMarker().getAttributes();
						Object[] keys = attrs.keySet().toArray();

						fDescriptors = new IPropertyDescriptor[keys.length];
						for (int i = 0; i < keys.length; i++) {
							TextPropertyDescriptor descriptor = new TextPropertyDescriptor(keys[i].toString(), keys[i].toString());
							fDescriptors[i] = descriptor;
						}
					}
					else if (fAnnotation instanceof TemporaryAnnotation) {
						Object key = ((TemporaryAnnotation) fAnnotation).getKey();
						if (key != null && key instanceof ReconcileAnnotationKey) {
							String[] keys = TEMPORARY_ANNOTATION_KEYS;
							fDescriptors = new IPropertyDescriptor[keys.length];
							for (int i = 0; i < keys.length; i++) {
								TextPropertyDescriptor descriptor = new TextPropertyDescriptor(keys[i].toString(), keys[i].toString());
								fDescriptors[i] = descriptor;
							}
						}
					}
				}
				catch (CoreException e) {
					e.printStackTrace();
				}
			}
			if (fDescriptors == null)
				fDescriptors = new IPropertyDescriptor[0];
			return fDescriptors;
		}

		public Object getPropertyValue(Object id) {
			String value = null;
			if (fAnnotation instanceof SimpleMarkerAnnotation) {
				Object o;
				try {
					o = ((SimpleMarkerAnnotation) fAnnotation).getMarker().getAttributes().get(id);
					if (o != null) {
						value = o.toString();
					}
				}
				catch (CoreException e) {
				}
			}
			else if (fAnnotation instanceof TemporaryAnnotation) {
				if (TEMPORARY_ANNOTATION_KEYS[0].equals(id)) {
					Object key = ((TemporaryAnnotation) fAnnotation).getKey();
					if (key != null && key instanceof ReconcileAnnotationKey) {
						value = ((ReconcileAnnotationKey) key).getPartitionType();
					}
				}
				else if (TEMPORARY_ANNOTATION_KEYS[1].equals(id)) {
					Object key = ((TemporaryAnnotation) fAnnotation).getKey();
					if (key != null && key instanceof ReconcileAnnotationKey) {
						IReconcileStep step = ((ReconcileAnnotationKey) key).getStep();
						if (step != null) {
							value = step.toString();
						}
					}
				}
				else if (TEMPORARY_ANNOTATION_KEYS[2].equals(id)) {
					Object key = ((TemporaryAnnotation) fAnnotation).getKey();
					if (key != null && key instanceof ReconcileAnnotationKey) {
						int scope = ((ReconcileAnnotationKey) key).getScope();
						if (scope == ReconcileAnnotationKey.PARTIAL) {
							value = "PARTIAL"; //$NON-NLS-1$
						}
						if (scope == ReconcileAnnotationKey.TOTAL) {
							value = "TOTAL"; //$NON-NLS-1$
						}
					}
				}
				else if (TEMPORARY_ANNOTATION_KEYS[3].equals(id)) {
					IAnnotationModel annotationModel = fTextEditor.getDocumentProvider().getAnnotationModel(fTextEditor.getEditorInput());
					Position p = annotationModel.getPosition(fAnnotation);
					if (p != null) {
						value = String.valueOf(p.getOffset());
					}
				}
				else if (TEMPORARY_ANNOTATION_KEYS[4].equals(id)) {
					IAnnotationModel annotationModel = fTextEditor.getDocumentProvider().getAnnotationModel(fTextEditor.getEditorInput());
					Position p = annotationModel.getPosition(fAnnotation);
					if (p != null) {
						value = String.valueOf(p.getLength());
					}
				}
				else if (TEMPORARY_ANNOTATION_KEYS[5].equals(id)) {
					value = ((TemporaryAnnotation) fAnnotation).getDescription();
				}
			}
			return value;
		}

		public boolean isPropertySet(Object id) {
			return false;
		}

		public void resetPropertyValue(Object id) {
			try {
				if (fAnnotation instanceof SimpleMarkerAnnotation) {
					((SimpleMarkerAnnotation) fAnnotation).getMarker().getAttributes().remove(id);
				}
				else if (fAnnotation instanceof TemporaryAnnotation) {
				}
			}
			catch (CoreException e) {
				e.printStackTrace();
			}
		}

		public void setPropertyValue(Object id, Object value) {
			try {
				if (fAnnotation instanceof SimpleMarkerAnnotation) {
					((MarkerAnnotation) fAnnotation).getMarker().setAttribute(id.toString(), value);
				}
				else if (fAnnotation instanceof TemporaryAnnotation) {
				}
			}
			catch (CoreException e) {
				e.printStackTrace();
			}
		}
	}

	class InformationDialog extends Dialog {

		IDocument fDocument = fTextEditor.getDocumentProvider().getDocument(fTextEditor.getEditorInput());

		public InformationDialog(Shell parentShell) {
			super(parentShell);
			setShellStyle(getShellStyle() | SWT.RESIZE);
		}

		private void createAnnotationTabContents(Composite annotationsTabComposite) {
			annotationsTabComposite.setLayout(new GridLayout());
			annotationsTabComposite.setLayoutData(new GridData());

			final Composite annotationsComposite = new Composite(annotationsTabComposite, SWT.NONE);
			annotationsComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));

			final TableViewer annotationsTable = new TableViewer(annotationsComposite, SWT.SINGLE | SWT.FULL_SELECTION | SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER);
			annotationsTable.setComparator(new ViewerComparator(new Comparator() {
				public int compare(Object o1, Object o2) {
					Annotation annotation1 = (Annotation) o1;
					Annotation annotation2 = (Annotation) o2;
					String line1 = getLineNumber(annotation1);
					String line2 = getLineNumber(annotation2);
					return Integer.parseInt(line1) - Integer.parseInt(line2);
				}
			}));
			annotationsTable.setContentProvider(new ArrayContentProvider());
			annotationsTable.getTable().setHeaderVisible(true);
			annotationsTable.getTable().setLinesVisible(true);
			String[] columns = new String[]{"Line", "Owner", "Type", "Class", "Message"}; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$
			annotationsTable.setLabelProvider(new ITableLabelProvider() {
				public void addListener(ILabelProviderListener listener) {
				}

				public void dispose() {
				}

				public Image getColumnImage(Object element, int columnIndex) {
					return null;
				}

				public String getColumnText(Object element, int columnIndex) {
					Annotation annotation = (Annotation) element;
					String text = null;
					switch (columnIndex) {
						case 0 :
							text = getLineNumber(annotation);
							break;
						case 1 :
							text = getOwner(annotation);
							break;
						case 2 :
							text = getType(annotation); //$NON-NLS-1$
							break;
						case 3 :
							text = annotation.getClass().getName();
							break;
						case 4 :
							text = annotation.getText();
							break;
					}
					if (text == null)
						text = ""; //$NON-NLS-1$
					return text;
				}


				private String getOwner(Annotation annotation) {
					String owner = null;
					if (annotation instanceof MarkerAnnotation) {
						owner = ((MarkerAnnotation) annotation).getMarker().getAttribute("owner", ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$				
					}
					else if (annotation instanceof TemporaryAnnotation) {
						Object key = ((TemporaryAnnotation) annotation).getKey();
						if (key != null) {
							if (key instanceof ReconcileAnnotationKey) {
								key = key.getClass().getName();
							}
							if (key != null)
								owner = key.toString();
						}
					}
					return owner;
				}

				private String getType(Annotation annotation) {
					String type = null;
					if (annotation instanceof MarkerAnnotation) {
						type = "M:"+MarkerUtilities.getMarkerType(((MarkerAnnotation) annotation).getMarker()); //$NON-NLS-1$
					}
					else {
						type = "A:"+annotation.getType(); //$NON-NLS-1$
					}
					if (type == null)
						type = ""; //$NON-NLS-1$
					return type;
				}

				public boolean isLabelProperty(Object element, String property) {
					return true;
				}

				public void removeListener(ILabelProviderListener listener) {
				}
			});

			TableLayout tlayout = new TableLayout();
			CellEditor[] cellEditors = new CellEditor[columns.length];
			int columnWidths[] = new int[]{Display.getCurrent().getBounds().width / 14, Display.getCurrent().getBounds().width / 7, Display.getCurrent().getBounds().width / 7, Display.getCurrent().getBounds().width / 14, Display.getCurrent().getBounds().width / 7};
			for (int i = 0; i < columns.length; i++) {
				tlayout.addColumnData(new ColumnWeightData(1));
				TableColumn tc = new TableColumn(annotationsTable.getTable(), SWT.NONE);
				tc.setText(columns[i]);
				tc.setResizable(true);
				tc.setWidth(columnWidths[i]);
			}
			annotationsTable.setCellEditors(cellEditors);
			annotationsTable.setColumnProperties(columns);
			List matchingAnnotations = new ArrayList(0);
			if (fTextEditor != null) {
				IAnnotationModel annotationModel = fTextEditor.getDocumentProvider().getAnnotationModel(fTextEditor.getEditorInput());
				if (annotationModel != null) {
					Iterator iterator = annotationModel.getAnnotationIterator();
					while (iterator.hasNext()) {
						Annotation element = (Annotation) iterator.next();
						if (true) {
							matchingAnnotations.add(element);
						}
					}
				}
			}
			annotationsTable.setSorter(new ViewerSorter());
			annotationsTable.setInput(matchingAnnotations);

			final Sash sash = new Sash(annotationsComposite, SWT.HORIZONTAL);

			final PropertySheetPage propertySheet = new PropertySheetPage();
			propertySheet.createControl(annotationsComposite);
			propertySheet.setPropertySourceProvider(new IPropertySourceProvider() {
				public IPropertySource getPropertySource(Object object) {
					if (object instanceof Annotation) {
						IPropertySource annotationPropertySource = new AnnotationPropertySource(((Annotation) object));
						return annotationPropertySource;
					}
					return null;
				}
			});

			annotationsTable.addSelectionChangedListener(new ISelectionChangedListener() {
				public void selectionChanged(SelectionChangedEvent event) {
					propertySheet.selectionChanged(null, event.getSelection());
				}
			});

			final FormLayout form = new FormLayout();
			annotationsComposite.setLayout(form);

			FormData tableData = new FormData();
			tableData.top = new FormAttachment(0, 0);
			tableData.bottom = new FormAttachment(sash, 2);
			tableData.left = new FormAttachment(0, 0);
			tableData.right = new FormAttachment(100, 0);
			annotationsTable.getControl().setLayoutData(tableData);

			FormData propertiesData = new FormData();
			propertiesData.top = new FormAttachment(sash, 2);
			propertiesData.left = new FormAttachment(0, 0);
			propertiesData.right = new FormAttachment(100, 0);
			propertiesData.bottom = new FormAttachment(100, 0);
			propertySheet.getControl().setLayoutData(propertiesData);

			final FormData sashData = new FormData();
			sashData.top = new FormAttachment(60, 0);
			sashData.left = new FormAttachment(0, 0);
			sashData.right = new FormAttachment(100, 0);
			sash.setLayoutData(sashData);
			sash.addListener(SWT.Selection, new org.eclipse.swt.widgets.Listener() {
				public void handleEvent(Event e) {
					sashData.top = new FormAttachment(0, e.y);
					annotationsComposite.layout();
				}
			});
			annotationsComposite.pack(true);
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see org.eclipse.jface.dialogs.Dialog#createDialogArea(org.eclipse.swt.widgets.Composite)
		 */
		protected Control createDialogArea(Composite parent) {
			ISelection selection = fTextEditor.getSelectionProvider().getSelection();
			ITextSelection textSelection = (ITextSelection) selection;
			IStructuredSelection structuredSelection = null;
			if (selection instanceof IStructuredSelection)
				structuredSelection = (IStructuredSelection) selection;

			parent.getShell().setText(SSEUIMessages.OffsetStatusLineContributionItem_0 + textSelection.getOffset() + "-" + (textSelection.getOffset() + textSelection.getLength())); //$NON-NLS-1$ //$NON-NLS-2$
			Composite composite = (Composite) super.createDialogArea(parent);

			Text documentTypeLabel = new Text(composite, SWT.SINGLE | SWT.READ_ONLY);
			GridData gd = new GridData(SWT.FILL, SWT.FILL, true, false);
			gd.horizontalSpan = 2;
			documentTypeLabel.setLayoutData(gd);
			documentTypeLabel.setText(SSEUIMessages.OffsetStatusLineContributionItem_6 + fDocument.getClass().getName()); //$NON-NLS-1$

			Text documentProviderLabel = new Text(composite, SWT.SINGLE | SWT.READ_ONLY);
			gd = new GridData(SWT.FILL, SWT.FILL, true, false);
			gd.horizontalSpan = 2;
			documentProviderLabel.setLayoutData(gd);
			documentProviderLabel.setText(SSEUIMessages.OffsetStatusLineContributionItem_7 + fTextEditor.getDocumentProvider().getClass().getName()); //$NON-NLS-1$

			Text editorInputLabel = new Text(composite, SWT.SINGLE | SWT.READ_ONLY);
			gd = new GridData(SWT.FILL, SWT.FILL, true, false);
			gd.horizontalSpan = 2;
			editorInputLabel.setLayoutData(gd);
			editorInputLabel.setText(SSEUIMessages.OffsetStatusLineContributionItem_12 + fTextEditor.getEditorInput().getClass().getName()); //$NON-NLS-1$

			final Text bomLabel = new Text(composite, SWT.SINGLE | SWT.READ_ONLY);
			gd = new GridData(SWT.FILL, SWT.FILL, true, false);
			gd.horizontalSpan = 2;
			bomLabel.setLayoutData(gd);
			bomLabel.setEnabled(false);
			bomLabel.setText("Byte Order Mark: ");

			IStructuredModel model = StructuredModelManager.getModelManager().getExistingModelForRead(fDocument);
			if (model != null) {
				Text modelIdLabel = new Text(composite, SWT.SINGLE | SWT.READ_ONLY);
				gd = new GridData(SWT.FILL, SWT.FILL, true, false);
				gd.horizontalSpan = 2;
				modelIdLabel.setLayoutData(gd);
				modelIdLabel.setText("ID: " + model.getId()); //$NON-NLS-1$

				Text modelBaseLocationLabel = new Text(composite, SWT.SINGLE | SWT.READ_ONLY);
				gd = new GridData(SWT.FILL, SWT.FILL, true, false);
				gd.horizontalSpan = 2;
				modelBaseLocationLabel.setLayoutData(gd);
				modelBaseLocationLabel.setText("Base Location: " + model.getBaseLocation()); //$NON-NLS-1$

				Text modelContentTypeLabel = new Text(composite, SWT.SINGLE | SWT.READ_ONLY);
				gd = new GridData(SWT.FILL, SWT.FILL, true, false);
				gd.horizontalSpan = 2;
				modelContentTypeLabel.setLayoutData(gd);
				modelContentTypeLabel.setText(SSEUIMessages.OffsetStatusLineContributionItem_4 + model.getContentTypeIdentifier()); //$NON-NLS-1$

				Text modelHandlerContentTypeLabel = new Text(composite, SWT.MULTI | SWT.WRAP | SWT.READ_ONLY);
				gd = new GridData(SWT.FILL, SWT.FILL, true, false);
				gd.horizontalSpan = 2;
				modelHandlerContentTypeLabel.setLayoutData(gd);
				modelHandlerContentTypeLabel.setText(SSEUIMessages.OffsetStatusLineContributionItem_5 + model.getModelHandler().getAssociatedContentTypeId() + " (" + model.getModelHandler() + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$

				final Text counts = new Text(composite, SWT.MULTI | SWT.WRAP | SWT.READ_ONLY);
				gd = new GridData(SWT.FILL, SWT.FILL, true, false);
				gd.horizontalSpan = 2;
				counts.setLayoutData(gd);
				counts.setText("Counting...");
				counts.setEnabled(false);
				final IStructuredModel finalModel = model;
				final Display display = Display.getCurrent();
				Job counter = new Job("Counting regions") {
					protected IStatus run(IProgressMonitor monitor) {
						IStructuredDocumentRegion[] structuredDocumentRegions = finalModel.getStructuredDocument().getStructuredDocumentRegions();
						int length = finalModel.getStructuredDocument().getLength();
						int regionCount = 0;
						for (int i = 0; i < structuredDocumentRegions.length; i++) {
							regionCount += structuredDocumentRegions[i].getNumberOfRegions();
						}
						NumberFormat formatter = NumberFormat.getIntegerInstance();
						final String regioncount = "Count: " + formatter.format(structuredDocumentRegions.length) + " document regions containing " + formatter.format(regionCount) + " text regions representing " + formatter.format(length) + " characters";//$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
						display.syncExec(new Runnable() {
							public void run() {
								counts.setText(regioncount);
								counts.setEnabled(true);
								bomLabel.setText("Byte Order Mark: " + getBOMText(fTextEditor.getEditorInput())); //$NON-NLS-1$
								bomLabel.setEnabled(true);
							}
						});
						return Status.OK_STATUS;
					}
				};
				counter.schedule(1000);

				Label blankRow = new Label(composite, SWT.NONE);
				gd = new GridData(SWT.FILL, SWT.FILL, true, false);
				gd.horizontalSpan = 2;
				blankRow.setLayoutData(gd);
			}
			if (model != null) {
				model.releaseFromRead();
			}

			TabFolder tabfolder = new TabFolder(composite, SWT.NONE);
			tabfolder.setLayoutData(new GridData(GridData.FILL_BOTH));

			TabItem partitionTab = new TabItem(tabfolder, SWT.BORDER);
			partitionTab.setText(SSEUIMessages.OffsetStatusLineContributionItem_2); //$NON-NLS-1$
			SashForm partitions = new SashForm(tabfolder, SWT.NONE);
			partitions.setOrientation(SWT.VERTICAL);
			partitionTab.setControl(partitions);
			createPartitionTabContents(partitions);
			partitions.setWeights(new int[]{2, 1});

			TabItem annotationsTab = new TabItem(tabfolder, SWT.BORDER);
			annotationsTab.setText("Annotations"); //$NON-NLS-1$
			Composite annotations = new Composite(tabfolder, SWT.NONE);
			annotationsTab.setControl(annotations);
			createAnnotationTabContents(annotations);

			// only create the ITextRegions tab for IStructuredDocuments
			if (fDocument instanceof IStructuredDocument) {
				TabItem regionTab = new TabItem(tabfolder, SWT.BORDER);
				regionTab.setText(SSEUIMessages.OffsetStatusLineContributionItem_3); //$NON-NLS-1$
				SashForm regions = new SashForm(tabfolder, SWT.NONE);
				regions.setOrientation(SWT.HORIZONTAL);
				regionTab.setControl(regions);
				createRegionTabContents(regions);
				regions.setWeights(new int[]{3, 2});
			}

			if (structuredSelection != null) {
				TabItem editorSelectionTab = new TabItem(tabfolder, SWT.BORDER);
				editorSelectionTab.setText(SSEUIMessages.OffsetStatusLineContributionItem_14);
				Composite editorSelectionComposite = new Composite(tabfolder, SWT.NONE);
				editorSelectionTab.setControl(editorSelectionComposite);
				fillSelectionTabContents(editorSelectionComposite, structuredSelection.toList(), "Class: " + structuredSelection.getClass().getName()); //$NON-NLS-1$
			}

			model = StructuredModelManager.getModelManager().getExistingModelForRead(fDocument);
			if (model != null) {
				TabItem overlappingIndexedRegionsTab = new TabItem(tabfolder, SWT.BORDER);
				overlappingIndexedRegionsTab.setText(SSEUIMessages.OffsetStatusLineContributionItem_20);
				Composite overlappingIndexedRegionsTabComposite = new Composite(tabfolder, SWT.NONE);
				overlappingIndexedRegionsTab.setControl(overlappingIndexedRegionsTabComposite);
				fillSelectionTabContents(overlappingIndexedRegionsTabComposite, getIndexedRegions(textSelection), "All IndexedRegions overlapping text selection"); //$NON-NLS-1$
				model.releaseFromRead();
			}

			IEditorSite site = fTextEditor.getEditorSite();
			if (site != null) {
				IWorkbenchWindow window = site.getWorkbenchWindow();
				if (window != null) {
					ISelectionService service = window.getSelectionService();
					ISelection selectionFromService = service.getSelection();
					if (service != null && !selectionFromService.equals(structuredSelection) && selectionFromService instanceof IStructuredSelection) {
						TabItem selectionServiceTab = new TabItem(tabfolder, SWT.BORDER);
						selectionServiceTab.setText(SSEUIMessages.OffsetStatusLineContributionItem_19);
						Composite selectionServiceComposite = new Composite(tabfolder, SWT.NONE);
						selectionServiceTab.setControl(selectionServiceComposite);
						fillSelectionTabContents(selectionServiceComposite, ((IStructuredSelection) selectionFromService).toList(), "Class: " + selectionFromService.getClass().getName()); //$NON-NLS-1$
					}
				}
			}

			return composite;
		}

		/**
		 * @param editorInput
		 * @return
		 */
		private String getBOMText(IEditorInput editorInput) {
			IFile file = (IFile) editorInput.getAdapter(IFile.class);
			String detectedBOM = "none"; //$NON-NLS-1$
			if (file != null) {
				InputStream s = null;
				try {
					s = file.getContents(true);
					if (s != null) {
						int b1 = s.read() & 0xFF;
						int b2 = s.read() & 0xFF;
						if (b1 == 0xFE && b2 == 0xFF) {
							detectedBOM = "FE FF (UTF-16BE)"; //$NON-NLS-1$
						}
						else if (b1 == 0xFF && b2 == 0xFE) {
							detectedBOM = "FF FE (UTF-16LE)"; //$NON-NLS-1$
						}
						else {
							int b3 = s.read() & 0xFF;
							if (b1 == 0xEF && b2 == 0xBB && b3 == 0xBF) {
								detectedBOM = "EF BB BF (UTF-8)"; //$NON-NLS-1$
							}
						}
					}
				}
				catch (Exception e) {
					detectedBOM = e.getMessage();
				}
				finally {
					if (s != null)
						try {
							s.close();
						}
						catch (IOException e) {
						}
				}
			}
			else {
				detectedBOM = "N/A"; //$NON-NLS-1$
			}
			return detectedBOM;
		}

		private List getIndexedRegions(ITextSelection textSelection) {
			Set overlappingIndexedRegions = new HashSet(2);
			int start = textSelection.getOffset();
			int end = start + textSelection.getLength();
			IStructuredModel model = StructuredModelManager.getModelManager().getExistingModelForRead(fDocument);
			if (model != null) {
				for (int i = start; i <= end; i++) {
					IndexedRegion r = model.getIndexedRegion(i);
					if (r != null) {
						overlappingIndexedRegions.add(r);
					}
				}
				model.releaseFromRead();
			}

			return Arrays.asList(overlappingIndexedRegions.toArray());
		}

		/**
		 * @param sash
		 */
		private void createPartitionTabContents(SashForm sash) {
			Composite partioningComposite = new Composite(sash, SWT.NONE);
			partioningComposite.setLayout(new GridLayout(2, false));
			partioningComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));

			Label label = new Label(partioningComposite, SWT.SINGLE);
			label.setText(SSEUIMessages.OffsetStatusLineContributionItem_8); //$NON-NLS-1$
			label.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
			final Combo partitioningCombo = new Combo(partioningComposite, SWT.READ_ONLY);
			partitioningCombo.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));

			final Label partitionerInstanceLabel = new Label(partioningComposite, SWT.SINGLE);
			GridData gd = new GridData(SWT.FILL, SWT.FILL, true, false);
			gd.horizontalSpan = 2;
			partitionerInstanceLabel.setLayoutData(gd);

			final TableViewer fPartitionTable = new TableViewer(partioningComposite, SWT.FULL_SELECTION);
			gd = new GridData(SWT.FILL, SWT.FILL, true, true);
			gd.horizontalSpan = 2;
			fPartitionTable.getControl().setLayoutData(gd);
			fPartitionTable.setContentProvider(new ArrayContentProvider());
			fPartitionTable.getTable().setHeaderVisible(true);
			fPartitionTable.getTable().setLinesVisible(true);
			String[] columns = new String[]{SSEUIMessages.OffsetStatusLineContributionItem_9, SSEUIMessages.OffsetStatusLineContributionItem_10, SSEUIMessages.OffsetStatusLineContributionItem_11}; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
			fPartitionTable.setLabelProvider(new ITableLabelProvider() {
				public void addListener(ILabelProviderListener listener) {
				}

				public void dispose() {
				}

				public Image getColumnImage(Object element, int columnIndex) {
					return null;
				}

				public String getColumnText(Object element, int columnIndex) {
					ITypedRegion partition = (ITypedRegion) element;
					String text = null;
					switch (columnIndex) {
						case 0 :
							text = Integer.toString(partition.getOffset());
							break;
						case 1 :
							text = Integer.toString(partition.getLength());
							break;
						case 2 :
							text = partition.getType();
							break;
					}
					if (text == null)
						text = ""; //$NON-NLS-1$
					return text;
				}

				public boolean isLabelProperty(Object element, String property) {
					return false;
				}

				public void removeListener(ILabelProviderListener listener) {
				}
			});
			TableLayout tlayout = new TableLayout();
			CellEditor[] cellEditors = new CellEditor[columns.length];
			int columnWidths[] = new int[]{Display.getCurrent().getBounds().width / 14, Display.getCurrent().getBounds().width / 14, Display.getCurrent().getBounds().width / 5};
			for (int i = 0; i < columns.length; i++) {
				tlayout.addColumnData(new ColumnWeightData(1));
				TableColumn tc = new TableColumn(fPartitionTable.getTable(), SWT.NONE);
				tc.setText(columns[i]);
				tc.setResizable(true);
				tc.setWidth(columnWidths[i]);
			}
			fPartitionTable.setCellEditors(cellEditors);
			fPartitionTable.setColumnProperties(columns);
			final String[] partitionings = (fDocument instanceof IDocumentExtension3) ? ((IDocumentExtension3) fDocument).getPartitionings() : new String[]{IDocumentExtension3.DEFAULT_PARTITIONING};
			partitioningCombo.setItems(partitionings);
			partitioningCombo.addSelectionListener(new SelectionAdapter() {
				public void widgetSelected(SelectionEvent e) {
					ISelection sel = fTextEditor.getSelectionProvider().getSelection();
					ITextSelection textSelection = (ITextSelection) sel;
					try {
						String partitionerText = fDocument instanceof IDocumentExtension3 ? ((IDocumentExtension3) fDocument).getDocumentPartitioner(partitioningCombo.getItem(partitioningCombo.getSelectionIndex())).toString() : ("" + fDocument.getDocumentPartitioner()); //$NON-NLS-1$
						partitionerInstanceLabel.setText(SSEUIMessages.OffsetStatusLineContributionItem_13 + partitionerText); //$NON-NLS-1$
						fPartitionTable.setInput(TextUtilities.computePartitioning(fDocument, partitioningCombo.getItem(partitioningCombo.getSelectionIndex()), textSelection.getOffset(), textSelection.getLength(), true));
					}
					catch (BadLocationException e1) {
						fPartitionTable.setInput(new ITypedRegion[0]);
					}
				}
			});
			try {
				if (partitionings.length > 0) {
					String selectedPartitioning = partitioningCombo.getItem(0);
					if (Utilities.contains(partitionings, IStructuredPartitioning.DEFAULT_STRUCTURED_PARTITIONING)) {
						selectedPartitioning = IStructuredPartitioning.DEFAULT_STRUCTURED_PARTITIONING;
						for (int i = 0; i < partitionings.length; i++) {
							if (partitionings[i].equals(IStructuredPartitioning.DEFAULT_STRUCTURED_PARTITIONING)) {
								partitioningCombo.select(i);
							}
						}
					}
					else {
						partitioningCombo.select(0);
					}
					ISelection sel = fTextEditor.getSelectionProvider().getSelection();
					ITextSelection textSelection = (ITextSelection) sel;
					ITypedRegion[] partitions = TextUtilities.computePartitioning(fDocument, selectedPartitioning, textSelection.getOffset(), textSelection.getLength(), true);
					fPartitionTable.setInput(partitions);
					String partitionerText = fDocument instanceof IDocumentExtension3 ? ((IDocumentExtension3) fDocument).getDocumentPartitioner(partitioningCombo.getItem(partitioningCombo.getSelectionIndex())).toString() : ("" + fDocument.getDocumentPartitioner()); //$NON-NLS-1$
					partitionerInstanceLabel.setText(SSEUIMessages.OffsetStatusLineContributionItem_13 + partitionerText); //$NON-NLS-1$
				}
				else {
					ISelection sel = fTextEditor.getSelectionProvider().getSelection();
					ITextSelection textSelection = (ITextSelection) sel;
					fPartitionTable.setInput(fDocument.computePartitioning(textSelection.getOffset(), textSelection.getLength()));
				}
			}
			catch (BadLocationException e1) {
				fPartitionTable.setInput(new ITypedRegion[0]);
			}
			partitioningCombo.setFocus();


			final StyledText text = new StyledText(sash, SWT.MULTI | SWT.READ_ONLY);
			fPartitionTable.addSelectionChangedListener(new ISelectionChangedListener() {
				public void selectionChanged(SelectionChangedEvent event) {
					if (event.getSelection() instanceof IStructuredSelection) {
						IRegion partition = (IRegion) ((IStructuredSelection) event.getSelection()).getFirstElement();
						IDocument document = fTextEditor.getDocumentProvider().getDocument(fTextEditor.getEditorInput());
						String source;
						try {
							source = document.get(partition.getOffset(), partition.getLength());
							text.setEnabled(true);
							text.setText(source);
						}
						catch (BadLocationException e) {
							e.printStackTrace();
						}
					}
				}
			});
			text.setEnabled(false);
		}

		/**
		 * @param composite
		 * @return
		 */
		private Composite createRegionTabContents(SashForm sashForm) {
			ISelection sel = fTextEditor.getSelectionProvider().getSelection();
			final ITextSelection textSelection = (ITextSelection) sel;
			final List documentRegions = new ArrayList();
			if (fDocument instanceof IStructuredDocument) {
				IStructuredDocument structuredDocument = (IStructuredDocument) fDocument;
				int pos = textSelection.getOffset();
				int end = textSelection.getOffset() + textSelection.getLength();
				IStructuredDocumentRegion docRegion = structuredDocument.getRegionAtCharacterOffset(pos);
				IStructuredDocumentRegion endRegion = structuredDocument.getRegionAtCharacterOffset(end);
				if (pos < end) {
					while (docRegion != endRegion) {
						documentRegions.add(docRegion);
						docRegion = docRegion.getNext();
					}
				}
				documentRegions.add(docRegion);
			}

			final TreeViewer tree = new TreeViewer(sashForm, SWT.V_SCROLL | SWT.H_SCROLL);
			final String START = SSEUIMessages.OffsetStatusLineContributionItem_15; //$NON-NLS-1$
			final String LENGTH = SSEUIMessages.OffsetStatusLineContributionItem_16; //$NON-NLS-1$
			final String TEXTLENGTH = SSEUIMessages.OffsetStatusLineContributionItem_17; //$NON-NLS-1$
			final String CONTEXT = SSEUIMessages.OffsetStatusLineContributionItem_18; //$NON-NLS-1$
			tree.setContentProvider(new ITreeContentProvider() {
				public void dispose() {
				}

				public Object[] getChildren(Object parentElement) {
					List children = new ArrayList(0);
					if (parentElement instanceof ITextSelection) {
						children.addAll(documentRegions);
					}
					if (parentElement instanceof ITextRegionCollection) {
						children.add(((ITextRegionCollection) parentElement).getRegions().toArray());
					}
					if (parentElement instanceof ITextRegion) {
						children.add(new KeyValuePair(CONTEXT, ((ITextRegion) parentElement).getType()));
						children.add(new KeyValuePair(START, Integer.toString(((ITextRegion) parentElement).getStart())));
						children.add(new KeyValuePair(TEXTLENGTH, Integer.toString(((ITextRegion) parentElement).getTextLength())));
						children.add(new KeyValuePair(LENGTH, Integer.toString(((ITextRegion) parentElement).getLength())));
					}
					if (parentElement instanceof ITextRegionList) {
						children.add(Arrays.asList(((ITextRegionList) parentElement).toArray()));
					}
					if (parentElement instanceof Collection) {
						children.addAll((Collection) parentElement);
					}
					if (parentElement instanceof Object[]) {
						children.addAll(Arrays.asList((Object[]) parentElement));
					}
					return children.toArray();
				}

				public Object[] getElements(Object inputElement) {
					return documentRegions.toArray();
				}

				public Object getParent(Object element) {
					if (element instanceof IStructuredDocumentRegion)
						return ((IStructuredDocumentRegion) element).getParentDocument();
					if (element instanceof ITextRegionContainer) {
						return ((ITextRegionContainer) element).getParent();
					}
					return null;
				}

				public boolean hasChildren(Object element) {
					return !(element instanceof KeyValuePair);
				}

				public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
				}
			});
			tree.setLabelProvider(new LabelProvider() {
				public String getText(Object element) {
					if (element instanceof KeyValuePair)
						return ((KeyValuePair) element).fKey.toString().toLowerCase() + ": " + ((KeyValuePair) element).fValue; //$NON-NLS-1$
					if (element instanceof IStructuredDocumentRegion) {
						IStructuredDocumentRegion documentRegion = (IStructuredDocumentRegion) element;
						int packageNameLength = documentRegion.getClass().getPackage().getName().length();
						if (packageNameLength > 0)
							packageNameLength++;
						String name = documentRegion.getClass().getName().substring(packageNameLength);
						String text = "[" + documentRegion.getStartOffset() + "-" + documentRegion.getEndOffset() + "] " + name + "@" + element.hashCode() + " " + documentRegion.getType(); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$
						return text;
					}
					if (element instanceof ITextRegion) {
						ITextRegion textRegion = (ITextRegion) element;
						int packageNameLength = textRegion.getClass().getPackage().getName().length();
						if (packageNameLength > 0)
							packageNameLength++;
						String name = textRegion.getClass().getName().substring(packageNameLength);
						String text = "[" + textRegion.getStart() + "-" + textRegion.getEnd() + "] " + name + "@" + element.hashCode() + " " + textRegion.getType(); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$
						return text;
					}
					return super.getText(element);
				}
			});
			tree.setInput(fDocument);


			final Text displayText = new Text(sashForm, SWT.V_SCROLL | SWT.H_SCROLL | SWT.READ_ONLY | SWT.BORDER);
			displayText.setBackground(sashForm.getDisplay().getSystemColor(SWT.COLOR_LIST_BACKGROUND));
			tree.addSelectionChangedListener(new ISelectionChangedListener() {
				public void selectionChanged(SelectionChangedEvent event) {
					if (event.getSelection() instanceof IStructuredSelection) {
						Object o = ((IStructuredSelection) event.getSelection()).getFirstElement();
						if (o instanceof KeyValuePair)
							displayText.setText(((KeyValuePair) o).fValue.toString());
						else if (o instanceof ITextSelection) {
							ITextSelection text = (ITextSelection) o;
							try {
								displayText.setText(fDocument.get(text.getOffset(), text.getLength()));
							}
							catch (BadLocationException e) {
								displayText.setText(""); //$NON-NLS-1$
							}
						}
						else if (o instanceof ITextRegionCollection) {
							ITextRegionCollection region = (ITextRegionCollection) o;
							displayText.setText(region.getFullText());
						}
						else
							displayText.setText("" + o); //$NON-NLS-1$
					}
				}
			});
			return sashForm;
		}

		private void fillSelectionTabContents(Composite area, List selection, String description) {
			area.setLayout(new GridLayout());
			area.setLayoutData(new GridData());

			Label typeName = new Label(area, SWT.WRAP);
			typeName.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
			typeName.setText(description); //$NON-NLS-1$

			(new Label(area, SWT.NONE)).setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
			SashForm structuredSashForm = new SashForm(area, SWT.NONE);
			structuredSashForm.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
			structuredSashForm.setOrientation(SWT.VERTICAL);

			final TableViewer structuredSelectionTable = new TableViewer(structuredSashForm, SWT.FULL_SELECTION | SWT.SINGLE | SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER);

			structuredSelectionTable.getTable().setHeaderVisible(true);
			structuredSelectionTable.getTable().setLinesVisible(true);
			structuredSelectionTable.setSorter(new ViewerSorter() {
				public int category(Object element) {
					if (element instanceof IndexedRegion)
						return ((IndexedRegion) element).getStartOffset();
					return super.category(element);
				}
			});

			structuredSelectionTable.setLabelProvider(new ITableLabelProvider() {
				public void addListener(ILabelProviderListener listener) {
				}

				public void dispose() {
				}

				public Image getColumnImage(Object element, int columnIndex) {
					if (element instanceof INodeNotifier) {
						INodeAdapter adapterFor = ((INodeNotifier) element).getAdapterFor(IJFaceNodeAdapter.class);
						if (columnIndex == 2 && adapterFor != null && adapterFor instanceof IJFaceNodeAdapter) {
							IJFaceNodeAdapter adapter = (IJFaceNodeAdapter) adapterFor;
							return adapter.getLabelImage((element));
						}
					}
					return null;
				}

				public String getColumnText(Object element, int columnIndex) {
					String text = null;
					if (element != null) {
						switch (columnIndex) {
							case 0 : {
								text = String.valueOf(((List) structuredSelectionTable.getInput()).indexOf(element));
							}
								break;
							case 1 : {
								text = element.getClass().getName();
							}
								break;
							case 2 : {
								text = StringUtils.firstLineOf(element.toString());
							}
								break;
							default :
								text = ""; //$NON-NLS-1$
						}
					}
					return text;
				}

				public boolean isLabelProperty(Object element, String property) {
					return false;
				}

				public void removeListener(ILabelProviderListener listener) {
				}
			});

			TableLayout tlayout = new TableLayout();
			tlayout.addColumnData(new ColumnWeightData(7, true));
			tlayout.addColumnData(new ColumnWeightData(28, true));
			tlayout.addColumnData(new ColumnWeightData(50, true));
			structuredSelectionTable.getTable().setLayout(tlayout);

			TableColumn tc = new TableColumn(structuredSelectionTable.getTable(), SWT.NONE);
			tc.setText("Item"); //$NON-NLS-1$
			tc.setResizable(true);
			tc.setWidth(40);

			tc = new TableColumn(structuredSelectionTable.getTable(), SWT.NONE);
			tc.setText("Class"); //$NON-NLS-1$
			tc.setResizable(true);
			tc.setWidth(40);

			tc = new TableColumn(structuredSelectionTable.getTable(), SWT.NONE);
			tc.setText("Value"); //$NON-NLS-1$
			tc.setResizable(true);
			tc.setWidth(40);

			structuredSelectionTable.setContentProvider(new ArrayContentProvider());
			final List input = selection;
			structuredSelectionTable.setInput(input);

			final TreeViewer infoTree = new TreeViewer(structuredSashForm, SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER);
			infoTree.setLabelProvider(new LabelProvider() {
				public Image getImage(Object element) {
					if (element instanceof TreeViewer && infoTree.getInput() instanceof INodeNotifier) {
						INodeAdapter adapterFor = ((INodeNotifier) infoTree.getInput()).getAdapterFor(IJFaceNodeAdapter.class);
						if (adapterFor != null && adapterFor instanceof IJFaceNodeAdapter) {
							IJFaceNodeAdapter adapter = (IJFaceNodeAdapter) adapterFor;
							return adapter.getLabelImage((infoTree.getInput()));
						}
					}
					return super.getImage(element);
				}

				public String getText(Object element) {
					if (element instanceof Class) {
						return "Class: " + ((Class) element).getName(); //$NON-NLS-1$
					}
					if (element instanceof Collection) {
						return "Registered Adapters:"; //$NON-NLS-1$
					}
					if (element instanceof IRegion) {
						return "Indexed Region offset span: [" + ((IRegion) element).getOffset() + "-" + ((IRegion) element).getLength() + "]"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
					}
					if (element instanceof TreeViewer && infoTree.getInput() instanceof INodeNotifier) {
						IJFaceNodeAdapter adapter = (IJFaceNodeAdapter) ((INodeNotifier) infoTree.getInput()).getAdapterFor(IJFaceNodeAdapter.class);
						if (adapter != null) {
							return adapter.getLabelText((infoTree.getInput()));
						}
					}
					return super.getText(element);
				}
			});
			infoTree.setContentProvider(new ITreeContentProvider() {
				public void dispose() {
				}

				public Object[] getChildren(Object parentElement) {
					if (parentElement instanceof Collection)
						return ((Collection) parentElement).toArray();
					return new Object[0];
				}

				public Object[] getElements(Object inputElement) {
					List elements = new ArrayList(4);
					if (inputElement != null) {
						if (inputElement instanceof INodeNotifier && ((INodeNotifier) inputElement).getAdapterFor(IJFaceNodeAdapter.class) != null) {
							elements.add(infoTree);
						}
						elements.add(inputElement.getClass());
						if (inputElement instanceof IndexedRegion) {
							elements.add(new Region(((IndexedRegion) inputElement).getStartOffset(), ((IndexedRegion) inputElement).getEndOffset()));
						}
						if (inputElement instanceof INodeNotifier) {
							elements.add(((INodeNotifier) inputElement).getAdapters());
						}
					}
					return elements.toArray();
				}

				public Object getParent(Object element) {
					return null;
				}

				public boolean hasChildren(Object element) {
					return element instanceof Collection;
				}

				public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
				}
			});

			structuredSelectionTable.addSelectionChangedListener(new ISelectionChangedListener() {
				public void selectionChanged(SelectionChangedEvent event) {
					int selectionIndex = structuredSelectionTable.getTable().getSelectionIndex();
					if (selectionIndex != -1) {
						infoTree.setInput(structuredSelectionTable.getElementAt(selectionIndex));
					}
					else {
						infoTree.setInput(event.getSelectionProvider().getSelection());
					}
					infoTree.expandToLevel(2);
				}
			});

			structuredSashForm.setWeights(new int[]{3, 2});
		}

		private String getLineNumber(Annotation annotation) {
			int line = -1;
			if (annotation instanceof MarkerAnnotation) {
				line = MarkerUtilities.getLineNumber(((MarkerAnnotation) annotation).getMarker());//$NON-NLS-1$
			}
			else {
				IAnnotationModel annotationModel = fTextEditor.getDocumentProvider().getAnnotationModel(fTextEditor.getEditorInput());
				Position p = annotationModel.getPosition(annotation);
				if (p != null && !p.isDeleted()) {
					try {
						line = fDocument.getLineOfOffset(p.getOffset());
					}
					catch (BadLocationException e) {
					}
				}
			}
			return Integer.toString(line);
		}
	}

	static class KeyValuePair {
		Object fKey;
		String fValue;

		public KeyValuePair(Object key, String value) {
			fKey = key;
			fValue = value;
		}
	}

	class ShowEditorInformationAction extends Action {
		public ShowEditorInformationAction() {
			super();
		}

		public void run() {
			/**
			 * TODO: Provide a more useful control, maybe a table where the
			 * selection shows you the partition's text in a StyledText pane
			 * beneath it.
			 */
			super.run();
			new InformationDialog(((Control) fTextEditor.getAdapter(Control.class)).getShell()).open();
		}
	}

	IAction fShowEditorInformationAction = new ShowEditorInformationAction();

	ITextEditor fTextEditor = null;

	/**
	 * @param id
	 */
	public OffsetStatusLineContributionItem(String id) {
		super(id);
		setToolTipText("Double-click for more information");
	}

	/**
	 * @param id
	 * @param visible
	 * @param widthInChars
	 */
	public OffsetStatusLineContributionItem(String id, boolean visible, int widthInChars) {
		super(id, visible, widthInChars);
		setToolTipText("Double-click for more information");
	}

	public void setActiveEditor(ITextEditor textEditor) {
		fTextEditor = textEditor;
		setActionHandler(fShowEditorInformationAction);
	}
}
