/*******************************************************************************
 * Copyright (c) 2000, 2004 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials 
 * are made available under the terms of the Common Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/cpl-v10.html
 * 
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/

package org.eclipse.jdt.astview.views;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.ListenerList;
import org.eclipse.core.runtime.Status;

import org.eclipse.core.filebuffers.FileBuffers;
import org.eclipse.core.filebuffers.IFileBuffer;
import org.eclipse.core.filebuffers.IFileBufferListener;
import org.eclipse.core.filebuffers.ITextFileBuffer;

import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.custom.ViewForm;
import org.eclipse.swt.events.FocusAdapter;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;

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.action.Separator;
import org.eclipse.jface.bindings.keys.KeyStroke;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.fieldassist.ContentProposalAdapter;
import org.eclipse.jface.fieldassist.IContentProposal;
import org.eclipse.jface.fieldassist.IContentProposalListener;
import org.eclipse.jface.fieldassist.IContentProposalProvider;
import org.eclipse.jface.fieldassist.TextContentAdapter;
import org.eclipse.jface.viewers.AbstractTreeViewer;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.IDoubleClickListener;
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.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerFilter;

import org.eclipse.jface.text.DocumentEvent;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentListener;
import org.eclipse.jface.text.ITextSelection;

import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IPartListener2;
import org.eclipse.ui.ISelectionListener;
import org.eclipse.ui.ISelectionService;
import org.eclipse.ui.ISharedImages;
import org.eclipse.ui.IViewSite;
import org.eclipse.ui.IWorkbenchActionConstants;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.IWorkbenchPartReference;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.actions.ActionFactory;
import org.eclipse.ui.part.DrillDownAdapter;
import org.eclipse.ui.part.IShowInSource;
import org.eclipse.ui.part.ShowInContext;
import org.eclipse.ui.part.ViewPart;
import org.eclipse.ui.texteditor.ITextEditor;
import org.eclipse.ui.texteditor.IWorkbenchActionDefinitionIds;

import org.eclipse.jdt.astview.ASTViewImages;
import org.eclipse.jdt.astview.ASTViewPlugin;
import org.eclipse.jdt.astview.EditorUtility;
import org.eclipse.jdt.astview.NodeFinder;
import org.eclipse.jdt.astview.TreeInfoCollector;

import org.eclipse.jdt.core.IBuffer;
import org.eclipse.jdt.core.IClassFile;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IOpenable;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IProblemRequestor;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.WorkingCopyOwner;
import org.eclipse.jdt.core.compiler.IProblem;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.IBinding;

import org.eclipse.jdt.ui.JavaUI;
import org.eclipse.jdt.ui.actions.ShowInPackageViewAction;

public class ASTView extends ViewPart implements IShowInSource {
	
	private class ASTViewSelectionProvider implements ISelectionProvider {
		ListenerList fListeners= new ListenerList();

		public void addSelectionChangedListener(ISelectionChangedListener listener) {
			fListeners.add(listener);
		}

		public ISelection getSelection() {
			IStructuredSelection selection= (IStructuredSelection) fViewer.getSelection();
			ArrayList externalSelection= new ArrayList();
			for (Iterator iter= selection.iterator(); iter.hasNext();) {
				Object element= iter.next();
				if (element instanceof JavaElement)
					externalSelection.add(((JavaElement) element).getJavaElement());
				else {
					//TODO: support for other node types?
				}
			}
			return new StructuredSelection(externalSelection);
		}

		public void removeSelectionChangedListener(ISelectionChangedListener listener) {
			fListeners.remove(listener);
		}

		public void setSelection(ISelection selection) {
			//not supported
		}
	}

	private class ASTLevelToggle extends Action {
		private int fLevel;

		public ASTLevelToggle(String label, int level) {
			super(label, AS_RADIO_BUTTON);
			fLevel= level;
			if (level == getCurrentASTLevel()) {
				setChecked(true);
			}
		}
	
		public int getLevel() {
			return fLevel;
		}
		
		public void run() {
			setASTLevel(fLevel, true);
		}
	}
	
	private static class ListenerMix implements ISelectionListener, IFileBufferListener, IDocumentListener, ISelectionChangedListener, IDoubleClickListener, IPartListener2 {
		
		private boolean fASTViewVisible= true;
		private ASTView fView;
		
		public ListenerMix(ASTView view) {
			fView= view;
		}
		
		public void dispose() {
			fView= null;
		}

		public void selectionChanged(IWorkbenchPart part, ISelection selection) {
			if (fASTViewVisible) {
				fView.handleEditorPostSelectionChanged(part, selection);
			}
		}

		/* (non-Javadoc)
		 * @see org.eclipse.core.filebuffers.IFileBufferListener#bufferCreated(org.eclipse.core.filebuffers.IFileBuffer)
		 */
		public void bufferCreated(IFileBuffer buffer) {
			// not interesting
		}

		/* (non-Javadoc)
		 * @see org.eclipse.core.filebuffers.IFileBufferListener#bufferDisposed(org.eclipse.core.filebuffers.IFileBuffer)
		 */
		public void bufferDisposed(IFileBuffer buffer) {
			if (buffer instanceof ITextFileBuffer) {
				fView.handleDocumentDisposed(((ITextFileBuffer) buffer).getDocument());
			}
		}

		/* (non-Javadoc)
		 * @see org.eclipse.core.filebuffers.IFileBufferListener#bufferContentAboutToBeReplaced(org.eclipse.core.filebuffers.IFileBuffer)
		 */
		public void bufferContentAboutToBeReplaced(IFileBuffer buffer) {
			// not interesting
		}

		/* (non-Javadoc)
		 * @see org.eclipse.core.filebuffers.IFileBufferListener#bufferContentReplaced(org.eclipse.core.filebuffers.IFileBuffer)
		 */
		public void bufferContentReplaced(IFileBuffer buffer) {
			// not interesting
		}

		/* (non-Javadoc)
		 * @see org.eclipse.core.filebuffers.IFileBufferListener#stateChanging(org.eclipse.core.filebuffers.IFileBuffer)
		 */
		public void stateChanging(IFileBuffer buffer) {
			// not interesting
		}

		/* (non-Javadoc)
		 * @see org.eclipse.core.filebuffers.IFileBufferListener#dirtyStateChanged(org.eclipse.core.filebuffers.IFileBuffer, boolean)
		 */
		public void dirtyStateChanged(IFileBuffer buffer, boolean isDirty) {
			// not interesting
		}

		/* (non-Javadoc)
		 * @see org.eclipse.core.filebuffers.IFileBufferListener#stateValidationChanged(org.eclipse.core.filebuffers.IFileBuffer, boolean)
		 */
		public void stateValidationChanged(IFileBuffer buffer, boolean isStateValidated) {
			// not interesting
		}

		/* (non-Javadoc)
		 * @see org.eclipse.core.filebuffers.IFileBufferListener#underlyingFileMoved(org.eclipse.core.filebuffers.IFileBuffer, org.eclipse.core.runtime.IPath)
		 */
		public void underlyingFileMoved(IFileBuffer buffer, IPath path) {
			// not interesting
		}

		/* (non-Javadoc)
		 * @see org.eclipse.core.filebuffers.IFileBufferListener#underlyingFileDeleted(org.eclipse.core.filebuffers.IFileBuffer)
		 */
		public void underlyingFileDeleted(IFileBuffer buffer) {
			// not interesting
		}

		/* (non-Javadoc)
		 * @see org.eclipse.core.filebuffers.IFileBufferListener#stateChangeFailed(org.eclipse.core.filebuffers.IFileBuffer)
		 */
		public void stateChangeFailed(IFileBuffer buffer) {
			// not interesting
		}

		/* (non-Javadoc)
		 * @see org.eclipse.jface.text.IDocumentListener#documentAboutToBeChanged(org.eclipse.jface.text.DocumentEvent)
		 */
		public void documentAboutToBeChanged(DocumentEvent event) {
			// not interesting
		}

		/* (non-Javadoc)
		 * @see org.eclipse.jface.text.IDocumentListener#documentChanged(org.eclipse.jface.text.DocumentEvent)
		 */
		public void documentChanged(DocumentEvent event) {
			fView.handleDocumentChanged(event.getDocument());
		}

		/* (non-Javadoc)
		 * @see org.eclipse.jface.viewers.ISelectionChangedListener#selectionChanged(org.eclipse.jface.viewers.SelectionChangedEvent)
		 */
		public void selectionChanged(SelectionChangedEvent event) {
			fView.handleSelectionChanged(event.getSelection());
		}

		/* (non-Javadoc)
		 * @see org.eclipse.jface.viewers.IDoubleClickListener#doubleClick(org.eclipse.jface.viewers.DoubleClickEvent)
		 */
		public void doubleClick(DoubleClickEvent event) {
			fView.handleDoubleClick(event);
		}
		
		/* (non-Javadoc)
		 * @see org.eclipse.ui.IPartListener2#partHidden(org.eclipse.ui.IWorkbenchPartReference)
		 */
		public void partHidden(IWorkbenchPartReference partRef) {
			IWorkbenchPart part= partRef.getPart(false);
			if (part == fView) {
				fASTViewVisible= false;
			}
		}

		/* (non-Javadoc)
		 * @see org.eclipse.ui.IPartListener2#partVisible(org.eclipse.ui.IWorkbenchPartReference)
		 */
		public void partVisible(IWorkbenchPartReference partRef) {
			IWorkbenchPart part= partRef.getPart(false);
			if (part == fView) {
				fASTViewVisible= true;
			}
		}

		/* (non-Javadoc)
		 * @see org.eclipse.ui.IPartListener2#partActivated(org.eclipse.ui.IWorkbenchPartReference)
		 */
		public void partActivated(IWorkbenchPartReference partRef) {
			// not interesting
		}

		/* (non-Javadoc)
		 * @see org.eclipse.ui.IPartListener2#partBroughtToTop(org.eclipse.ui.IWorkbenchPartReference)
		 */
		public void partBroughtToTop(IWorkbenchPartReference partRef) {
			// not interesting
		}

		/* (non-Javadoc)
		 * @see org.eclipse.ui.IPartListener2#partClosed(org.eclipse.ui.IWorkbenchPartReference)
		 */
		public void partClosed(IWorkbenchPartReference partRef) {
			// not interesting
		}

		/* (non-Javadoc)
		 * @see org.eclipse.ui.IPartListener2#partDeactivated(org.eclipse.ui.IWorkbenchPartReference)
		 */
		public void partDeactivated(IWorkbenchPartReference partRef) {
			// not interesting
		}

		/* (non-Javadoc)
		 * @see org.eclipse.ui.IPartListener2#partOpened(org.eclipse.ui.IWorkbenchPartReference)
		 */
		public void partOpened(IWorkbenchPartReference partRef) {
			// not interesting
		}

		/* (non-Javadoc)
		 * @see org.eclipse.ui.IPartListener2#partInputChanged(org.eclipse.ui.IWorkbenchPartReference)
		 */
		public void partInputChanged(IWorkbenchPartReference partRef) {
			// not interesting
		}
	}
		
	private final static String SETTINGS_LINK_WITH_EDITOR= "link_with_editor"; //$NON-NLS-1$
	private final static String SETTINGS_USE_RECONCILER= "use_reconciler"; //$NON-NLS-1$
	private final static String SETTINGS_NO_BINDINGS= "create_bindings"; //$NON-NLS-1$
	private final static String SETTINGS_JLS= "jls"; //$NON-NLS-1$
	private final static int DEFAULT_AST_LEVEL= AST.JLS25;
	private final static int[] KNOWN_AST_LEVELS= new int[] {
			AST.JLS25, AST.JLS24, AST.JLS23, AST.JLS22, AST.JLS21,
			AST.JLS20, AST.JLS19, AST.JLS18, AST.JLS17, AST.JLS16,
			AST.JLS15, AST.JLS14, AST.JLS13, AST.JLS12, AST.JLS11,
			AST.JLS10, AST.JLS9, AST.JLS8, AST.JLS4, AST.JLS3,
			AST.JLS2
	};
	private final static String SETTINGS_SHOW_INSPECTOR= "show_inspector"; //$NON-NLS-1$
	private final static String SETTINGS_SHOW_TRAY= "show_tray"; //$NON-NLS-1$
	private final static String SETTINGS_VERTICAL_LAYOUT= "vertical_layout"; //$NON-NLS-1$
	private final static String SETTINGS_AUTO_EXPAND= "auto_expand"; //$NON-NLS-1$
	private final static String SETTINGS_SHOW_RANGES= "show_ranges"; //$NON-NLS-1$
	private final static String SETTINGS_SHOW_FLAGS= "show_flags"; //$NON-NLS-1$
	private final static String SETTINGS_HIGHLIGHT_SELECTION= "highlight_selection"; //$NON-NLS-1$
	private final static String SETTINGS_COMPACT_LABELS= "compact_labels"; //$NON-NLS-1$
	private final static String SETTINGS_ONLY_UNRESOLVED_BINDINGS= "only_unresolved_bindings"; //$NON-NLS-1$
	private final static String SETTINGS_SHOW_HEADER= "show_header"; //$NON-NLS-1$
	private final static String SETTINGS_SHOW_STATUS_BAR= "show_status_bar"; //$NON-NLS-1$
	private final static String SETTINGS_SHOW_TREE_LINES= "show_tree_lines"; //$NON-NLS-1$
	private final static String SETTINGS_DENSE_MODE= "dense_mode"; //$NON-NLS-1$
	private final static String SETTINGS_VISUAL_THEME= "visual_theme"; //$NON-NLS-1$
	private final static String SETTINGS_ELEMENT_FILTER= "element_filter"; //$NON-NLS-1$
	private final static String SETTINGS_SHOW_SOURCE_PREVIEW= "show_source_preview"; //$NON-NLS-1$
	private final static String SETTINGS_SHOW_BREADCRUMB= "show_breadcrumb"; //$NON-NLS-1$
	private final static String SETTINGS_PIN_INSPECTOR= "pin_inspector"; //$NON-NLS-1$
	private final static String SETTINGS_ONLY_MALFORMED= "only_malformed"; //$NON-NLS-1$
	private final static String SETTINGS_INSPECTOR_DETAIL= "inspector_detail"; //$NON-NLS-1$
	private final static String SETTINGS_RANGE_MODE= "range_mode"; //$NON-NLS-1$
	private final static String SETTINGS_QUALIFIED_NODE_NAMES= "qualified_node_names"; //$NON-NLS-1$
	private final static String SETTINGS_SHOW_PROPERTY_COUNT= "show_property_count"; //$NON-NLS-1$
	private final static String SETTINGS_SHOW_FLAG_HEX= "show_flag_hex"; //$NON-NLS-1$
	private final static String SETTINGS_SOURCE_CONTEXT= "source_context"; //$NON-NLS-1$
	private final static String SETTINGS_VIEW_PRESET= "view_preset"; //$NON-NLS-1$
	private final static String SETTINGS_SEARCH_CASE_SENSITIVE= "search_case_sensitive"; //$NON-NLS-1$
	private final static String SETTINGS_SEARCH_REGEX= "search_regex"; //$NON-NLS-1$
	private final static String SETTINGS_SEARCH_SCOPE= "search_scope"; //$NON-NLS-1$
	private final static String SETTINGS_MAX_DEPTH= "max_depth"; //$NON-NLS-1$
	private final static String SETTINGS_HIDE_LEAF_ATTRIBUTES= "hide_leaf_attributes"; //$NON-NLS-1$
	private final static String SETTINGS_FOCUS_SUBTREE= "focus_subtree"; //$NON-NLS-1$
	private final static String SETTINGS_INTELLISENSE= "intellisense"; //$NON-NLS-1$
	private final static String SETTINGS_INTELLISENSE_AUTO_REVEAL= "intellisense_auto_reveal"; //$NON-NLS-1$
	private final static String SETTINGS_INTELLISENSE_MODE= "intellisense_mode"; //$NON-NLS-1$
	private final static String SETTINGS_SHOW_GRAPH= "show_graph"; //$NON-NLS-1$
	private final static String SETTINGS_GRAPH_DEPTH= "graph_depth"; //$NON-NLS-1$
	private final static String SETTINGS_GRAPH_ORIENTATION= "graph_orientation"; //$NON-NLS-1$
	private final static String SETTINGS_GRAPH_SYNC_SELECTION= "graph_sync_selection"; //$NON-NLS-1$
	private final static String SETTINGS_GRAPH_COMPACT= "graph_compact"; //$NON-NLS-1$
	private final static String SETTINGS_GRAPH_ZOOM= "graph_zoom"; //$NON-NLS-1$
	private final static String SETTINGS_GRAPH_OVERVIEW= "graph_overview"; //$NON-NLS-1$
	private final static String SETTINGS_GRAPH_HEATMAP= "graph_heatmap"; //$NON-NLS-1$
	
	private final static int THEME_LIGHT= 0;
	private final static int THEME_DARK= 1;
	private final static int THEME_CONTRAST= 2;
	
	private final static int FILTER_ALL= 0;
	private final static int FILTER_AST_NODES= 1;
	private final static int FILTER_PROPERTIES= 2;
	private final static int FILTER_BINDINGS= 3;
	private final static int FILTER_PROBLEMS= 4;
	
	private final static int INSPECTOR_SUMMARY= 0;
	private final static int INSPECTOR_TECHNICAL= 1;
	private final static int INSPECTOR_SOURCE= 2;
	
	private final static int PRESET_CUSTOM= 0;
	private final static int PRESET_REVIEW= 1;
	private final static int PRESET_DIAGNOSTICS= 2;
	private final static int PRESET_BINDINGS= 3;
	private final static int PRESET_SOURCE= 4;
	
	private final static int SEARCH_LABEL= 0;
	private final static int SEARCH_CLASS= 1;
	private final static int SEARCH_BOTH= 2;
	
	private final static int SENSE_ALL= 0;
	private final static int SENSE_NODES= 1;
	private final static int SENSE_PROPERTIES= 2;
	private final static int SENSE_BINDINGS= 3;
	
	private final static int GRAPH_VERTICAL= 0;
	private final static int GRAPH_HORIZONTAL= 1;

	
	private TreeViewer fViewer;
	private ASTViewLabelProvider fLabelProvider;
	private DrillDownAdapter fDrillDownAdapter;
	private Action fFocusAction;
	private Action fRefreshAction;
	private Action fUseReconcilerAction;
	private Action fCreateBindingsAction;
	private Action fCollapseAction;
	private Action fExpandAction;
	private Action fClearAction;
	private TreeCopyAction fCopyAction;
	private Action fDoubleClickAction;
	private Action fLinkWithEditor;
	private Action fToggleInspectorAction;
	private Action fToggleTrayAction;
	private Action fToggleLayoutAction;
	private Action fShowRangesAction;
	private Action fShowFlagsAction;
	private Action fHighlightSelectionAction;
	private Action fCompactLabelsAction;
	private Action fOnlyUnresolvedBindingsAction;
	private Action fShowHeaderAction;
	private Action fShowStatusBarAction;
	private Action fShowTreeLinesAction;
	private Action fDenseModeAction;
	private Action fShowSourcePreviewAction;
	private Action fShowBreadcrumbAction;
	private Action fPinInspectorAction;
	private Action fOnlyMalformedAction;
	private Action fQualifiedNodeNamesAction;
	private Action fShowPropertyCountAction;
	private Action fShowFlagHexAction;
	private Action fSearchCaseSensitiveAction;
	private Action fSearchRegexAction;
	private Action fHideLeafAttributesAction;
	private Action fFocusSubtreeAction;
	private Action fIntelliSenseAction;
	private Action fIntelliSenseAutoRevealAction;
	private Action fShowGraphAction;
	private Action fGraphSyncSelectionAction;
	private Action fGraphCompactAction;
	private Action fGraphOverviewAction;
	private Action fGraphHeatmapAction;
	private Action[] fAutoExpandActions;
	private Action[] fThemeActions;
	private Action[] fElementFilterActions;
	private Action[] fInspectorDetailActions;
	private Action[] fRangeModeActions;
	private Action[] fViewPresetActions;
	private Action[] fSearchScopeActions;
	private Action[] fMaxDepthActions;
	private Action[] fIntelliSenseModeActions;
	private Action[] fGraphDepthActions;
	private Action[] fGraphOrientationActions;
	private Action[] fGraphZoomActions;
	
	private ASTLevelToggle[] fASTVersionToggleActions;
	private int fCurrentASTLevel;
	
	private ITextEditor fEditor;
	private IOpenable fOpenable;
	private CompilationUnit fRoot;
	private IDocument fCurrentDocument;
	
	private boolean fDoLinkWithEditor;
	private boolean fDoUseReconciler;
	private boolean fCreateBindings;
	private boolean fShowInspector;
	private boolean fShowTray;
	private boolean fVerticalLayout;
	private boolean fShowRanges;
	private boolean fShowFlags;
	private boolean fHighlightSelection;
	private boolean fCompactLabels;
	private boolean fOnlyUnresolvedBindings;
	private boolean fShowHeader;
	private boolean fShowStatusBar;
	private boolean fShowTreeLines;
	private boolean fDenseMode;
	private boolean fShowSourcePreview;
	private boolean fShowBreadcrumb;
	private boolean fPinInspector;
	private boolean fOnlyMalformed;
	private boolean fQualifiedNodeNames;
	private boolean fShowPropertyCount;
	private boolean fShowFlagHex;
	private boolean fSearchCaseSensitive;
	private boolean fSearchRegex;
	private boolean fHideLeafAttributes;
	private boolean fFocusSubtree;
	private boolean fIntelliSense;
	private boolean fIntelliSenseAutoReveal;
	private boolean fShowGraph;
	private boolean fGraphSyncSelection;
	private boolean fGraphCompact;
	private boolean fGraphOverview;
	private boolean fGraphHeatmap;
	private int fAutoExpandLevel;
	private int fVisualTheme;
	private int fElementFilter;
	private int fInspectorDetail;
	private int fRangeMode;
	private int fSourceContextLines;
	private int fViewPreset;
	private int fSearchScope;
	private int fMaxDepth;
	private int fIntelliSenseMode;
	private int fGraphDepth;
	private int fGraphOrientation;
	private int fGraphZoom;
	private ASTNode fSubtreeRoot;
	private ASTNode fGraphRoot;
	private Object fPreviousDouble;
	
	private ListenerMix fSuperListener;

	private Composite fParent;
	private Composite fHeader;
	private SashForm fWorkspace;
	private Composite fAstPane;
	private Composite fStatusBar;
	private SashForm fSash;
	private SashForm fGraphStack;
	private TreeViewer fTray;
	private ViewForm fGraphForm;
	private Canvas fGraphCanvas;
	private Label fGraphTitle;
	private ViewForm fTrayForm;
	private ArrayList fTrayRoots;
	private Action fAddToTrayAction;
	private ISelectionChangedListener fTrayUpdater;
	private Action fDeleteAction;
	private IDialogSettings fDialogSettings;
	private Label fHeroTitle;
	private Label fHeroSubtitle;
	private Label fMetricNodes;
	private Label fMetricMode;
	private Label fMetricHealth;
	private Label fInspectorTitle;
	private Label fInspectorKind;
	private Label fInspectorRange;
	private Label fInspectorExtra;
	private Label fInspectorPath;
	private Label fInspectorStats;
	private Text fInspectorPreview;
	private Composite fInspector;
	private Text fSearchText;
	private Text fGraphSenseText;
	private Combo fExpandCombo;
	private Combo fElementFilterCombo;
	private Combo fThemeCombo;
	private Combo fInspectorDetailCombo;
	private Combo fRangeModeCombo;
	private Combo fSourceContextCombo;
	private Combo fPresetCombo;
	private Combo fSearchScopeCombo;
	private Combo fMaxDepthCombo;
	private Combo fIntelliSenseModeCombo;
	private Combo fGraphDepthCombo;
	private Combo fGraphOrientationCombo;
	private Combo fGraphZoomCombo;
	private Button fInspectorButton;
	private Button fTrayButton;
	private Button fLayoutButton;
	private Button fCompactButton;
	private Button fDenseButton;
	private Button fLinesButton;
	private Button fPreviewButton;
	private Button fPinButton;
	private Button fMalformedButton;
	private Button fQualifiedButton;
	private Button fPropsButton;
	private Button fHexButton;
	private Button fCaseButton;
	private Button fRegexButton;
	private Button fLeafButton;
	private Button fSubtreeButton;
	private Button fSenseButton;
	private Button fAutoRevealButton;
	private Button fGraphButton;
	private Button fGraphSyncButton;
	private Button fGraphCompactButton;
	private Button fGraphOverviewButton;
	private Button fGraphHeatmapButton;
	private Button fGraphZoomInButton;
	private Button fGraphZoomOutButton;
	private Button fGraphFitButton;
	private ContentProposalAdapter fSearchProposalAdapter;
	private ContentProposalAdapter fGraphProposalAdapter;
	private ArrayList fGraphNodes= new ArrayList();
	private Label fStatusFilter;
	private Label fStatusSelection;
	private Label fStatusTray;
	private String fSearchPattern= ""; //$NON-NLS-1$
	private ViewerFilter fSearchFilter;
	private ViewerFilter fUnresolvedBindingsFilter;
	private ViewerFilter fElementTypeFilter;
	private ViewerFilter fMalformedFilter;
	private ViewerFilter fDepthFilter;
	private ViewerFilter fLeafAttributeFilter;
	private ViewerFilter fSubtreeFilter;
	private Color fShell;
	private Color fPanel;
	private Color fPanelDeep;
	private Color fAccent;
	private Color fAccentSoft;
	private Color fText;
	private Color fMutedText;
	private Color fBorder;
	private Color fWarning;
	private Color fDiagramCanvas;
	private Color fDiagramUnitFill;
	private Color fDiagramModuleFill;
	private Color fDiagramGroupFill;
	private Color fDiagramLeafFill;
	private Color fDiagramProblemFill;
	private Font fHeroFont;
	private Font fMonoFont;

	
	public ASTView() {
		fSuperListener= null;
		fDialogSettings= ASTViewPlugin.getDefault().getDialogSettings();
		fDoLinkWithEditor= fDialogSettings.getBoolean(SETTINGS_LINK_WITH_EDITOR);
		fDoUseReconciler= fDialogSettings.getBoolean(SETTINGS_USE_RECONCILER);
		fCreateBindings= !fDialogSettings.getBoolean(SETTINGS_NO_BINDINGS); // inverse so that default is to create bindings
		fShowInspector= getBooleanSetting(SETTINGS_SHOW_INSPECTOR, true);
		fShowTray= getBooleanSetting(SETTINGS_SHOW_TRAY, true);
		fVerticalLayout= getBooleanSetting(SETTINGS_VERTICAL_LAYOUT, false);
		fShowRanges= getBooleanSetting(SETTINGS_SHOW_RANGES, true);
		fShowFlags= getBooleanSetting(SETTINGS_SHOW_FLAGS, false);
		fHighlightSelection= getBooleanSetting(SETTINGS_HIGHLIGHT_SELECTION, true);
		fCompactLabels= getBooleanSetting(SETTINGS_COMPACT_LABELS, false);
		fOnlyUnresolvedBindings= getBooleanSetting(SETTINGS_ONLY_UNRESOLVED_BINDINGS, false);
		fShowHeader= getBooleanSetting(SETTINGS_SHOW_HEADER, true);
		fShowStatusBar= getBooleanSetting(SETTINGS_SHOW_STATUS_BAR, true);
		fShowTreeLines= getBooleanSetting(SETTINGS_SHOW_TREE_LINES, false);
		fDenseMode= getBooleanSetting(SETTINGS_DENSE_MODE, false);
		fShowSourcePreview= getBooleanSetting(SETTINGS_SHOW_SOURCE_PREVIEW, true);
		fShowBreadcrumb= getBooleanSetting(SETTINGS_SHOW_BREADCRUMB, true);
		fPinInspector= getBooleanSetting(SETTINGS_PIN_INSPECTOR, false);
		fOnlyMalformed= getBooleanSetting(SETTINGS_ONLY_MALFORMED, false);
		fQualifiedNodeNames= getBooleanSetting(SETTINGS_QUALIFIED_NODE_NAMES, false);
		fShowPropertyCount= getBooleanSetting(SETTINGS_SHOW_PROPERTY_COUNT, false);
		fShowFlagHex= getBooleanSetting(SETTINGS_SHOW_FLAG_HEX, false);
		fSearchCaseSensitive= getBooleanSetting(SETTINGS_SEARCH_CASE_SENSITIVE, false);
		fSearchRegex= getBooleanSetting(SETTINGS_SEARCH_REGEX, false);
		fHideLeafAttributes= getBooleanSetting(SETTINGS_HIDE_LEAF_ATTRIBUTES, false);
		fFocusSubtree= getBooleanSetting(SETTINGS_FOCUS_SUBTREE, false);
		fIntelliSense= getBooleanSetting(SETTINGS_INTELLISENSE, true);
		fIntelliSenseAutoReveal= getBooleanSetting(SETTINGS_INTELLISENSE_AUTO_REVEAL, true);
		fShowGraph= getBooleanSetting(SETTINGS_SHOW_GRAPH, true);
		fGraphSyncSelection= getBooleanSetting(SETTINGS_GRAPH_SYNC_SELECTION, true);
		fGraphCompact= getBooleanSetting(SETTINGS_GRAPH_COMPACT, false);
		fGraphOverview= getBooleanSetting(SETTINGS_GRAPH_OVERVIEW, true);
		fGraphHeatmap= getBooleanSetting(SETTINGS_GRAPH_HEATMAP, true);
		fAutoExpandLevel= getIntSetting(SETTINGS_AUTO_EXPAND, 0);
		fVisualTheme= getIntSetting(SETTINGS_VISUAL_THEME, THEME_LIGHT);
		fElementFilter= getIntSetting(SETTINGS_ELEMENT_FILTER, FILTER_ALL);
		fInspectorDetail= getIntSetting(SETTINGS_INSPECTOR_DETAIL, INSPECTOR_TECHNICAL);
		fRangeMode= getIntSetting(SETTINGS_RANGE_MODE, ASTViewLabelProvider.RANGE_OFFSET);
		fSourceContextLines= getIntSetting(SETTINGS_SOURCE_CONTEXT, 2);
		fViewPreset= getIntSetting(SETTINGS_VIEW_PRESET, PRESET_CUSTOM);
		fSearchScope= getIntSetting(SETTINGS_SEARCH_SCOPE, SEARCH_BOTH);
		fMaxDepth= getIntSetting(SETTINGS_MAX_DEPTH, 0);
		fIntelliSenseMode= getIntSetting(SETTINGS_INTELLISENSE_MODE, SENSE_ALL);
		fGraphDepth= getIntSetting(SETTINGS_GRAPH_DEPTH, 3);
		fGraphOrientation= getIntSetting(SETTINGS_GRAPH_ORIENTATION, GRAPH_VERTICAL);
		fGraphZoom= getIntSetting(SETTINGS_GRAPH_ZOOM, 100);
		if (fVisualTheme < THEME_LIGHT || fVisualTheme > THEME_CONTRAST) {
			fVisualTheme= THEME_LIGHT;
		}
		if (fElementFilter < FILTER_ALL || fElementFilter > FILTER_PROBLEMS) {
			fElementFilter= FILTER_ALL;
		}
		if (fInspectorDetail < INSPECTOR_SUMMARY || fInspectorDetail > INSPECTOR_SOURCE) {
			fInspectorDetail= INSPECTOR_TECHNICAL;
		}
		if (fRangeMode < ASTViewLabelProvider.RANGE_NONE || fRangeMode > ASTViewLabelProvider.RANGE_BOTH) {
			fRangeMode= ASTViewLabelProvider.RANGE_OFFSET;
		}
		if (fViewPreset < PRESET_CUSTOM || fViewPreset > PRESET_SOURCE) {
			fViewPreset= PRESET_CUSTOM;
		}
		if (fSourceContextLines < 0) {
			fSourceContextLines= 0;
		}
		if (fSearchScope < SEARCH_LABEL || fSearchScope > SEARCH_BOTH) {
			fSearchScope= SEARCH_BOTH;
		}
		if (fMaxDepth < 0) {
			fMaxDepth= 0;
		}
		if (fIntelliSenseMode < SENSE_ALL || fIntelliSenseMode > SENSE_BINDINGS) {
			fIntelliSenseMode= SENSE_ALL;
		}
		if (fGraphDepth < 1) {
			fGraphDepth= 3;
		}
		if (fGraphOrientation < GRAPH_VERTICAL || fGraphOrientation > GRAPH_HORIZONTAL) {
			fGraphOrientation= GRAPH_VERTICAL;
		}
		if (fGraphZoom < 75 || fGraphZoom > 150) {
			fGraphZoom= 100;
		}
		fCurrentASTLevel= DEFAULT_AST_LEVEL;
		try {
			int level= fDialogSettings.getInt(SETTINGS_JLS);
			if (isKnownASTLevel(level)) {
				fCurrentASTLevel= level;
			}
		} catch (NumberFormatException e) {
			// ignore
		}
	}
	
	/*(non-Javadoc)
	 * @see org.eclipse.ui.IViewPart#init(org.eclipse.ui.IViewSite)
	 */
	public void init(IViewSite site) throws PartInitException {
		super.setSite(site);
		if (fSuperListener == null) {
			fSuperListener= new ListenerMix(this);
			
			ISelectionService service= site.getWorkbenchWindow().getSelectionService();
			service.addPostSelectionListener(fSuperListener);
			site.getPage().addPartListener(fSuperListener);
			FileBuffers.getTextFileBufferManager().addFileBufferListener(fSuperListener);
		}
	}
	
	public int getCurrentASTLevel() {
		return fCurrentASTLevel;
	}

	private boolean isKnownASTLevel(int level) {
		for (int i= 0; i < KNOWN_AST_LEVELS.length; i++) {
			if (KNOWN_AST_LEVELS[i] == level) {
				return true;
			}
		}
		return false;
	}

	public void setInput(ITextEditor editor) throws CoreException {
		if (fEditor != null) {
			uninstallModificationListener();
		}
		
		fEditor= null;
		fOpenable= null;
		fRoot= null;
		
		if (editor != null) {
			IOpenable openable= EditorUtility.getJavaInput(editor);
			if (!isSupportedOpenable(openable)) {
				throw new CoreException(getErrorStatus("Editor not showing a CU or classfile", null)); //$NON-NLS-1$
			}
			fOpenable= openable;
			int astLevel= getInitialASTLevel(openable);
			
			ISelection selection= editor.getSelectionProvider().getSelection();
			if (selection instanceof ITextSelection) {
				ITextSelection textSelection= (ITextSelection) selection;
				fRoot= internalSetInput(openable, textSelection.getOffset(), textSelection.getLength(), astLevel);
				fEditor= editor;
				setASTLevel(astLevel, false);
			} else {
				fRoot= internalSetInput(openable, 0, 0, astLevel);
				fEditor= editor;
				setASTLevel(astLevel, false);
			}
			installModificationListener();
		}

	}
	
	private boolean isSupportedOpenable(IOpenable openable) {
		return openable instanceof ICompilationUnit || openable instanceof IClassFile;
	}
	
	private int getInitialASTLevel(IOpenable openable) {
		if (!(openable instanceof IJavaElement)) {
			return DEFAULT_AST_LEVEL;
		}
		IJavaProject project= (IJavaProject) ((IJavaElement) openable).getAncestor(IJavaElement.JAVA_PROJECT);
		if (project != null) {
			String sourceVersion = project.getOption(JavaCore.COMPILER_SOURCE, true);
			int level= getASTLevelForSourceVersion(sourceVersion);
			if (level != -1) {
				return level;
			}
			sourceVersion= project.getOption(JavaCore.COMPILER_COMPLIANCE, true);
			level= getASTLevelForSourceVersion(sourceVersion);
			if (level != -1) {
				return level;
			}
		}
		return DEFAULT_AST_LEVEL;
	}

	private int getASTLevelForSourceVersion(String sourceVersion) {
		int version= parseJavaVersion(sourceVersion);
		if (version == -1) {
			return -1;
		}
		if (version >= 25) {
			return AST.JLS25;
		} else if (version == 24) {
			return AST.JLS24;
		} else if (version == 23) {
			return AST.JLS23;
		} else if (version == 22) {
			return AST.JLS22;
		} else if (version == 21) {
			return AST.JLS21;
		} else if (version == 20) {
			return AST.JLS20;
		} else if (version == 19) {
			return AST.JLS19;
		} else if (version == 18) {
			return AST.JLS18;
		} else if (version == 17) {
			return AST.JLS17;
		} else if (version == 16) {
			return AST.JLS16;
		} else if (version == 15) {
			return AST.JLS15;
		} else if (version == 14) {
			return AST.JLS14;
		} else if (version == 13) {
			return AST.JLS13;
		} else if (version == 12) {
			return AST.JLS12;
		} else if (version == 11) {
			return AST.JLS11;
		} else if (version == 10) {
			return AST.JLS10;
		} else if (version == 9) {
			return AST.JLS9;
		} else if (version >= 6) {
			return AST.JLS8;
		} else if (version == 5) {
			return AST.JLS3;
		}
		return AST.JLS2;
	}

	private int parseJavaVersion(String sourceVersion) {
		if (sourceVersion == null) {
			return -1;
		}
		sourceVersion= sourceVersion.trim();
		if (sourceVersion.startsWith("1.")) { //$NON-NLS-1$
			sourceVersion= sourceVersion.substring(2);
		}
		int end= 0;
		while (end < sourceVersion.length() && Character.isDigit(sourceVersion.charAt(end))) {
			end++;
		}
		if (end == 0) {
			return -1;
		}
		try {
			return Integer.parseInt(sourceVersion.substring(0, end));
		} catch (NumberFormatException e) {
			return -1;
		}
	}

	private boolean getBooleanSetting(String key, boolean defaultValue) {
		if (fDialogSettings.get(key) == null) {
			return defaultValue;
		}
		return fDialogSettings.getBoolean(key);
	}

	private int getIntSetting(String key, int defaultValue) {
		if (fDialogSettings.get(key) == null) {
			return defaultValue;
		}
		try {
			return fDialogSettings.getInt(key);
		} catch (NumberFormatException e) {
			return defaultValue;
		}
	}

	private CompilationUnit internalSetInput(IOpenable input, int offset, int length, int astLevel) throws CoreException {
		IBuffer buffer= input.getBuffer();
		if (buffer == null) {
			throw new CoreException(getErrorStatus("Input has no buffer", null)); //$NON-NLS-1$
		}
		int bufferLength= buffer.getLength();
		if (offset < 0) {
			offset= 0;
		} else if (offset > bufferLength) {
			offset= bufferLength;
		}
		if (length < 0) {
			length= 0;
		}
		if (offset + length > bufferLength) {
			length= bufferLength - offset;
		}
		
		try {
			CompilationUnit root= createAST(input, astLevel);
			
			if (fLabelProvider != null) {
				fLabelProvider.setRoot(root);
				fLabelProvider.setSelectedRange(offset, length);
			}
			resetView(root);
			ASTNode node= NodeFinder.perform(root, offset, length);
			if (node != null) {
				fViewer.getTree().setRedraw(false);
				fViewer.setSelection(new StructuredSelection(node), true);
				fViewer.getTree().setRedraw(true);
			}
			return root;
			
		} catch (RuntimeException e) {
			throw new CoreException(getErrorStatus("Could not create AST:\n" + e.getMessage(), e)); //$NON-NLS-1$
		}
	}

	private void resetView(CompilationUnit root) {
		if (root == null) {
			if (fLabelProvider != null) {
				fLabelProvider.setRoot(null);
			}
			setContentDescription("Open a Java editor and press the 'Show AST of active editor' toolbar button"); //$NON-NLS-1$
			updateHero("AST Explorer", "Open a Java editor to inspect syntax, bindings, ranges and diagnostics."); //$NON-NLS-1$ //$NON-NLS-2$
			updateMetrics("--", getModeLabel(), "Idle"); //$NON-NLS-1$ //$NON-NLS-2$
			updateInspector(null);
		}
		fViewer.setInput(root);
		fSubtreeRoot= fFocusSubtree ? root : null;
		fGraphRoot= null;
		if (fGraphSenseText != null && !fGraphSenseText.isDisposed()) {
			fGraphSenseText.setText(""); //$NON-NLS-1$
		}
		applyTrayVisibility();
		applyAutoExpand();
		fTrayRoots= new ArrayList();
		if (fTray != null)
			fTray.setInput(fTrayRoots);
		updateStatusBar(null);
		redrawGraph();
		setASTUptoDate(root != null);
		fClearAction.setEnabled(root != null);
	}
	
	private CompilationUnit createAST(IOpenable input, int astLevel) throws JavaModelException, CoreException {
		long startTime= 0;
		long endTime= 0;
		CompilationUnit root= null;
		boolean useReconciler= input instanceof ICompilationUnit && fDoUseReconciler;
		CoreException primaryException= null;
		
		try {
			if (useReconciler) {
				ICompilationUnit wc= ((ICompilationUnit) input).getWorkingCopy(
						new WorkingCopyOwner() {/*useless subclass*/},
						new IProblemRequestor() { //TODO: strange: don't get bindings when supplying null as problemRequestor
							public void acceptProblem(IProblem problem) {/*not interested*/}
							public void beginReporting() {/*not interested*/}
							public void endReporting() {/*not interested*/}
							public boolean isActive() {
								return true;
							}
						},
						null);
				try {
					//make inconsistent (otherwise, no AST is generated):
//					IBuffer buffer= wc.getBuffer();
//					buffer.append(new char[] {' '});
//					buffer.replace(buffer.getLength() - 1, 1, new char[0]);
					
					startTime= System.currentTimeMillis();
					root= wc.reconcile(astLevel, true, null, null);
					endTime= System.currentTimeMillis();
				} finally {
					wc.discardWorkingCopy();
				}
			} else {
				startTime= System.currentTimeMillis();
				root= createASTWithParser(input, astLevel, fCreateBindings);
				endTime= System.currentTimeMillis();
			}
		} catch (CoreException e) {
			primaryException= e;
			root= null;
		} catch (RuntimeException e) {
			primaryException= getCoreException("Could not create AST", e); //$NON-NLS-1$
			root= null;
		}
		if (root == null) {
			startTime= System.currentTimeMillis();
			root= createASTWithFallback(input, astLevel, primaryException);
			endTime= System.currentTimeMillis();
			useReconciler= false;
		}
		if (root == null) {
			throw new CoreException(getErrorStatus("Could not create AST", null)); //$NON-NLS-1$
		}
		updateContentDescription((IJavaElement) input, root, endTime - startTime, useReconciler);
		return root;
	}

	private CompilationUnit createASTWithFallback(IOpenable input, int preferredLevel, CoreException firstException) throws CoreException {
		CoreException lastException= firstException;
		int[] levels= getFallbackASTLevels(preferredLevel);
		for (int i= 0; i < levels.length; i++) {
			try {
				return createASTWithParser(input, levels[i], fCreateBindings);
			} catch (CoreException e) {
				lastException= e;
			} catch (RuntimeException e) {
				lastException= getCoreException("Could not create AST", e); //$NON-NLS-1$
			}
			if (fCreateBindings) {
				try {
					return createASTWithParser(input, levels[i], false);
				} catch (CoreException e) {
					lastException= e;
				} catch (RuntimeException e) {
					lastException= getCoreException("Could not create AST", e); //$NON-NLS-1$
				}
			}
		}
		if (lastException != null) {
			throw lastException;
		}
		return null;
	}

	private CoreException getCoreException(String message, Throwable th) {
		return new CoreException(getErrorStatus(message, th));
	}

	private CompilationUnit createASTWithParser(IOpenable input, int astLevel, boolean resolveBindings) throws CoreException {
		ASTParser parser= ASTParser.newParser(astLevel);
		parser.setKind(ASTParser.K_COMPILATION_UNIT);
		parser.setResolveBindings(resolveBindings);
		parser.setBindingsRecovery(resolveBindings);
		parser.setStatementsRecovery(true);
		if (input instanceof ICompilationUnit) {
			parser.setSource((ICompilationUnit) input);
		} else if (input instanceof IClassFile) {
			parser.setSource((IClassFile) input);
		} else {
			throw new CoreException(getErrorStatus("Input is not a CU or classfile", null)); //$NON-NLS-1$
		}
		return (CompilationUnit) parser.createAST(null);
	}

	private int[] getFallbackASTLevels(int preferredLevel) {
		if (!isKnownASTLevel(preferredLevel)) {
			return KNOWN_AST_LEVELS;
		}
		int[] levels= new int[KNOWN_AST_LEVELS.length];
		levels[0]= preferredLevel;
		int count= 1;
		for (int i= 0; i < KNOWN_AST_LEVELS.length; i++) {
			if (KNOWN_AST_LEVELS[i] != preferredLevel) {
				levels[count++]= KNOWN_AST_LEVELS[i];
			}
		}
		return levels;
	}

	private void updateContentDescription(IJavaElement element, CompilationUnit root, long time, boolean useReconciler) {
		String version= getASTLevelLabel(root.getAST().apiLevel());
		if (useReconciler)
			version+= ", from reconciler"; //$NON-NLS-1$
		TreeInfoCollector collector= new TreeInfoCollector(root);

		String msg= "{0} ({1}).  Creation time: {2,number} ms.  Size: {3,number} nodes, {4,number} bytes (AST nodes only)."; //$NON-NLS-1$
		Object[] args= { element.getElementName(), version, new Long(time),  new Integer(collector.getNumberOfNodes()), new Integer(collector.getSize())};
		setContentDescription(MessageFormat.format(msg, args));
		updateHero(element.getElementName(), version + "  |  " + time + " ms"); //$NON-NLS-1$ //$NON-NLS-2$
		updateMetrics(Integer.toString(collector.getNumberOfNodes()), getModeLabel(), getProblemLabel(root));

	}

	private void updateHero(String title, String subtitle) {
		if (fHeroTitle != null && !fHeroTitle.isDisposed()) {
			fHeroTitle.setText(title);
		}
		if (fHeroSubtitle != null && !fHeroSubtitle.isDisposed()) {
			fHeroSubtitle.setText(subtitle);
		}
	}

	private void updateMetrics(String nodes, String mode, String health) {
		if (fMetricNodes != null && !fMetricNodes.isDisposed()) {
			fMetricNodes.setText(nodes);
		}
		if (fMetricMode != null && !fMetricMode.isDisposed()) {
			fMetricMode.setText(mode);
		}
		if (fMetricHealth != null && !fMetricHealth.isDisposed()) {
			fMetricHealth.setText(health);
			fMetricHealth.setForeground(health.indexOf("Problems") != -1 ? fWarning : fText); //$NON-NLS-1$
		}
	}

	private String getProblemLabel(CompilationUnit root) {
		IProblem[] problems= root.getProblems();
		if (problems == null || problems.length == 0) {
			return "Clean"; //$NON-NLS-1$
		}
		return problems.length + " Problems"; //$NON-NLS-1$
	}

	private String getASTLevelLabel(int level) {
		if (level == AST.JLS2)
			return "AST Level 2"; //$NON-NLS-1$
		if (level == AST.JLS3)
			return "AST Level 3"; //$NON-NLS-1$
		if (level == AST.JLS4)
			return "AST Level 4"; //$NON-NLS-1$
		if (level == AST.JLS8)
			return "AST Level 8"; //$NON-NLS-1$
		if (level == AST.JLS9)
			return "AST Level 9"; //$NON-NLS-1$
		if (level == AST.JLS10)
			return "AST Level 10"; //$NON-NLS-1$
		if (level == AST.JLS11)
			return "AST Level 11"; //$NON-NLS-1$
		if (level == AST.JLS12)
			return "AST Level 12"; //$NON-NLS-1$
		if (level == AST.JLS13)
			return "AST Level 13"; //$NON-NLS-1$
		if (level == AST.JLS14)
			return "AST Level 14"; //$NON-NLS-1$
		if (level == AST.JLS15)
			return "AST Level 15"; //$NON-NLS-1$
		if (level == AST.JLS16)
			return "AST Level 16"; //$NON-NLS-1$
		if (level == AST.JLS17)
			return "AST Level 17"; //$NON-NLS-1$
		if (level == AST.JLS18)
			return "AST Level 18"; //$NON-NLS-1$
		if (level == AST.JLS19)
			return "AST Level 19"; //$NON-NLS-1$
		if (level == AST.JLS20)
			return "AST Level 20"; //$NON-NLS-1$
		if (level == AST.JLS21)
			return "AST Level 21"; //$NON-NLS-1$
		if (level == AST.JLS22)
			return "AST Level 22"; //$NON-NLS-1$
		if (level == AST.JLS23)
			return "AST Level 23"; //$NON-NLS-1$
		if (level == AST.JLS24)
			return "AST Level 24"; //$NON-NLS-1$
		if (level == AST.JLS25)
			return "AST Level 25"; //$NON-NLS-1$
		return "AST Level " + level; //$NON-NLS-1$
	}

	/* (non-Javadoc)
	 * @see org.eclipse.ui.IWorkbenchPart#dispose()
	 */
	public void dispose() {
		if (fSuperListener != null) {
			if (fEditor != null) {
				uninstallModificationListener();
			}
			ISelectionService service= getSite().getWorkbenchWindow().getSelectionService();
			service.removePostSelectionListener(fSuperListener);
			getSite().getPage().removePartListener(fSuperListener);
			FileBuffers.getTextFileBufferManager().removeFileBufferListener(fSuperListener);
			fSuperListener.dispose(); // removes reference to view
			fSuperListener= null;
		}
		if (fTrayUpdater != null) {
			fViewer.removePostSelectionChangedListener(fTrayUpdater);
			fTray.removePostSelectionChangedListener(fTrayUpdater);
			fTrayUpdater= null;
		}
		disposeVisualResources();
		super.dispose();
	}

	private void disposeVisualResources() {
		dispose(fHeroFont);
		dispose(fMonoFont);
		disposeVisualColors();
	}

	private void disposeVisualColors() {
		dispose(fShell);
		dispose(fPanel);
		dispose(fPanelDeep);
		dispose(fAccent);
		dispose(fAccentSoft);
		dispose(fText);
		dispose(fMutedText);
		dispose(fBorder);
		dispose(fWarning);
		dispose(fDiagramCanvas);
		dispose(fDiagramUnitFill);
		dispose(fDiagramModuleFill);
		dispose(fDiagramGroupFill);
		dispose(fDiagramLeafFill);
		dispose(fDiagramProblemFill);
	}

	private void dispose(Object resource) {
		if (resource instanceof Font && !((Font) resource).isDisposed()) {
			((Font) resource).dispose();
		} else if (resource instanceof Color && !((Color) resource).isDisposed()) {
			((Color) resource).dispose();
		}
	}
	
	private IStatus getErrorStatus(String message, Throwable th) {
		return new Status(IStatus.ERROR, ASTViewPlugin.getPluginId(), IStatus.ERROR, message, th);
	}
	
	/*
	 * (non-Javadoc)
	 * @see org.eclipse.ui.IWorkbenchPart#createPartControl(org.eclipse.swt.widgets.Composite)
	 */
	public void createPartControl(Composite parent) {
		fParent= parent;
		createVisualResources(parent);
		parent.setBackground(fShell);
		parent.setLayout(new GridLayout(1, false));
		
		fHeader= createHeader(parent);
		fHeader.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false));
		
		fWorkspace= new SashForm(parent, (fVerticalLayout ? SWT.VERTICAL : SWT.HORIZONTAL) | SWT.SMOOTH);
		fWorkspace.setBackground(fShell);
		fWorkspace.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
		
		fAstPane= new Composite(fWorkspace, SWT.NONE);
		fAstPane.setBackground(fShell);
		GridLayout astPaneLayout= new GridLayout(1, false);
		astPaneLayout.marginWidth= 0;
		astPaneLayout.marginHeight= 0;
		astPaneLayout.verticalSpacing= 8;
		fAstPane.setLayout(astPaneLayout);
		
		Composite toolStrip= createToolStrip(fAstPane);
		toolStrip.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false));
		
		fSash= new SashForm(fAstPane, SWT.HORIZONTAL | SWT.SMOOTH);
		fSash.setBackground(fShell);
		fSash.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
		fViewer = new TreeViewer(fSash, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);
		fDrillDownAdapter = new DrillDownAdapter(fViewer);
		fViewer.setContentProvider(new ASTViewContentProvider());
		fLabelProvider= new ASTViewLabelProvider();
		applyLabelOptions();
		fViewer.setLabelProvider(fLabelProvider);
		styleTree(fViewer.getTree());
		fViewer.addSelectionChangedListener(fSuperListener);
		fViewer.addDoubleClickListener(fSuperListener);
		fViewer.addFilter(new ViewerFilter() {
			public boolean select(Viewer viewer, Object parentElement, Object element) {
				if (!fCreateBindings && element instanceof Binding)
					return false;
				return true;
			}
		});
		fSearchFilter= new ViewerFilter() {
			public boolean select(Viewer viewer, Object parentElement, Object element) {
				if (fSearchPattern.length() == 0) {
					return true;
				}
				if (matchesSearch(element)) {
					return true;
				}
				return hasFilteredDescendant(element);
			}
		};
		fViewer.addFilter(fSearchFilter);
		fUnresolvedBindingsFilter= new ViewerFilter() {
			public boolean select(Viewer viewer, Object parentElement, Object element) {
				if (!fOnlyUnresolvedBindings) {
					return true;
				}
				if (element instanceof Binding) {
					return ((Binding) element).getBinding() == null || hasUnresolvedBindingDescendant(element);
				}
				return hasUnresolvedBindingDescendant(element);
			}
		};
		fViewer.addFilter(fUnresolvedBindingsFilter);
		fElementTypeFilter= new ViewerFilter() {
			public boolean select(Viewer viewer, Object parentElement, Object element) {
				if (fElementFilter == FILTER_ALL) {
					return true;
				}
				return matchesElementFilter(element) || hasElementFilterDescendant(element);
			}
		};
		fViewer.addFilter(fElementTypeFilter);
		fMalformedFilter= new ViewerFilter() {
			public boolean select(Viewer viewer, Object parentElement, Object element) {
				if (!fOnlyMalformed) {
					return true;
				}
				return isMalformedElement(element) || hasMalformedDescendant(element);
			}
		};
		fViewer.addFilter(fMalformedFilter);
		fDepthFilter= new ViewerFilter() {
			public boolean select(Viewer viewer, Object parentElement, Object element) {
				return fMaxDepth == 0 || getElementDepth(element) <= fMaxDepth;
			}
		};
		fViewer.addFilter(fDepthFilter);
		fLeafAttributeFilter= new ViewerFilter() {
			public boolean select(Viewer viewer, Object parentElement, Object element) {
				if (!fHideLeafAttributes || !(element instanceof ASTAttribute) || element instanceof Binding || element instanceof ProblemNode) {
					return true;
				}
				return ((ASTAttribute) element).getChildren().length > 0;
			}
		};
		fViewer.addFilter(fLeafAttributeFilter);
		fSubtreeFilter= new ViewerFilter() {
			public boolean select(Viewer viewer, Object parentElement, Object element) {
				return !fFocusSubtree || fSubtreeRoot == null || isInFocusedSubtreePath(element);
			}
		};
		fViewer.addFilter(fSubtreeFilter);
		
		fGraphStack= new SashForm(fSash, SWT.VERTICAL | SWT.SMOOTH);
		fGraphStack.setBackground(fShell);
		createGraphView(fGraphStack);
		
		fTrayForm= new ViewForm(fGraphStack, SWT.NONE);
		fTrayForm.setBackground(fPanel);
		Label label= new Label(fTrayForm, SWT.NONE);
		label.setText(" Comparison Tray (* = selection in the upper tree):"); //$NON-NLS-1$
		label.setBackground(fPanel);
		label.setForeground(fMutedText);
		fTrayForm.setTopLeft(label);
		
		fTray= new TreeViewer(fTrayForm, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);
		styleTree(fTray.getTree());
		fTrayForm.setContent(fTray.getTree());
		
		fTrayRoots= new ArrayList();
		fTray.setContentProvider(new TrayContentProvider());
		final TrayLabelProvider trayLabelProvider= new TrayLabelProvider();
		fTray.setLabelProvider(trayLabelProvider);
		fTray.setAutoExpandLevel(AbstractTreeViewer.ALL_LEVELS);
		fTrayUpdater= new ISelectionChangedListener() {
			public void selectionChanged(SelectionChangedEvent event) {
				IStructuredSelection viewerSelection= (IStructuredSelection) fViewer.getSelection();
				if (viewerSelection.size() == 1 && viewerSelection.getFirstElement() instanceof Binding) {
					trayLabelProvider.setViewerElement((Binding) viewerSelection.getFirstElement());
				} else {
					trayLabelProvider.setViewerElement(null);
				}
			}
		};
		fTray.addPostSelectionChangedListener(fTrayUpdater);
		fViewer.addPostSelectionChangedListener(fTrayUpdater);
		fTray.addDoubleClickListener(new IDoubleClickListener() {
			public void doubleClick(DoubleClickEvent event) {
				performTrayDoubleClick();
			}
		});
		fTray.addSelectionChangedListener(new ISelectionChangedListener() {
			public void selectionChanged(SelectionChangedEvent event) {
				IStructuredSelection selection= (IStructuredSelection) event.getSelection();
				boolean deleteEnabled= false;
				if (selection.size() == 1 && selection.getFirstElement() instanceof Binding)
					deleteEnabled= fTray.getTree().isFocusControl();
				fDeleteAction.setEnabled(deleteEnabled);
			}
		});
		fTray.getTree().addFocusListener(new FocusAdapter() {
			public void focusGained(FocusEvent e) {
				IStructuredSelection selection= (IStructuredSelection) fTray.getSelection();
				boolean deleteEnabled= false;
				if (selection.size() == 1 && selection.getFirstElement() instanceof Binding)
					deleteEnabled= true;
				fDeleteAction.setEnabled(deleteEnabled);
			}
			public void focusLost(FocusEvent e) {
				fDeleteAction.setEnabled(false);
			}
		});
		//TODO: hook tray context menu with copy & delete actions, ...
		
		makeActions();
		hookContextMenu();
		contributeToActionBars();
		getSite().setSelectionProvider(new ASTViewSelectionProvider());
		
		createInspector(fWorkspace);
		fWorkspace.setWeights(new int[] { 74, 26 });
		applyLayoutOptions();
		
		fStatusBar= createStatusBar(fAstPane);
		fStatusBar.setLayoutData(new GridData(SWT.FILL, SWT.BOTTOM, true, false));
		applyDisplayChrome();
		
		try {
			IEditorPart part= EditorUtility.getActiveEditor();
			if (part instanceof ITextEditor) {
				setInput((ITextEditor) part);
			}
		} catch (CoreException e) {
			// ignore
		}
		if (fOpenable == null) {
			resetView(null);
		} else {
			setASTUptoDate(fOpenable != null);
		}
	}

	private void createVisualResources(Composite parent) {
		createThemeColors(parent);
		
		FontData[] defaultData= parent.getFont().getFontData();
		FontData heroData= new FontData(defaultData[0].getName(), defaultData[0].getHeight() + 5, SWT.BOLD);
		fHeroFont= new Font(parent.getDisplay(), heroData);
		FontData monoData= new FontData("Monospace", defaultData[0].getHeight(), SWT.NORMAL); //$NON-NLS-1$
		fMonoFont= new Font(parent.getDisplay(), monoData);
	}

	private void createThemeColors(Composite parent) {
		if (fVisualTheme == THEME_DARK) {
			fShell= new Color(parent.getDisplay(), 30, 35, 42);
			fPanel= new Color(parent.getDisplay(), 42, 48, 57);
			fPanelDeep= new Color(parent.getDisplay(), 14, 20, 29);
			fAccent= new Color(parent.getDisplay(), 86, 156, 214);
			fAccentSoft= new Color(parent.getDisplay(), 35, 61, 82);
			fText= new Color(parent.getDisplay(), 232, 237, 243);
			fMutedText= new Color(parent.getDisplay(), 167, 178, 190);
			fBorder= new Color(parent.getDisplay(), 72, 82, 96);
			fWarning= new Color(parent.getDisplay(), 244, 126, 96);
			fDiagramCanvas= new Color(parent.getDisplay(), 28, 32, 38);
			fDiagramUnitFill= new Color(parent.getDisplay(), 58, 49, 75);
			fDiagramModuleFill= new Color(parent.getDisplay(), 71, 58, 39);
			fDiagramGroupFill= new Color(parent.getDisplay(), 40, 63, 56);
			fDiagramLeafFill= new Color(parent.getDisplay(), 42, 58, 72);
			fDiagramProblemFill= new Color(parent.getDisplay(), 82, 45, 45);
		} else if (fVisualTheme == THEME_CONTRAST) {
			fShell= new Color(parent.getDisplay(), 0, 0, 0);
			fPanel= new Color(parent.getDisplay(), 16, 16, 16);
			fPanelDeep= new Color(parent.getDisplay(), 0, 0, 0);
			fAccent= new Color(parent.getDisplay(), 255, 214, 10);
			fAccentSoft= new Color(parent.getDisplay(), 50, 45, 0);
			fText= new Color(parent.getDisplay(), 255, 255, 255);
			fMutedText= new Color(parent.getDisplay(), 210, 210, 210);
			fBorder= new Color(parent.getDisplay(), 120, 120, 120);
			fWarning= new Color(parent.getDisplay(), 255, 94, 94);
			fDiagramCanvas= new Color(parent.getDisplay(), 0, 0, 0);
			fDiagramUnitFill= new Color(parent.getDisplay(), 34, 27, 49);
			fDiagramModuleFill= new Color(parent.getDisplay(), 47, 39, 15);
			fDiagramGroupFill= new Color(parent.getDisplay(), 18, 45, 34);
			fDiagramLeafFill= new Color(parent.getDisplay(), 14, 36, 52);
			fDiagramProblemFill= new Color(parent.getDisplay(), 54, 18, 18);
		} else {
			fShell= new Color(parent.getDisplay(), 244, 247, 251);
			fPanel= new Color(parent.getDisplay(), 255, 255, 255);
			fPanelDeep= new Color(parent.getDisplay(), 21, 31, 45);
			fAccent= new Color(parent.getDisplay(), 0, 122, 204);
			fAccentSoft= new Color(parent.getDisplay(), 223, 239, 255);
			fText= new Color(parent.getDisplay(), 28, 35, 45);
			fMutedText= new Color(parent.getDisplay(), 93, 105, 120);
			fBorder= new Color(parent.getDisplay(), 219, 225, 233);
			fWarning= new Color(parent.getDisplay(), 204, 72, 52);
			fDiagramCanvas= new Color(parent.getDisplay(), 250, 251, 253);
			fDiagramUnitFill= new Color(parent.getDisplay(), 245, 239, 255);
			fDiagramModuleFill= new Color(parent.getDisplay(), 255, 247, 222);
			fDiagramGroupFill= new Color(parent.getDisplay(), 239, 249, 232);
			fDiagramLeafFill= new Color(parent.getDisplay(), 235, 246, 255);
			fDiagramProblemFill= new Color(parent.getDisplay(), 255, 237, 237);
		}
	}

	private Composite createHeader(Composite parent) {
		Composite header= new Composite(parent, SWT.NONE);
		header.setBackground(fPanelDeep);
		GridLayout layout= new GridLayout(2, false);
		layout.marginWidth= 18;
		layout.marginHeight= 14;
		layout.horizontalSpacing= 18;
		header.setLayout(layout);
		
		Composite titleBox= new Composite(header, SWT.NONE);
		titleBox.setBackground(fPanelDeep);
		GridLayout titleLayout= new GridLayout(1, false);
		titleLayout.marginWidth= 0;
		titleLayout.marginHeight= 0;
		titleLayout.verticalSpacing= 3;
		titleBox.setLayout(titleLayout);
		titleBox.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
		
		fHeroTitle= new Label(titleBox, SWT.NONE);
		fHeroTitle.setText("AST Explorer"); //$NON-NLS-1$
		fHeroTitle.setFont(fHeroFont);
		fHeroTitle.setForeground(fPanel);
		fHeroTitle.setBackground(fPanelDeep);
		
		fHeroSubtitle= new Label(titleBox, SWT.NONE);
		fHeroSubtitle.setText("Open a Java editor to inspect syntax, bindings, ranges and diagnostics."); //$NON-NLS-1$
		fHeroSubtitle.setForeground(fBorder);
		fHeroSubtitle.setBackground(fPanelDeep);
		
		Composite metrics= new Composite(header, SWT.NONE);
		metrics.setBackground(fPanelDeep);
		GridLayout metricLayout= new GridLayout(3, true);
		metricLayout.marginWidth= 0;
		metricLayout.marginHeight= 0;
		metricLayout.horizontalSpacing= 8;
		metrics.setLayout(metricLayout);
		metrics.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false));
		
		fMetricNodes= createMetric(metrics, "Nodes", "--"); //$NON-NLS-1$ //$NON-NLS-2$
		fMetricMode= createMetric(metrics, "Mode", getModeLabel()); //$NON-NLS-1$
		fMetricHealth= createMetric(metrics, "Status", "Idle"); //$NON-NLS-1$ //$NON-NLS-2$
		return header;
	}

	private Label createMetric(Composite parent, String title, String value) {
		Composite chip= new Composite(parent, SWT.BORDER);
		chip.setBackground(fPanel);
		GridLayout layout= new GridLayout(1, false);
		layout.marginWidth= 10;
		layout.marginHeight= 7;
		layout.verticalSpacing= 1;
		chip.setLayout(layout);
		chip.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false));
		
		Label titleLabel= new Label(chip, SWT.NONE);
		titleLabel.setText(title);
		titleLabel.setBackground(fPanel);
		titleLabel.setForeground(fMutedText);
		
		Label valueLabel= new Label(chip, SWT.NONE);
		valueLabel.setText(value);
		valueLabel.setBackground(fPanel);
		valueLabel.setForeground(fText);
		valueLabel.setFont(fMonoFont);
		return valueLabel;
	}

	private Composite createToolStrip(Composite parent) {
		Composite toolStrip= new Composite(parent, SWT.NONE);
		toolStrip.setBackground(fShell);
		GridLayout layout= new GridLayout(43, false);
		layout.marginWidth= 0;
		layout.marginHeight= 0;
		layout.horizontalSpacing= 8;
		toolStrip.setLayout(layout);
		
		Label searchLabel= new Label(toolStrip, SWT.NONE);
		searchLabel.setText("Filter"); //$NON-NLS-1$
		searchLabel.setBackground(fShell);
		searchLabel.setForeground(fMutedText);
		
		fSearchText= new Text(toolStrip, SWT.SEARCH | SWT.ICON_SEARCH | SWT.CANCEL | SWT.BORDER);
		fSearchText.setMessage("Node, binding, property..."); //$NON-NLS-1$
		fSearchText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
		fSearchText.addModifyListener(new ModifyListener() {
			public void modifyText(ModifyEvent e) {
				fSearchPattern= fSearchText.getText().trim();
				if (fViewer != null) {
					fViewer.refresh();
					applyAutoExpand();
					if (fIntelliSenseAutoReveal) {
						revealFirstSearchMatch();
					}
					updateStatusBar(fViewer.getSelection());
				}
			}
		});
		installSearchIntelliSense();
		
		fSearchScopeCombo= new Combo(toolStrip, SWT.READ_ONLY | SWT.BORDER);
		fSearchScopeCombo.add("Label"); //$NON-NLS-1$
		fSearchScopeCombo.add("Class"); //$NON-NLS-1$
		fSearchScopeCombo.add("Both"); //$NON-NLS-1$
		fSearchScopeCombo.select(fSearchScope);
		fSearchScopeCombo.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				setSearchScope(fSearchScopeCombo.getSelectionIndex());
			}
		});
		
		fCaseButton= createToolToggle(toolStrip, "Case", fSearchCaseSensitive); //$NON-NLS-1$
		fCaseButton.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				setSearchCaseSensitive(fCaseButton.getSelection());
			}
		});
		
		fRegexButton= createToolToggle(toolStrip, "Regex", fSearchRegex); //$NON-NLS-1$
		fRegexButton.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				setSearchRegex(fRegexButton.getSelection());
			}
		});
		
		fIntelliSenseModeCombo= new Combo(toolStrip, SWT.READ_ONLY | SWT.BORDER);
		fIntelliSenseModeCombo.add("Sense: All"); //$NON-NLS-1$
		fIntelliSenseModeCombo.add("Sense: Nodes"); //$NON-NLS-1$
		fIntelliSenseModeCombo.add("Sense: Props"); //$NON-NLS-1$
		fIntelliSenseModeCombo.add("Sense: Bindings"); //$NON-NLS-1$
		fIntelliSenseModeCombo.select(fIntelliSenseMode);
		fIntelliSenseModeCombo.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				setIntelliSenseMode(fIntelliSenseModeCombo.getSelectionIndex());
			}
		});
		
		fSenseButton= createToolToggle(toolStrip, "IntelliSense", fIntelliSense); //$NON-NLS-1$
		fSenseButton.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				setIntelliSense(fSenseButton.getSelection());
			}
		});
		
		fAutoRevealButton= createToolToggle(toolStrip, "Auto reveal", fIntelliSenseAutoReveal); //$NON-NLS-1$
		fAutoRevealButton.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				setIntelliSenseAutoReveal(fAutoRevealButton.getSelection());
			}
		});
		
		fGraphButton= createToolToggle(toolStrip, "Graph", fShowGraph); //$NON-NLS-1$
		fGraphButton.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				setShowGraph(fGraphButton.getSelection());
			}
		});
		
		Label graphFocusLabel= new Label(toolStrip, SWT.NONE);
		graphFocusLabel.setText("Graph focus"); //$NON-NLS-1$
		graphFocusLabel.setBackground(fShell);
		graphFocusLabel.setForeground(fMutedText);
		
		fGraphSenseText= new Text(toolStrip, SWT.SEARCH | SWT.ICON_SEARCH | SWT.CANCEL | SWT.BORDER);
		fGraphSenseText.setMessage("Type AST node..."); //$NON-NLS-1$
		fGraphSenseText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
		fGraphSenseText.addModifyListener(new ModifyListener() {
			public void modifyText(ModifyEvent e) {
				if (fGraphSenseText.getText().trim().length() == 0) {
					fGraphRoot= null;
					redrawGraph();
				}
			}
		});
		installGraphIntelliSense();
		
		fGraphDepthCombo= new Combo(toolStrip, SWT.READ_ONLY | SWT.BORDER);
		fGraphDepthCombo.add("Graph 2"); //$NON-NLS-1$
		fGraphDepthCombo.add("Graph 3"); //$NON-NLS-1$
		fGraphDepthCombo.add("Graph 4"); //$NON-NLS-1$
		fGraphDepthCombo.add("Graph 6"); //$NON-NLS-1$
		fGraphDepthCombo.select(getGraphDepthComboIndex());
		fGraphDepthCombo.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				setGraphDepth(getGraphDepthFromCombo());
			}
		});
		
		fGraphOrientationCombo= new Combo(toolStrip, SWT.READ_ONLY | SWT.BORDER);
		fGraphOrientationCombo.add("Top-down"); //$NON-NLS-1$
		fGraphOrientationCombo.add("Left-right"); //$NON-NLS-1$
		fGraphOrientationCombo.select(fGraphOrientation);
		fGraphOrientationCombo.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				setGraphOrientation(fGraphOrientationCombo.getSelectionIndex());
			}
		});
		
		fGraphZoomCombo= new Combo(toolStrip, SWT.READ_ONLY | SWT.BORDER);
		fGraphZoomCombo.add("75%"); //$NON-NLS-1$
		fGraphZoomCombo.add("100%"); //$NON-NLS-1$
		fGraphZoomCombo.add("125%"); //$NON-NLS-1$
		fGraphZoomCombo.add("150%"); //$NON-NLS-1$
		fGraphZoomCombo.select(getGraphZoomComboIndex());
		fGraphZoomCombo.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				setGraphZoom(getGraphZoomFromCombo());
			}
		});
		
		fGraphSyncButton= createToolToggle(toolStrip, "Graph sync", fGraphSyncSelection); //$NON-NLS-1$
		fGraphSyncButton.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				setGraphSyncSelection(fGraphSyncButton.getSelection());
			}
		});
		
		fGraphCompactButton= createToolToggle(toolStrip, "Graph compact", fGraphCompact); //$NON-NLS-1$
		fGraphCompactButton.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				setGraphCompact(fGraphCompactButton.getSelection());
			}
		});
		
		fGraphOverviewButton= createToolToggle(toolStrip, "Overview", fGraphOverview); //$NON-NLS-1$
		fGraphOverviewButton.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				setGraphOverview(fGraphOverviewButton.getSelection());
			}
		});
		
		fGraphHeatmapButton= createToolToggle(toolStrip, "Heatmap", fGraphHeatmap); //$NON-NLS-1$
		fGraphHeatmapButton.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				setGraphHeatmap(fGraphHeatmapButton.getSelection());
			}
		});
		
		Label depthLabel= new Label(toolStrip, SWT.NONE);
		depthLabel.setText("Depth"); //$NON-NLS-1$
		depthLabel.setBackground(fShell);
		depthLabel.setForeground(fMutedText);
		
		fMaxDepthCombo= new Combo(toolStrip, SWT.READ_ONLY | SWT.BORDER);
		fMaxDepthCombo.add("Any"); //$NON-NLS-1$
		fMaxDepthCombo.add("3"); //$NON-NLS-1$
		fMaxDepthCombo.add("5"); //$NON-NLS-1$
		fMaxDepthCombo.add("8"); //$NON-NLS-1$
		fMaxDepthCombo.add("12"); //$NON-NLS-1$
		fMaxDepthCombo.select(getMaxDepthComboIndex());
		fMaxDepthCombo.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				setMaxDepth(getMaxDepthFromCombo());
			}
		});
		
		fExpandCombo= new Combo(toolStrip, SWT.READ_ONLY | SWT.BORDER);
		fExpandCombo.add("Manual"); //$NON-NLS-1$
		fExpandCombo.add("1"); //$NON-NLS-1$
		fExpandCombo.add("2"); //$NON-NLS-1$
		fExpandCombo.add("3"); //$NON-NLS-1$
		fExpandCombo.add("All"); //$NON-NLS-1$
		fExpandCombo.select(getAutoExpandComboIndex());
		fExpandCombo.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				setAutoExpandLevel(getAutoExpandLevelFromCombo());
			}
		});
		
		Label viewLabel= new Label(toolStrip, SWT.NONE);
		viewLabel.setText("Show"); //$NON-NLS-1$
		viewLabel.setBackground(fShell);
		viewLabel.setForeground(fMutedText);
		
		fElementFilterCombo= new Combo(toolStrip, SWT.READ_ONLY | SWT.BORDER);
		fElementFilterCombo.add("All"); //$NON-NLS-1$
		fElementFilterCombo.add("Nodes"); //$NON-NLS-1$
		fElementFilterCombo.add("Properties"); //$NON-NLS-1$
		fElementFilterCombo.add("Bindings"); //$NON-NLS-1$
		fElementFilterCombo.add("Problems"); //$NON-NLS-1$
		fElementFilterCombo.select(fElementFilter);
		fElementFilterCombo.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				setElementFilter(fElementFilterCombo.getSelectionIndex());
			}
		});
		
		Label themeLabel= new Label(toolStrip, SWT.NONE);
		themeLabel.setText("Theme"); //$NON-NLS-1$
		themeLabel.setBackground(fShell);
		themeLabel.setForeground(fMutedText);
		
		fThemeCombo= new Combo(toolStrip, SWT.READ_ONLY | SWT.BORDER);
		fThemeCombo.add("Light"); //$NON-NLS-1$
		fThemeCombo.add("Dark"); //$NON-NLS-1$
		fThemeCombo.add("Contrast"); //$NON-NLS-1$
		fThemeCombo.select(fVisualTheme);
		fThemeCombo.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				setVisualTheme(fThemeCombo.getSelectionIndex());
			}
		});
		
		Label detailLabel= new Label(toolStrip, SWT.NONE);
		detailLabel.setText("Inspect"); //$NON-NLS-1$
		detailLabel.setBackground(fShell);
		detailLabel.setForeground(fMutedText);
		
		fInspectorDetailCombo= new Combo(toolStrip, SWT.READ_ONLY | SWT.BORDER);
		fInspectorDetailCombo.add("Summary"); //$NON-NLS-1$
		fInspectorDetailCombo.add("Technical"); //$NON-NLS-1$
		fInspectorDetailCombo.add("Source"); //$NON-NLS-1$
		fInspectorDetailCombo.select(fInspectorDetail);
		fInspectorDetailCombo.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				setInspectorDetail(fInspectorDetailCombo.getSelectionIndex());
			}
		});
		
		Label presetLabel= new Label(toolStrip, SWT.NONE);
		presetLabel.setText("Preset"); //$NON-NLS-1$
		presetLabel.setBackground(fShell);
		presetLabel.setForeground(fMutedText);
		
		fPresetCombo= new Combo(toolStrip, SWT.READ_ONLY | SWT.BORDER);
		fPresetCombo.add("Custom"); //$NON-NLS-1$
		fPresetCombo.add("Review"); //$NON-NLS-1$
		fPresetCombo.add("Diagnostics"); //$NON-NLS-1$
		fPresetCombo.add("Bindings"); //$NON-NLS-1$
		fPresetCombo.add("Source"); //$NON-NLS-1$
		fPresetCombo.select(fViewPreset);
		fPresetCombo.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				applyViewPreset(fPresetCombo.getSelectionIndex());
			}
		});
		
		Label rangeLabel= new Label(toolStrip, SWT.NONE);
		rangeLabel.setText("Range"); //$NON-NLS-1$
		rangeLabel.setBackground(fShell);
		rangeLabel.setForeground(fMutedText);
		
		fRangeModeCombo= new Combo(toolStrip, SWT.READ_ONLY | SWT.BORDER);
		fRangeModeCombo.add("Off"); //$NON-NLS-1$
		fRangeModeCombo.add("Offset"); //$NON-NLS-1$
		fRangeModeCombo.add("Line"); //$NON-NLS-1$
		fRangeModeCombo.add("Both"); //$NON-NLS-1$
		fRangeModeCombo.select(fRangeMode);
		fRangeModeCombo.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				setRangeMode(fRangeModeCombo.getSelectionIndex());
			}
		});
		
		Label contextLabel= new Label(toolStrip, SWT.NONE);
		contextLabel.setText("Context"); //$NON-NLS-1$
		contextLabel.setBackground(fShell);
		contextLabel.setForeground(fMutedText);
		
		fSourceContextCombo= new Combo(toolStrip, SWT.READ_ONLY | SWT.BORDER);
		fSourceContextCombo.add("0"); //$NON-NLS-1$
		fSourceContextCombo.add("2"); //$NON-NLS-1$
		fSourceContextCombo.add("5"); //$NON-NLS-1$
		fSourceContextCombo.add("10"); //$NON-NLS-1$
		fSourceContextCombo.select(getSourceContextComboIndex());
		fSourceContextCombo.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				setSourceContextLines(getSourceContextFromCombo());
			}
		});
		
		fInspectorButton= createToolToggle(toolStrip, "Inspector", fShowInspector); //$NON-NLS-1$
		fInspectorButton.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				setShowInspector(fInspectorButton.getSelection());
			}
		});
		
		fTrayButton= createToolToggle(toolStrip, "Tray", fShowTray); //$NON-NLS-1$
		fTrayButton.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				setShowTray(fTrayButton.getSelection());
			}
		});
		
		fLayoutButton= createToolToggle(toolStrip, "Vertical", fVerticalLayout); //$NON-NLS-1$
		fLayoutButton.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				setVerticalLayout(fLayoutButton.getSelection());
			}
		});
		
		fCompactButton= createToolToggle(toolStrip, "Compact", fCompactLabels); //$NON-NLS-1$
		fCompactButton.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				setCompactLabels(fCompactButton.getSelection());
			}
		});
		
		fDenseButton= createToolToggle(toolStrip, "Dense", fDenseMode); //$NON-NLS-1$
		fDenseButton.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				setDenseMode(fDenseButton.getSelection());
			}
		});
		
		fLinesButton= createToolToggle(toolStrip, "Lines", fShowTreeLines); //$NON-NLS-1$
		fLinesButton.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				setShowTreeLines(fLinesButton.getSelection());
			}
		});
		
		fPreviewButton= createToolToggle(toolStrip, "Preview", fShowSourcePreview); //$NON-NLS-1$
		fPreviewButton.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				setShowSourcePreview(fPreviewButton.getSelection());
			}
		});
		
		fPinButton= createToolToggle(toolStrip, "Pin", fPinInspector); //$NON-NLS-1$
		fPinButton.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				setPinInspector(fPinButton.getSelection());
			}
		});
		
		fMalformedButton= createToolToggle(toolStrip, "Malformed", fOnlyMalformed); //$NON-NLS-1$
		fMalformedButton.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				setOnlyMalformed(fMalformedButton.getSelection());
			}
		});
		
		fQualifiedButton= createToolToggle(toolStrip, "Qualified", fQualifiedNodeNames); //$NON-NLS-1$
		fQualifiedButton.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				setQualifiedNodeNames(fQualifiedButton.getSelection());
			}
		});
		
		fPropsButton= createToolToggle(toolStrip, "Props", fShowPropertyCount); //$NON-NLS-1$
		fPropsButton.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				setShowPropertyCount(fPropsButton.getSelection());
			}
		});
		
		fHexButton= createToolToggle(toolStrip, "Hex", fShowFlagHex); //$NON-NLS-1$
		fHexButton.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				setShowFlagHex(fHexButton.getSelection());
			}
		});
		
		fLeafButton= createToolToggle(toolStrip, "Hide leafs", fHideLeafAttributes); //$NON-NLS-1$
		fLeafButton.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				setHideLeafAttributes(fLeafButton.getSelection());
			}
		});
		
		fSubtreeButton= createToolToggle(toolStrip, "Subtree", fFocusSubtree); //$NON-NLS-1$
		fSubtreeButton.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				setFocusSubtree(fSubtreeButton.getSelection());
			}
		});
		return toolStrip;
	}

	private Button createToolToggle(Composite parent, String text, boolean selected) {
		Button button= new Button(parent, SWT.TOGGLE);
		button.setText(text);
		button.setSelection(selected);
		button.setBackground(selected ? fAccentSoft : fPanel);
		button.setForeground(selected ? fAccent : fText);
		return button;
	}

	private void createGraphView(Composite parent) {
		fGraphForm= new ViewForm(parent, SWT.NONE);
		fGraphForm.setBackground(fPanel);
		fGraphTitle= new Label(fGraphForm, SWT.NONE);
		fGraphTitle.setText(" AST Diagram"); //$NON-NLS-1$
		fGraphTitle.setBackground(fPanel);
		fGraphTitle.setForeground(fText);
		fGraphForm.setTopLeft(fGraphTitle);
		fGraphForm.setTopRight(createGraphToolbar(fGraphForm));
		
		fGraphCanvas= new Canvas(fGraphForm, SWT.DOUBLE_BUFFERED | SWT.BORDER);
		fGraphCanvas.setBackground(fDiagramCanvas);
		fGraphCanvas.addPaintListener(new PaintListener() {
			public void paintControl(PaintEvent e) {
				paintGraph(e);
			}
		});
		fGraphCanvas.addMouseListener(new MouseAdapter() {
			public void mouseDown(MouseEvent e) {
				GraphNode node= findGraphNode(e.x, e.y);
				if (node != null && node.fNode != null && fViewer != null) {
					fViewer.setSelection(new StructuredSelection(node.fNode), true);
					fViewer.reveal(node.fNode);
				}
			}
		});
		fGraphForm.setContent(fGraphCanvas);
	}

	private Composite createGraphToolbar(Composite parent) {
		Composite toolbar= new Composite(parent, SWT.NONE);
		toolbar.setBackground(fPanel);
		GridLayout layout= new GridLayout(4, false);
		layout.marginWidth= 4;
		layout.marginHeight= 2;
		layout.horizontalSpacing= 4;
		toolbar.setLayout(layout);
		fGraphZoomOutButton= createDiagramButton(toolbar, "-"); //$NON-NLS-1$
		fGraphZoomOutButton.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				setGraphZoom(Math.max(75, fGraphZoom - 25));
			}
		});
		fGraphZoomInButton= createDiagramButton(toolbar, "+"); //$NON-NLS-1$
		fGraphZoomInButton.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				setGraphZoom(Math.min(150, fGraphZoom + 25));
			}
		});
		fGraphFitButton= createDiagramButton(toolbar, "Fit"); //$NON-NLS-1$
		fGraphFitButton.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				fGraphZoom= 100;
				fGraphDepth= Math.max(fGraphDepth, 4);
				fDialogSettings.put(SETTINGS_GRAPH_ZOOM, fGraphZoom);
				fDialogSettings.put(SETTINGS_GRAPH_DEPTH, fGraphDepth);
				redrawGraph();
				updateOptionControls();
			}
		});
		Button menuButton= createDiagramButton(toolbar, "..."); //$NON-NLS-1$
		menuButton.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				fGraphOverview= !fGraphOverview;
				fDialogSettings.put(SETTINGS_GRAPH_OVERVIEW, fGraphOverview);
				redrawGraph();
				updateOptionControls();
			}
		});
		return toolbar;
	}

	private Button createDiagramButton(Composite parent, String text) {
		Button button= new Button(parent, SWT.PUSH);
		button.setText(text);
		button.setBackground(fPanel);
		button.setForeground(fText);
		GridData data= new GridData(SWT.CENTER, SWT.CENTER, false, false);
		data.widthHint= "Fit".equals(text) ? 42 : 30; //$NON-NLS-1$
		button.setLayoutData(data);
		return button;
	}

	private static class GraphNode {
		ASTNode fNode;
		GraphNode fParent;
		String fLabel;
		String fDetail;
		int fDepth;
		int fChildCount;
		boolean fMatched;
		boolean fMalformed;
		boolean fVirtual;
		int fWeight;
		int fX;
		int fY;
		int fWidth;
		int fHeight;

		GraphNode(ASTNode node, String label, int depth, int childCount, boolean matched, boolean malformed) {
			fNode= node;
			fLabel= label;
			fDepth= depth;
			fChildCount= childCount;
			fMatched= matched;
			fMalformed= malformed;
			fWeight= 1;
		}

		GraphNode(String label, String detail, int depth, int childCount, GraphNode parent) {
			fLabel= label;
			fDetail= detail;
			fDepth= depth;
			fChildCount= childCount;
			fParent= parent;
			fVirtual= true;
			fWeight= Math.max(childCount, 1);
		}
	}

	private void paintGraph(PaintEvent event) {
		if (fGraphCanvas == null || fGraphCanvas.isDisposed()) {
			return;
		}
		Rectangle area= fGraphCanvas.getClientArea();
		event.gc.setAntialias(SWT.ON);
		event.gc.setBackground(fDiagramCanvas);
		event.gc.fillRectangle(area);
		if (!fShowGraph || fRoot == null) {
			drawGraphEmpty(event, area, "Open a Java editor to generate the AST graph."); //$NON-NLS-1$
			return;
		}
		ASTNode root= getGraphRoot();
		if (root == null) {
			drawGraphEmpty(event, area, "Select an AST node or refresh the AST."); //$NON-NLS-1$
			return;
		}
		fGraphNodes.clear();
		collectGraphNodes(root, 0, fGraphNodes);
		if (fGraphNodes.size() == 0) {
			drawGraphEmpty(event, area, "No graph nodes match the current filters."); //$NON-NLS-1$
			return;
		}
		drawGraphBackdrop(event, area, root);
		layoutGraphNodes(area);
		drawGraphEdges(event, root);
		drawGraphNodes(event);
		drawGraphLegend(event, area);
		drawGraphOverview(event, area);
		drawGraphMetrics(event, area);
		updateGraphTitle();
	}

	private void drawGraphEmpty(PaintEvent event, Rectangle area, String message) {
		drawGraphBackdrop(event, area, null);
		event.gc.setForeground(fMutedText);
		event.gc.drawText(message, area.x + 18, area.y + 18, true);
	}

	private void drawGraphBackdrop(PaintEvent event, Rectangle area, ASTNode root) {
		event.gc.setBackground(fDiagramCanvas);
		event.gc.fillRectangle(area);
		event.gc.setForeground(fBorder);
		event.gc.setLineWidth(1);
		int grid= scaleGraph(64);
		for (int x= area.x + grid; x < area.x + area.width; x+= grid) {
			event.gc.drawLine(x, area.y, x, area.y + area.height);
		}
		for (int y= area.y + grid; y < area.y + area.height; y+= grid) {
			event.gc.drawLine(area.x, y, area.x + area.width, y);
		}
		event.gc.setForeground(fMutedText);
		String title= root == null ? "AST Diagram" : "AST Diagram - " + getSimpleClassName(root); //$NON-NLS-1$ //$NON-NLS-2$
		if (fGraphRoot != null) {
			title+= "  [IntelliSense focus]"; //$NON-NLS-1$
		}
		event.gc.drawText(title, area.x + 14, area.y + 10, true);
	}

	private ASTNode getGraphRoot() {
		if (fGraphRoot != null) {
			return fGraphRoot;
		}
		if (fGraphSyncSelection) {
			IStructuredSelection selection= (IStructuredSelection) fViewer.getSelection();
			ASTNode selected= getASTNodeNearSelection(selection);
			if (selected != null) {
				return selected;
			}
		}
		return fFocusSubtree && fSubtreeRoot != null ? fSubtreeRoot : fRoot;
	}

	private void collectGraphProposals(ASTNode node, String prefix, ArrayList proposals, int limit) {
		if (node == null || proposals.size() >= limit) {
			return;
		}
		addGraphProposal(node, prefix, proposals);
		Object[] children= new ASTViewContentProvider().getChildren(node);
		for (int i= 0; i < children.length && proposals.size() < limit; i++) {
			if (children[i] instanceof ASTNode) {
				collectGraphProposals((ASTNode) children[i], prefix, proposals, limit);
			} else if (children[i] instanceof NodeProperty) {
				Object[] propertyChildren= ((NodeProperty) children[i]).getChildren();
				for (int j= 0; j < propertyChildren.length && proposals.size() < limit; j++) {
					if (propertyChildren[j] instanceof ASTNode) {
						collectGraphProposals((ASTNode) propertyChildren[j], prefix, proposals, limit);
					}
				}
			}
		}
	}

	private void addGraphProposal(ASTNode node, String prefix, ArrayList proposals) {
		String name= getSimpleClassName(node);
		String content= getGraphProposalContent(node);
		if (!proposalMatchesPrefix(content, prefix) && !proposalMatchesPrefix(name, prefix)) {
			return;
		}
		if (containsGraphProposal(proposals, node)) {
			return;
		}
		int line= fRoot == null || node.getStartPosition() < 0 ? -1 : fRoot.getLineNumber(node.getStartPosition());
		String label= name + "  -  line " + line + "  children " + getGraphChildCount(node); //$NON-NLS-1$ //$NON-NLS-2$
		String description= "Graph root: " + getNodePath(node); //$NON-NLS-1$
		proposals.add(new GraphContentProposal(node, content, label, description));
	}

	private String getGraphProposalContent(ASTNode node) {
		int line= fRoot == null || node.getStartPosition() < 0 ? -1 : fRoot.getLineNumber(node.getStartPosition());
		return getSimpleClassName(node) + " L" + line + " @" + node.getStartPosition(); //$NON-NLS-1$ //$NON-NLS-2$
	}

	private boolean containsGraphProposal(ArrayList proposals, ASTNode node) {
		for (int i= 0; i < proposals.size(); i++) {
			GraphContentProposal proposal= (GraphContentProposal) proposals.get(i);
			if (proposal.getNode() == node) {
				return true;
			}
		}
		return false;
	}

	private void collectGraphNodes(ASTNode node, int depth, ArrayList result) {
		collectGraphNodes(node, depth, result, null);
	}

	private void collectGraphNodes(ASTNode node, int depth, ArrayList result, GraphNode parentGraphNode) {
		if (node == null || depth > fGraphDepth || result.size() >= 180) {
			return;
		}
		GraphNode graphNode= null;
		if (shouldRenderGraphNode(node, depth)) {
			graphNode= new GraphNode(node, getGraphLabel(node), depth, getGraphChildCount(node), matchesSearch(node), isMalformedElement(node));
			graphNode.fParent= parentGraphNode;
			graphNode.fDetail= getGraphNodeDetail(node);
			graphNode.fWeight= Math.min(countSubtreeNodes(node), 999);
			result.add(graphNode);
		}
		Object[] children= new ASTViewContentProvider().getChildren(node);
		for (int i= 0; i < children.length; i++) {
			if (children[i] instanceof ASTNode) {
				collectGraphNodes((ASTNode) children[i], depth + 1, result, graphNode == null ? parentGraphNode : graphNode);
			} else if (children[i] instanceof NodeProperty) {
				NodeProperty property= (NodeProperty) children[i];
				Object[] propertyChildren= property.getChildren();
				GraphNode propertyGraphNode= graphNode;
				if (graphNode != null && shouldShowGraphPropertyBlock(propertyChildren, depth + 1)) {
					propertyGraphNode= new GraphNode(getGraphPropertyLabel(property), getGraphPropertyDetail(propertyChildren), depth + 1, propertyChildren.length, graphNode);
					result.add(propertyGraphNode);
				}
				for (int j= 0; j < propertyChildren.length; j++) {
					if (propertyChildren[j] instanceof ASTNode) {
						collectGraphNodes((ASTNode) propertyChildren[j], propertyGraphNode == graphNode ? depth + 1 : depth + 2, result, propertyGraphNode);
					}
				}
			}
		}
	}

	private boolean shouldShowGraphPropertyBlock(Object[] propertyChildren, int depth) {
		return propertyChildren.length > 1 && depth <= fGraphDepth && fGraphNodes.size() < 180;
	}

	private String getGraphPropertyLabel(NodeProperty property) {
		String label= property.getLabel();
		int colon= label.indexOf(':');
		if (colon != -1) {
			label= label.substring(0, colon);
		}
		return label.length() > 30 ? label.substring(0, 27) + "..." : label; //$NON-NLS-1$
	}

	private String getGraphPropertyDetail(Object[] propertyChildren) {
		return propertyChildren.length + (propertyChildren.length == 1 ? " child" : " children"); //$NON-NLS-1$ //$NON-NLS-2$
	}

	private boolean shouldRenderGraphNode(ASTNode node, int depth) {
		if (depth == 0) {
			return true;
		}
		if (matchesGraphNodeExact(node)) {
			return true;
		}
		return fSearchPattern.length() > 0 && hasGraphMatchDescendant(node, depth);
	}

	private boolean matchesGraphNodeExact(ASTNode node) {
		boolean searchOk= fSearchPattern.length() == 0 || matchesSearch(node);
		boolean malformedOk= !fOnlyMalformed || isMalformedElement(node);
		return searchOk && malformedOk;
	}

	private boolean hasGraphMatchDescendant(ASTNode node, int depth) {
		if (depth >= fGraphDepth) {
			return false;
		}
		Object[] children= new ASTViewContentProvider().getChildren(node);
		for (int i= 0; i < children.length; i++) {
			if (children[i] instanceof ASTNode) {
				ASTNode child= (ASTNode) children[i];
				if (matchesGraphNodeExact(child) || hasGraphMatchDescendant(child, depth + 1)) {
					return true;
				}
			} else if (children[i] instanceof NodeProperty) {
				Object[] propertyChildren= ((NodeProperty) children[i]).getChildren();
				for (int j= 0; j < propertyChildren.length; j++) {
					if (propertyChildren[j] instanceof ASTNode) {
						ASTNode child= (ASTNode) propertyChildren[j];
						if (matchesGraphNodeExact(child) || hasGraphMatchDescendant(child, depth + 1)) {
							return true;
						}
					}
				}
			}
		}
		return false;
	}

	private String getGraphLabel(ASTNode node) {
		String label= getSimpleClassName(node);
		if (!fGraphCompact && node.getStartPosition() >= 0 && fGraphOrientation == GRAPH_HORIZONTAL) {
			int line= fRoot == null ? -1 : fRoot.getLineNumber(node.getStartPosition());
			label+= "  L" + line + " @" + node.getStartPosition(); //$NON-NLS-1$ //$NON-NLS-2$
		}
		return label.length() > 34 ? label.substring(0, 31) + "..." : label; //$NON-NLS-1$
	}

	private String getGraphNodeDetail(ASTNode node) {
		if (node.getStartPosition() < 0 || fRoot == null) {
			return getGraphChildCount(node) + " children"; //$NON-NLS-1$
		}
		int line= fRoot.getLineNumber(node.getStartPosition());
		return "line " + line + "  children " + getGraphChildCount(node); //$NON-NLS-1$ //$NON-NLS-2$
	}

	private int getGraphChildCount(ASTNode node) {
		int count= 0;
		Object[] children= new ASTViewContentProvider().getChildren(node);
		for (int i= 0; i < children.length; i++) {
			if (children[i] instanceof ASTNode) {
				count++;
			} else if (children[i] instanceof NodeProperty) {
				Object[] propertyChildren= ((NodeProperty) children[i]).getChildren();
				for (int j= 0; j < propertyChildren.length; j++) {
					if (propertyChildren[j] instanceof ASTNode) {
						count++;
					}
				}
			}
		}
		return count;
	}

	private void layoutGraphNodes(Rectangle area) {
		int maxDepth= 0;
		for (int i= 0; i < fGraphNodes.size(); i++) {
			maxDepth= Math.max(maxDepth, ((GraphNode) fGraphNodes.get(i)).fDepth);
		}
		for (int depth= 0; depth <= maxDepth; depth++) {
			ArrayList level= getGraphLevel(depth);
			int nodeWidth= scaleGraph(fGraphCompact ? 132 : 190);
			int nodeHeight= scaleGraph(fGraphCompact ? 42 : 66);
			int gap= scaleGraph(fGraphCompact ? 28 : 42);
			int levelBreadth= level.size() * nodeWidth + Math.max(level.size() - 1, 0) * gap;
			int startX= Math.max(area.x + scaleGraph(28), area.x + (area.width - levelBreadth) / 2);
			int startY= Math.max(area.y + scaleGraph(68), area.y + (area.height - levelBreadth) / 2);
			for (int i= 0; i < level.size(); i++) {
				GraphNode node= (GraphNode) level.get(i);
				node.fWidth= nodeWidth;
				node.fHeight= nodeHeight;
				if (fGraphOrientation == GRAPH_HORIZONTAL) {
					node.fX= area.x + scaleGraph(32) + depth * (node.fWidth + scaleGraph(86));
					node.fY= startY + i * (node.fHeight + gap);
				} else {
					node.fX= startX + i * (node.fWidth + gap);
					node.fY= area.y + scaleGraph(64) + depth * (node.fHeight + scaleGraph(54));
				}
			}
		}
	}

	private ArrayList getGraphLevel(int depth) {
		ArrayList level= new ArrayList();
		for (int i= 0; i < fGraphNodes.size(); i++) {
			GraphNode node= (GraphNode) fGraphNodes.get(i);
			if (node.fDepth == depth) {
				level.add(node);
			}
		}
		return level;
	}

	private void drawGraphEdges(PaintEvent event, ASTNode root) {
		event.gc.setForeground(fText);
		event.gc.setLineWidth(2);
		for (int i= 0; i < fGraphNodes.size(); i++) {
			GraphNode child= (GraphNode) fGraphNodes.get(i);
			GraphNode parentNode= child.fParent;
			if (parentNode == null && child.fNode != null) {
				parentNode= findGraphNode(child.fNode.getParent());
			}
			if (parentNode != null) {
				int x1= parentNode.fX + parentNode.fWidth / 2;
				int y1= parentNode.fY + parentNode.fHeight;
				int x2= child.fX + child.fWidth / 2;
				int y2= child.fY;
				if (fGraphOrientation == GRAPH_HORIZONTAL) {
					x1= parentNode.fX + parentNode.fWidth;
					y1= parentNode.fY + parentNode.fHeight / 2;
					x2= child.fX;
					y2= child.fY + child.fHeight / 2;
					int mid= x1 + (x2 - x1) / 2;
					event.gc.drawLine(x1, y1, mid, y1);
					event.gc.drawLine(mid, y1, mid, y2);
					event.gc.drawLine(mid, y2, x2, y2);
					drawGraphArrow(event, x2, y2, true);
				} else {
					int mid= y1 + (y2 - y1) / 2;
					event.gc.drawLine(x1, y1, x1, mid);
					event.gc.drawLine(x1, mid, x2, mid);
					event.gc.drawLine(x2, mid, x2, y2);
					drawGraphArrow(event, x2, y2, false);
				}
			}
		}
		event.gc.setLineWidth(1);
	}

	private void drawGraphArrow(PaintEvent event, int x, int y, boolean horizontal) {
		int size= scaleGraph(7);
		if (horizontal) {
			event.gc.drawLine(x, y, x - size, y - size / 2);
			event.gc.drawLine(x, y, x - size, y + size / 2);
		} else {
			event.gc.drawLine(x, y, x - size / 2, y - size);
			event.gc.drawLine(x, y, x + size / 2, y - size);
		}
	}

	private void drawGraphNodes(PaintEvent event) {
		IStructuredSelection selection= fViewer == null ? StructuredSelection.EMPTY : (IStructuredSelection) fViewer.getSelection();
		Object selected= selection.getFirstElement();
		for (int i= 0; i < fGraphNodes.size(); i++) {
			GraphNode node= (GraphNode) fGraphNodes.get(i);
			boolean isSelected= node.fNode != null && selected == node.fNode;
			Color border= getDiagramBorder(node, isSelected);
			Color fill= getDiagramFill(node, isSelected);
			event.gc.setBackground(fBorder);
			event.gc.setForeground(fBorder);
			event.gc.fillRoundRectangle(node.fX + scaleGraph(3), node.fY + scaleGraph(4), node.fWidth, node.fHeight, scaleGraph(5), scaleGraph(5));
			event.gc.setBackground(fill);
			event.gc.setForeground(border);
			event.gc.fillRoundRectangle(node.fX, node.fY, node.fWidth, node.fHeight, scaleGraph(4), scaleGraph(4));
			event.gc.setLineWidth(isSelected ? 3 : 2);
			event.gc.drawRoundRectangle(node.fX, node.fY, node.fWidth, node.fHeight, scaleGraph(4), scaleGraph(4));
			event.gc.setLineWidth(1);
			event.gc.setForeground(isSelected ? fAccent : fText);
			drawCenteredGraphText(event, node.fLabel, node.fX + scaleGraph(8), node.fY + scaleGraph(11), node.fWidth - scaleGraph(16));
			if (!fGraphCompact && node.fDetail != null) {
				event.gc.setForeground(fMutedText);
				drawCenteredGraphText(event, node.fDetail, node.fX + scaleGraph(8), node.fY + scaleGraph(34), node.fWidth - scaleGraph(16));
			}
		}
	}

	private Color getDiagramFill(GraphNode node, boolean selected) {
		if (selected || (fGraphHeatmap && node.fWeight > 30)) {
			return fAccentSoft;
		}
		if (node.fMalformed) {
			return fDiagramProblemFill;
		}
		if (node.fVirtual) {
			return fDiagramGroupFill;
		}
		String name= getSimpleClassName(node.fNode);
		if (name.indexOf("CompilationUnit") != -1) { //$NON-NLS-1$
			return fDiagramUnitFill;
		}
		if (name.indexOf("Module") != -1 || name.indexOf("Declaration") != -1) { //$NON-NLS-1$ //$NON-NLS-2$
			return fDiagramModuleFill;
		}
		if (node.fChildCount == 0) {
			return fDiagramLeafFill;
		}
		return fPanel;
	}

	private Color getDiagramBorder(GraphNode node, boolean selected) {
		if (selected || (node.fMatched && fSearchPattern.length() > 0)) {
			return fAccent;
		}
		if (node.fMalformed) {
			return fWarning;
		}
		if (node.fVirtual) {
			return fMutedText;
		}
		return fBorder;
	}

	private void drawCenteredGraphText(PaintEvent event, String text, int x, int y, int width) {
		String shortened= shortenGraphText(event, text, width);
		Point extent= event.gc.textExtent(shortened);
		event.gc.drawText(shortened, x + Math.max(0, (width - extent.x) / 2), y, true);
	}

	private String shortenGraphText(PaintEvent event, String text, int width) {
		if (text == null) {
			return ""; //$NON-NLS-1$
		}
		String result= text;
		while (result.length() > 4 && event.gc.textExtent(result).x > width) {
			result= result.substring(0, result.length() - 4) + "..."; //$NON-NLS-1$
		}
		return result;
	}

	private int scaleGraph(int value) {
		return Math.max(1, value * fGraphZoom / 100);
	}

	private void drawGraphLegend(PaintEvent event, Rectangle area) {
		int x= area.x + Math.max(area.width - 260, 20);
		int y= area.y + 10;
		event.gc.setBackground(fPanel);
		event.gc.setForeground(fBorder);
		event.gc.fillRoundRectangle(x, y, 240, 28, 8, 8);
		event.gc.drawRoundRectangle(x, y, 240, 28, 8, 8);
		drawLegendSwatch(event, x + 10, y + 9, fAccent, "IntelliSense match"); //$NON-NLS-1$
		drawLegendSwatch(event, x + 128, y + 9, fWarning, "Malformed"); //$NON-NLS-1$
	}

	private void drawLegendSwatch(PaintEvent event, int x, int y, Color color, String text) {
		event.gc.setBackground(color);
		event.gc.fillRectangle(x, y, 10, 10);
		event.gc.setForeground(fMutedText);
		event.gc.drawText(text, x + 14, y - 3, true);
	}

	private void drawGraphOverview(PaintEvent event, Rectangle area) {
		if (!fGraphOverview || fGraphNodes.size() == 0) {
			return;
		}
		int width= 150;
		int height= 86;
		int x= area.x + area.width - width - 18;
		int y= area.y + 48;
		event.gc.setBackground(fPanel);
		event.gc.setForeground(fBorder);
		event.gc.fillRoundRectangle(x, y, width, height, 8, 8);
		event.gc.drawRoundRectangle(x, y, width, height, 8, 8);
		Rectangle bounds= getGraphBounds();
		for (int i= 0; i < fGraphNodes.size(); i++) {
			GraphNode node= (GraphNode) fGraphNodes.get(i);
			int px= x + 8 + (node.fX - bounds.x) * (width - 16) / Math.max(bounds.width, 1);
			int py= y + 8 + (node.fY - bounds.y) * (height - 16) / Math.max(bounds.height, 1);
			event.gc.setBackground(node.fMatched && fSearchPattern.length() > 0 ? fAccent : fMutedText);
			event.gc.fillRectangle(px, py, 3, 3);
		}
		event.gc.setForeground(fMutedText);
		event.gc.drawText("overview", x + 8, y + height - 18, true); //$NON-NLS-1$
	}

	private Rectangle getGraphBounds() {
		if (fGraphNodes.size() == 0) {
			return new Rectangle(0, 0, 1, 1);
		}
		GraphNode first= (GraphNode) fGraphNodes.get(0);
		int minX= first.fX;
		int minY= first.fY;
		int maxX= first.fX + first.fWidth;
		int maxY= first.fY + first.fHeight;
		for (int i= 1; i < fGraphNodes.size(); i++) {
			GraphNode node= (GraphNode) fGraphNodes.get(i);
			minX= Math.min(minX, node.fX);
			minY= Math.min(minY, node.fY);
			maxX= Math.max(maxX, node.fX + node.fWidth);
			maxY= Math.max(maxY, node.fY + node.fHeight);
		}
		return new Rectangle(minX, minY, maxX - minX, maxY - minY);
	}

	private void drawGraphMetrics(PaintEvent event, Rectangle area) {
		int matches= 0;
		int malformed= 0;
		for (int i= 0; i < fGraphNodes.size(); i++) {
			GraphNode node= (GraphNode) fGraphNodes.get(i);
			if (node.fMatched && fSearchPattern.length() > 0) {
				matches++;
			}
			if (node.fMalformed) {
				malformed++;
			}
		}
		String text= "nodes " + fGraphNodes.size() + "   matches " + matches + "   malformed " + malformed + "   zoom " + fGraphZoom + "%"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$
		event.gc.setBackground(fPanel);
		event.gc.setForeground(fMutedText);
		event.gc.drawText(text, area.x + 14, area.y + area.height - 24, true);
	}

	private GraphNode findGraphNode(ASTNode astNode) {
		if (astNode == null) {
			return null;
		}
		for (int i= 0; i < fGraphNodes.size(); i++) {
			GraphNode node= (GraphNode) fGraphNodes.get(i);
			if (node.fNode == astNode) {
				return node;
			}
		}
		return null;
	}

	private GraphNode findGraphNode(int x, int y) {
		for (int i= 0; i < fGraphNodes.size(); i++) {
			GraphNode node= (GraphNode) fGraphNodes.get(i);
			if (x >= node.fX && x <= node.fX + node.fWidth && y >= node.fY && y <= node.fY + node.fHeight) {
				return node;
			}
		}
		return null;
	}

	private void updateGraphTitle() {
		if (fGraphTitle != null && !fGraphTitle.isDisposed()) {
			fGraphTitle.setText(" AST Diagram (" + fGraphNodes.size() + " blocks, depth " + fGraphDepth + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
		}
	}

	private void installSearchIntelliSense() {
		fSearchProposalAdapter= new ContentProposalAdapter(
				fSearchText,
				new TextContentAdapter(),
				new SearchProposalProvider(),
				KeyStroke.getInstance(SWT.CTRL, ' '),
				getProposalActivationCharacters());
		fSearchProposalAdapter.setProposalAcceptanceStyle(ContentProposalAdapter.PROPOSAL_REPLACE);
		fSearchProposalAdapter.setFilterStyle(ContentProposalAdapter.FILTER_CUMULATIVE);
		fSearchProposalAdapter.setAutoActivationDelay(180);
		fSearchProposalAdapter.setPopupSize(new Point(520, 260));
		fSearchProposalAdapter.setEnabled(fIntelliSense);
	}

	private void installGraphIntelliSense() {
		fGraphProposalAdapter= new ContentProposalAdapter(
				fGraphSenseText,
				new TextContentAdapter(),
				new GraphProposalProvider(),
				KeyStroke.getInstance(SWT.CTRL, ' '),
				getProposalActivationCharacters());
		fGraphProposalAdapter.setProposalAcceptanceStyle(ContentProposalAdapter.PROPOSAL_REPLACE);
		fGraphProposalAdapter.setFilterStyle(ContentProposalAdapter.FILTER_CUMULATIVE);
		fGraphProposalAdapter.setAutoActivationDelay(160);
		fGraphProposalAdapter.setPopupSize(new Point(560, 280));
		fGraphProposalAdapter.setEnabled(true);
		fGraphProposalAdapter.addContentProposalListener(new IContentProposalListener() {
			public void proposalAccepted(IContentProposal proposal) {
				if (proposal instanceof GraphContentProposal) {
					fGraphRoot= ((GraphContentProposal) proposal).getNode();
					fShowGraph= true;
					fGraphSyncSelection= false;
					fDialogSettings.put(SETTINGS_SHOW_GRAPH, fShowGraph);
					fDialogSettings.put(SETTINGS_GRAPH_SYNC_SELECTION, fGraphSyncSelection);
					applyTrayVisibility();
					redrawGraph();
					updateOptionControls();
				}
			}
		});
	}

	private char[] getProposalActivationCharacters() {
		return new char[] {
				'a','b','c','d','e','f','g','h','i','j','k','l','m',
				'n','o','p','q','r','s','t','u','v','w','x','y','z',
				'A','B','C','D','E','F','G','H','I','J','K','L','M',
				'N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
				'.', '>', '[', '_' };
	}

	private class SearchProposalProvider implements IContentProposalProvider {
		public IContentProposal[] getProposals(String contents, int position) {
			ArrayList proposals= new ArrayList();
			String prefix= contents == null ? "" : contents.substring(0, Math.min(position, contents.length())); //$NON-NLS-1$
			collectSearchProposals(fRoot, prefix, proposals, 80);
			return (IContentProposal[]) proposals.toArray(new IContentProposal[proposals.size()]);
		}
	}

	private class GraphProposalProvider implements IContentProposalProvider {
		public IContentProposal[] getProposals(String contents, int position) {
			ArrayList proposals= new ArrayList();
			String prefix= contents == null ? "" : contents.substring(0, Math.min(position, contents.length())); //$NON-NLS-1$
			collectGraphProposals(fRoot, prefix, proposals, 120);
			return (IContentProposal[]) proposals.toArray(new IContentProposal[proposals.size()]);
		}
	}

	private static class SearchProposal implements IContentProposal {
		private String fContent;
		private String fLabel;
		private String fDescription;

		public SearchProposal(String content, String label, String description) {
			fContent= content;
			fLabel= label;
			fDescription= description;
		}

		public String getContent() {
			return fContent;
		}

		public int getCursorPosition() {
			return fContent.length();
		}

		public String getLabel() {
			return fLabel;
		}

		public String getDescription() {
			return fDescription;
		}
	}

	private static class GraphContentProposal implements IContentProposal {
		private ASTNode fNode;
		private String fContent;
		private String fLabel;
		private String fDescription;

		public GraphContentProposal(ASTNode node, String content, String label, String description) {
			fNode= node;
			fContent= content;
			fLabel= label;
			fDescription= description;
		}

		public ASTNode getNode() {
			return fNode;
		}

		public String getContent() {
			return fContent;
		}

		public int getCursorPosition() {
			return fContent.length();
		}

		public String getLabel() {
			return fLabel;
		}

		public String getDescription() {
			return fDescription;
		}
	}

	private Composite createInspector(Composite parent) {
		fInspector= new Composite(parent, SWT.BORDER);
		fInspector.setBackground(fPanel);
		GridLayout layout= new GridLayout(1, false);
		layout.marginWidth= 14;
		layout.marginHeight= 14;
		layout.verticalSpacing= 10;
		fInspector.setLayout(layout);
		
		Label eyebrow= new Label(fInspector, SWT.NONE);
		eyebrow.setText("INSPECTOR"); //$NON-NLS-1$
		eyebrow.setBackground(fPanel);
		eyebrow.setForeground(fAccent);
		eyebrow.setFont(fMonoFont);
		
		fInspectorTitle= new Label(fInspector, SWT.WRAP);
		fInspectorTitle.setText("No selection"); //$NON-NLS-1$
		fInspectorTitle.setBackground(fPanel);
		fInspectorTitle.setForeground(fText);
		fInspectorTitle.setFont(fHeroFont);
		fInspectorTitle.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false));
		
		fInspectorKind= createInspectorLine(fInspector, "Kind: --"); //$NON-NLS-1$
		fInspectorRange= createInspectorLine(fInspector, "Range: --"); //$NON-NLS-1$
		fInspectorExtra= createInspectorLine(fInspector, "Details: select an AST item to inspect it here."); //$NON-NLS-1$
		fInspectorStats= createInspectorLine(fInspector, "Stats: --"); //$NON-NLS-1$
		fInspectorPath= createInspectorLine(fInspector, "Path: --"); //$NON-NLS-1$
		
		fInspectorPreview= new Text(fInspector, SWT.MULTI | SWT.WRAP | SWT.READ_ONLY | SWT.BORDER | SWT.V_SCROLL);
		fInspectorPreview.setText("Source preview: --"); //$NON-NLS-1$
		fInspectorPreview.setBackground(fShell);
		fInspectorPreview.setForeground(fText);
		fInspectorPreview.setFont(fMonoFont);
		GridData previewData= new GridData(SWT.FILL, SWT.FILL, true, true);
		previewData.heightHint= 120;
		fInspectorPreview.setLayoutData(previewData);
		return fInspector;
	}

	private Label createInspectorLine(Composite parent, String text) {
		Label label= new Label(parent, SWT.WRAP);
		label.setText(text);
		label.setBackground(fPanel);
		label.setForeground(fMutedText);
		label.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false));
		return label;
	}

	private Composite createStatusBar(Composite parent) {
		Composite status= new Composite(parent, SWT.NONE);
		status.setBackground(fPanel);
		GridLayout layout= new GridLayout(3, true);
		layout.marginWidth= 10;
		layout.marginHeight= 5;
		layout.horizontalSpacing= 10;
		status.setLayout(layout);
		
		fStatusFilter= createStatusLine(status, "View: " + getElementFilterLabel()); //$NON-NLS-1$
		fStatusSelection= createStatusLine(status, "Selection: --"); //$NON-NLS-1$
		fStatusTray= createStatusLine(status, "Tray: 0 items"); //$NON-NLS-1$
		return status;
	}

	private Label createStatusLine(Composite parent, String text) {
		Label label= new Label(parent, SWT.NONE);
		label.setText(text);
		label.setBackground(fPanel);
		label.setForeground(fMutedText);
		label.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
		return label;
	}

	private void styleTree(Tree tree) {
		tree.setBackground(fPanel);
		tree.setForeground(fText);
		tree.setFont(fMonoFont);
		tree.setLinesVisible(fShowTreeLines);
		if (fDenseMode) {
			tree.setHeaderVisible(false);
		}
	}

	private boolean hasFilteredDescendant(Object element) {
		if (fViewer == null || fViewer.getContentProvider() == null) {
			return false;
		}
		try {
			Object[] children= ((ASTViewContentProvider) fViewer.getContentProvider()).getChildren(element);
			for (int i= 0; i < children.length; i++) {
				if (matchesSearch(children[i])) {
					return true;
				}
				if (hasFilteredDescendant(children[i])) {
					return true;
				}
			}
		} catch (RuntimeException e) {
			return false;
		}
		return false;
	}

	private boolean matchesSearch(Object element) {
		if (fSearchPattern.length() == 0) {
			return true;
		}
		String label= fLabelProvider == null ? "" : fLabelProvider.getText(element); //$NON-NLS-1$
		String className= element == null ? "" : element.getClass().getName(); //$NON-NLS-1$
		String target;
		if (fSearchScope == SEARCH_LABEL) {
			target= label;
		} else if (fSearchScope == SEARCH_CLASS) {
			target= className;
		} else {
			target= label + " " + className; //$NON-NLS-1$
		}
		if (fSearchRegex) {
			try {
				int flags= fSearchCaseSensitive ? 0 : Pattern.CASE_INSENSITIVE;
				return Pattern.compile(fSearchPattern, flags).matcher(target).find();
			} catch (PatternSyntaxException e) {
				return false;
			}
		}
		if (fSearchCaseSensitive) {
			return target.indexOf(fSearchPattern) != -1;
		}
		return target.toLowerCase().indexOf(fSearchPattern.toLowerCase()) != -1;
	}

	private void collectSearchProposals(Object element, String prefix, ArrayList proposals, int limit) {
		if (element == null || proposals.size() >= limit) {
			return;
		}
		addSearchProposal(element, prefix, proposals);
		if (proposals.size() >= limit || fViewer == null || fViewer.getContentProvider() == null) {
			return;
		}
		Object[] children= ((ASTViewContentProvider) fViewer.getContentProvider()).getChildren(element);
		for (int i= 0; i < children.length && proposals.size() < limit; i++) {
			collectSearchProposals(children[i], prefix, proposals, limit);
		}
	}

	private void addSearchProposal(Object element, String prefix, ArrayList proposals) {
		if (!matchesIntelliSenseMode(element)) {
			return;
		}
		String label= fLabelProvider == null ? String.valueOf(element) : fLabelProvider.getText(element);
		String className= element == null ? "" : element.getClass().getName(); //$NON-NLS-1$
		String content= getProposalContent(element, label, className);
		if (content == null || content.length() == 0 || !proposalMatchesPrefix(content, prefix) || containsProposal(proposals, content)) {
			return;
		}
		proposals.add(new SearchProposal(content, content + "  -  " + getProposalKind(element), className)); //$NON-NLS-1$
	}

	private boolean matchesIntelliSenseMode(Object element) {
		if (fIntelliSenseMode == SENSE_NODES) {
			return element instanceof ASTNode;
		}
		if (fIntelliSenseMode == SENSE_PROPERTIES) {
			return element instanceof ASTAttribute && !(element instanceof Binding);
		}
		if (fIntelliSenseMode == SENSE_BINDINGS) {
			return element instanceof Binding || element instanceof BindingProperty;
		}
		return true;
	}

	private String getProposalContent(Object element, String label, String className) {
		if (element instanceof ASTNode) {
			String name= getSimpleClassName(element);
			return fSearchScope == SEARCH_CLASS ? className : name;
		}
		if (element instanceof NodeProperty) {
			return ((NodeProperty) element).getPropertyName();
		}
		if (element instanceof Binding) {
			return ((Binding) element).getLabel();
		}
		if (element instanceof ASTAttribute) {
			int colon= label.indexOf(':');
			return colon == -1 ? label : label.substring(0, colon);
		}
		return label;
	}

	private String getProposalKind(Object element) {
		if (element instanceof ASTNode) {
			return "AST node"; //$NON-NLS-1$
		}
		if (element instanceof Binding) {
			return "binding"; //$NON-NLS-1$
		}
		if (element instanceof ASTAttribute) {
			return "attribute"; //$NON-NLS-1$
		}
		return "element"; //$NON-NLS-1$
	}

	private boolean proposalMatchesPrefix(String content, String prefix) {
		if (prefix == null || prefix.trim().length() == 0) {
			return true;
		}
		String typed= prefix.trim();
		if (fSearchRegex) {
			return true;
		}
		if (fSearchCaseSensitive) {
			return content.indexOf(typed) != -1;
		}
		return content.toLowerCase().indexOf(typed.toLowerCase()) != -1;
	}

	private boolean containsProposal(ArrayList proposals, String content) {
		for (int i= 0; i < proposals.size(); i++) {
			SearchProposal proposal= (SearchProposal) proposals.get(i);
			if (proposal.getContent().equals(content)) {
				return true;
			}
		}
		return false;
	}

	private void revealFirstSearchMatch() {
		if (fSearchPattern.length() == 0 || fRoot == null || fViewer == null) {
			return;
		}
		Object match= findFirstSearchMatch(fRoot);
		if (match != null) {
			fViewer.reveal(match);
			fViewer.setSelection(new StructuredSelection(match), true);
		}
	}

	private Object findFirstSearchMatch(Object element) {
		if (element == null) {
			return null;
		}
		if (matchesSearch(element)) {
			return element;
		}
		Object[] children= ((ASTViewContentProvider) fViewer.getContentProvider()).getChildren(element);
		for (int i= 0; i < children.length; i++) {
			Object found= findFirstSearchMatch(children[i]);
			if (found != null) {
				return found;
			}
		}
		return null;
	}

	private int getElementDepth(Object element) {
		ASTNode node= getASTNodeForDepth(element);
		if (node == null) {
			return 0;
		}
		int depth= getNodeDepth(node);
		if (element instanceof ASTAttribute) {
			depth++;
		}
		return depth;
	}

	private ASTNode getASTNodeForDepth(Object element) {
		if (element instanceof ASTNode) {
			return (ASTNode) element;
		}
		if (element instanceof ASTAttribute) {
			return ((ASTAttribute) element).getParentASTNode();
		}
		return null;
	}

	private boolean isInFocusedSubtreePath(Object element) {
		ASTNode node= getASTNodeForDepth(element);
		if (node == null) {
			return true;
		}
		return isSameOrDescendant(node, fSubtreeRoot) || isSameOrDescendant(fSubtreeRoot, node);
	}

	private boolean isSameOrDescendant(ASTNode node, ASTNode possibleAncestor) {
		ASTNode current= node;
		while (current != null) {
			if (current == possibleAncestor) {
				return true;
			}
			current= current.getParent();
		}
		return false;
	}

	private String getModeLabel() {
		StringBuffer buf= new StringBuffer();
		buf.append(getASTLevelLabel(fCurrentASTLevel).replaceAll("AST Level ", "JLS ")); //$NON-NLS-1$ //$NON-NLS-2$
		buf.append(fCreateBindings ? " + bindings" : " + syntax"); //$NON-NLS-1$ //$NON-NLS-2$
		if (fDoUseReconciler) {
			buf.append(" + reconciler"); //$NON-NLS-1$
		}
		return buf.toString();
	}

	private void applyLabelOptions() {
		if (fLabelProvider != null) {
			fLabelProvider.setShowRanges(fShowRanges);
			fLabelProvider.setShowFlags(fShowFlags);
			fLabelProvider.setHighlightSelection(fHighlightSelection);
			fLabelProvider.setCompactLabels(fCompactLabels);
			fLabelProvider.setRangeMode(fRangeMode);
			fLabelProvider.setQualifiedNodeNames(fQualifiedNodeNames);
			fLabelProvider.setShowPropertyCount(fShowPropertyCount);
			fLabelProvider.setShowFlagHex(fShowFlagHex);
			fLabelProvider.setRoot(fRoot);
		}
	}

	private void applyViewOptions() {
		applyLabelOptions();
		if (fViewer != null) {
			styleTree(fViewer.getTree());
			if (fTray != null) {
				styleTree(fTray.getTree());
			}
			fViewer.refresh();
			applyAutoExpand();
		}
		redrawGraph();
		updateStatusBar(null);
		updateOptionControls();
	}

	private void applyLayoutOptions() {
		if (fWorkspace != null) {
			fWorkspace.setOrientation(fVerticalLayout ? SWT.VERTICAL : SWT.HORIZONTAL);
			if (fShowInspector) {
				fWorkspace.setMaximizedControl(null);
				fWorkspace.setWeights(fVerticalLayout ? new int[] { 68, 32 } : new int[] { 74, 26 });
			} else if (fAstPane != null) {
				fWorkspace.setMaximizedControl(fAstPane);
			}
		}
		applyTrayVisibility();
		applyDisplayChrome();
		updateOptionControls();
	}

	private void applyDisplayChrome() {
		setControlVisible(fHeader, fShowHeader);
		setControlVisible(fStatusBar, fShowStatusBar);
		if (fParent != null && !fParent.isDisposed()) {
			fParent.layout(true, true);
		}
		if (fAstPane != null && !fAstPane.isDisposed()) {
			fAstPane.layout(true, true);
		}
	}

	private void setControlVisible(Control control, boolean visible) {
		if (control == null || control.isDisposed()) {
			return;
		}
		control.setVisible(visible);
		Object data= control.getLayoutData();
		if (data instanceof GridData) {
			((GridData) data).exclude= !visible;
		}
	}

	private void applyTrayVisibility() {
		if (fSash == null || fViewer == null) {
			return;
		}
		if (fGraphForm != null && !fGraphForm.isDisposed()) {
			fGraphForm.setVisible(fShowGraph);
		}
		if (fTrayForm != null && !fTrayForm.isDisposed()) {
			fTrayForm.setVisible(fShowTray);
		}
		if (!fShowGraph && !fShowTray) {
			fSash.setMaximizedControl(fViewer.getTree());
		} else {
			fSash.setMaximizedControl(null);
			fSash.setWeights(fShowGraph ? new int[] { 50, 50 } : new int[] { 68, 32 });
		}
		if (fGraphStack != null && !fGraphStack.isDisposed()) {
			if (fShowGraph && fShowTray) {
				fGraphStack.setMaximizedControl(null);
				fGraphStack.setWeights(new int[] { 78, 22 });
			} else if (fShowGraph) {
				fGraphStack.setMaximizedControl(fGraphForm);
			} else if (fShowTray) {
				fGraphStack.setMaximizedControl(fTrayForm);
			}
		}
		redrawGraph();
	}

	private void applyAutoExpand() {
		if (fViewer == null) {
			return;
		}
		if (fSearchPattern.length() > 0 && fAutoExpandLevel == 0) {
			fViewer.expandToLevel(2);
		} else if (fAutoExpandLevel == -1) {
			fViewer.expandAll();
		} else if (fAutoExpandLevel > 0) {
			fViewer.expandToLevel(fAutoExpandLevel);
		}
	}

	private void updateOptionControls() {
		updateToggle(fInspectorButton, fShowInspector);
		updateToggle(fTrayButton, fShowTray);
		updateToggle(fLayoutButton, fVerticalLayout);
		updateToggle(fCompactButton, fCompactLabels);
		updateToggle(fDenseButton, fDenseMode);
		updateToggle(fLinesButton, fShowTreeLines);
		updateToggle(fPreviewButton, fShowSourcePreview);
		updateToggle(fPinButton, fPinInspector);
		updateToggle(fMalformedButton, fOnlyMalformed);
		updateToggle(fQualifiedButton, fQualifiedNodeNames);
		updateToggle(fPropsButton, fShowPropertyCount);
		updateToggle(fHexButton, fShowFlagHex);
		updateToggle(fCaseButton, fSearchCaseSensitive);
		updateToggle(fRegexButton, fSearchRegex);
		updateToggle(fLeafButton, fHideLeafAttributes);
		updateToggle(fSubtreeButton, fFocusSubtree);
		updateToggle(fSenseButton, fIntelliSense);
		updateToggle(fAutoRevealButton, fIntelliSenseAutoReveal);
		updateToggle(fGraphButton, fShowGraph);
		updateToggle(fGraphSyncButton, fGraphSyncSelection);
		updateToggle(fGraphCompactButton, fGraphCompact);
		updateToggle(fGraphOverviewButton, fGraphOverview);
		updateToggle(fGraphHeatmapButton, fGraphHeatmap);
		if (fExpandCombo != null && !fExpandCombo.isDisposed()) {
			fExpandCombo.select(getAutoExpandComboIndex());
		}
		if (fElementFilterCombo != null && !fElementFilterCombo.isDisposed()) {
			fElementFilterCombo.select(fElementFilter);
		}
		if (fThemeCombo != null && !fThemeCombo.isDisposed()) {
			fThemeCombo.select(fVisualTheme);
		}
		if (fInspectorDetailCombo != null && !fInspectorDetailCombo.isDisposed()) {
			fInspectorDetailCombo.select(fInspectorDetail);
		}
		if (fRangeModeCombo != null && !fRangeModeCombo.isDisposed()) {
			fRangeModeCombo.select(fRangeMode);
		}
		if (fSourceContextCombo != null && !fSourceContextCombo.isDisposed()) {
			fSourceContextCombo.select(getSourceContextComboIndex());
		}
		if (fPresetCombo != null && !fPresetCombo.isDisposed()) {
			fPresetCombo.select(fViewPreset);
		}
		if (fSearchScopeCombo != null && !fSearchScopeCombo.isDisposed()) {
			fSearchScopeCombo.select(fSearchScope);
		}
		if (fMaxDepthCombo != null && !fMaxDepthCombo.isDisposed()) {
			fMaxDepthCombo.select(getMaxDepthComboIndex());
		}
		if (fIntelliSenseModeCombo != null && !fIntelliSenseModeCombo.isDisposed()) {
			fIntelliSenseModeCombo.select(fIntelliSenseMode);
		}
		if (fGraphDepthCombo != null && !fGraphDepthCombo.isDisposed()) {
			fGraphDepthCombo.select(getGraphDepthComboIndex());
		}
		if (fGraphOrientationCombo != null && !fGraphOrientationCombo.isDisposed()) {
			fGraphOrientationCombo.select(fGraphOrientation);
		}
		if (fGraphZoomCombo != null && !fGraphZoomCombo.isDisposed()) {
			fGraphZoomCombo.select(getGraphZoomComboIndex());
		}
		if (fToggleInspectorAction != null) {
			fToggleInspectorAction.setChecked(fShowInspector);
			fToggleTrayAction.setChecked(fShowTray);
			fToggleLayoutAction.setChecked(fVerticalLayout);
			fShowRangesAction.setChecked(fShowRanges);
			fShowFlagsAction.setChecked(fShowFlags);
			fHighlightSelectionAction.setChecked(fHighlightSelection);
			fCompactLabelsAction.setChecked(fCompactLabels);
			fOnlyUnresolvedBindingsAction.setChecked(fOnlyUnresolvedBindings);
			fShowHeaderAction.setChecked(fShowHeader);
			fShowStatusBarAction.setChecked(fShowStatusBar);
			fShowTreeLinesAction.setChecked(fShowTreeLines);
			fDenseModeAction.setChecked(fDenseMode);
			fShowSourcePreviewAction.setChecked(fShowSourcePreview);
			fShowBreadcrumbAction.setChecked(fShowBreadcrumb);
			fPinInspectorAction.setChecked(fPinInspector);
			fOnlyMalformedAction.setChecked(fOnlyMalformed);
			fQualifiedNodeNamesAction.setChecked(fQualifiedNodeNames);
			fShowPropertyCountAction.setChecked(fShowPropertyCount);
			fShowFlagHexAction.setChecked(fShowFlagHex);
			fSearchCaseSensitiveAction.setChecked(fSearchCaseSensitive);
			fSearchRegexAction.setChecked(fSearchRegex);
			fHideLeafAttributesAction.setChecked(fHideLeafAttributes);
			fFocusSubtreeAction.setChecked(fFocusSubtree);
			fIntelliSenseAction.setChecked(fIntelliSense);
			fIntelliSenseAutoRevealAction.setChecked(fIntelliSenseAutoReveal);
			fShowGraphAction.setChecked(fShowGraph);
			fGraphSyncSelectionAction.setChecked(fGraphSyncSelection);
			fGraphCompactAction.setChecked(fGraphCompact);
			fGraphOverviewAction.setChecked(fGraphOverview);
			fGraphHeatmapAction.setChecked(fGraphHeatmap);
		}
		if (fAutoExpandActions != null) {
			for (int i= 0; i < fAutoExpandActions.length; i++) {
				fAutoExpandActions[i].setChecked(getAutoExpandLevelForAction(i) == fAutoExpandLevel);
			}
		}
		if (fThemeActions != null) {
			for (int i= 0; i < fThemeActions.length; i++) {
				fThemeActions[i].setChecked(i == fVisualTheme);
			}
		}
		if (fElementFilterActions != null) {
			for (int i= 0; i < fElementFilterActions.length; i++) {
				fElementFilterActions[i].setChecked(i == fElementFilter);
			}
		}
		if (fInspectorDetailActions != null) {
			for (int i= 0; i < fInspectorDetailActions.length; i++) {
				fInspectorDetailActions[i].setChecked(i == fInspectorDetail);
			}
		}
		if (fRangeModeActions != null) {
			for (int i= 0; i < fRangeModeActions.length; i++) {
				fRangeModeActions[i].setChecked(i == fRangeMode);
			}
		}
		if (fViewPresetActions != null) {
			for (int i= 0; i < fViewPresetActions.length; i++) {
				fViewPresetActions[i].setChecked(i == fViewPreset);
			}
		}
		if (fSearchScopeActions != null) {
			for (int i= 0; i < fSearchScopeActions.length; i++) {
				fSearchScopeActions[i].setChecked(i == fSearchScope);
			}
		}
		if (fMaxDepthActions != null) {
			for (int i= 0; i < fMaxDepthActions.length; i++) {
				fMaxDepthActions[i].setChecked(getMaxDepthForAction(i) == fMaxDepth);
			}
		}
		if (fIntelliSenseModeActions != null) {
			for (int i= 0; i < fIntelliSenseModeActions.length; i++) {
				fIntelliSenseModeActions[i].setChecked(i == fIntelliSenseMode);
			}
		}
		if (fGraphDepthActions != null) {
			for (int i= 0; i < fGraphDepthActions.length; i++) {
				fGraphDepthActions[i].setChecked(getGraphDepthForAction(i) == fGraphDepth);
			}
		}
		if (fGraphOrientationActions != null) {
			for (int i= 0; i < fGraphOrientationActions.length; i++) {
				fGraphOrientationActions[i].setChecked(i == fGraphOrientation);
			}
		}
		if (fGraphZoomActions != null) {
			for (int i= 0; i < fGraphZoomActions.length; i++) {
				fGraphZoomActions[i].setChecked(getGraphZoomForAction(i) == fGraphZoom);
			}
		}
	}

	private void updateToggle(Button button, boolean selected) {
		if (button == null || button.isDisposed()) {
			return;
		}
		button.setSelection(selected);
		button.setBackground(selected ? fAccentSoft : fPanel);
		button.setForeground(selected ? fAccent : fText);
	}

	private int getAutoExpandComboIndex() {
		if (fAutoExpandLevel == -1) {
			return 4;
		}
		if (fAutoExpandLevel >= 1 && fAutoExpandLevel <= 3) {
			return fAutoExpandLevel;
		}
		return 0;
	}

	private int getAutoExpandLevelFromCombo() {
		if (fExpandCombo == null) {
			return 0;
		}
		int index= fExpandCombo.getSelectionIndex();
		if (index == 4) {
			return -1;
		}
		if (index >= 1 && index <= 3) {
			return index;
		}
		return 0;
	}

	private int getAutoExpandLevelForAction(int index) {
		if (index == 4) {
			return -1;
		}
		if (index >= 1 && index <= 3) {
			return index;
		}
		return 0;
	}

	private int getSourceContextComboIndex() {
		if (fSourceContextLines == 10) {
			return 3;
		}
		if (fSourceContextLines == 5) {
			return 2;
		}
		if (fSourceContextLines == 2) {
			return 1;
		}
		return 0;
	}

	private int getSourceContextFromCombo() {
		if (fSourceContextCombo == null) {
			return fSourceContextLines;
		}
		int index= fSourceContextCombo.getSelectionIndex();
		if (index == 3) {
			return 10;
		}
		if (index == 2) {
			return 5;
		}
		if (index == 1) {
			return 2;
		}
		return 0;
	}

	private int getMaxDepthComboIndex() {
		if (fMaxDepth == 12) {
			return 4;
		}
		if (fMaxDepth == 8) {
			return 3;
		}
		if (fMaxDepth == 5) {
			return 2;
		}
		if (fMaxDepth == 3) {
			return 1;
		}
		return 0;
	}

	private int getMaxDepthFromCombo() {
		return getMaxDepthForAction(fMaxDepthCombo == null ? 0 : fMaxDepthCombo.getSelectionIndex());
	}

	private int getMaxDepthForAction(int index) {
		if (index == 4) {
			return 12;
		}
		if (index == 3) {
			return 8;
		}
		if (index == 2) {
			return 5;
		}
		if (index == 1) {
			return 3;
		}
		return 0;
	}

	private int getGraphDepthComboIndex() {
		if (fGraphDepth == 6) {
			return 3;
		}
		if (fGraphDepth == 4) {
			return 2;
		}
		if (fGraphDepth == 3) {
			return 1;
		}
		return 0;
	}

	private int getGraphDepthFromCombo() {
		return getGraphDepthForAction(fGraphDepthCombo == null ? 1 : fGraphDepthCombo.getSelectionIndex());
	}

	private int getGraphDepthForAction(int index) {
		if (index == 3) {
			return 6;
		}
		if (index == 2) {
			return 4;
		}
		if (index == 1) {
			return 3;
		}
		return 2;
	}

	private int getGraphZoomComboIndex() {
		if (fGraphZoom == 150) {
			return 3;
		}
		if (fGraphZoom == 125) {
			return 2;
		}
		if (fGraphZoom == 100) {
			return 1;
		}
		return 0;
	}

	private int getGraphZoomFromCombo() {
		return getGraphZoomForAction(fGraphZoomCombo == null ? 1 : fGraphZoomCombo.getSelectionIndex());
	}

	private int getGraphZoomForAction(int index) {
		if (index == 3) {
			return 150;
		}
		if (index == 2) {
			return 125;
		}
		if (index == 1) {
			return 100;
		}
		return 75;
	}

	private void redrawGraph() {
		if (fGraphCanvas != null && !fGraphCanvas.isDisposed()) {
			fGraphCanvas.redraw();
		}
	}

	private void markCustomPreset() {
		fViewPreset= PRESET_CUSTOM;
		fDialogSettings.put(SETTINGS_VIEW_PRESET, fViewPreset);
	}

	private void setAutoExpandLevel(int level) {
		fAutoExpandLevel= level;
		fDialogSettings.put(SETTINGS_AUTO_EXPAND, fAutoExpandLevel);
		applyAutoExpand();
		updateOptionControls();
	}

	private void setShowInspector(boolean showInspector) {
		fShowInspector= showInspector;
		fDialogSettings.put(SETTINGS_SHOW_INSPECTOR, fShowInspector);
		applyLayoutOptions();
	}

	private void setShowTray(boolean showTray) {
		fShowTray= showTray;
		fDialogSettings.put(SETTINGS_SHOW_TRAY, fShowTray);
		applyLayoutOptions();
	}

	private void setVerticalLayout(boolean verticalLayout) {
		fVerticalLayout= verticalLayout;
		fDialogSettings.put(SETTINGS_VERTICAL_LAYOUT, fVerticalLayout);
		applyLayoutOptions();
	}

	private void setShowRanges(boolean showRanges) {
		fShowRanges= showRanges;
		fRangeMode= showRanges ? ASTViewLabelProvider.RANGE_OFFSET : ASTViewLabelProvider.RANGE_NONE;
		fDialogSettings.put(SETTINGS_SHOW_RANGES, fShowRanges);
		fDialogSettings.put(SETTINGS_RANGE_MODE, fRangeMode);
		markCustomPreset();
		applyViewOptions();
	}

	private void setShowFlags(boolean showFlags) {
		fShowFlags= showFlags;
		fDialogSettings.put(SETTINGS_SHOW_FLAGS, fShowFlags);
		markCustomPreset();
		applyViewOptions();
	}

	private void setHighlightSelection(boolean highlightSelection) {
		fHighlightSelection= highlightSelection;
		fDialogSettings.put(SETTINGS_HIGHLIGHT_SELECTION, fHighlightSelection);
		applyViewOptions();
	}

	private void setCompactLabels(boolean compactLabels) {
		fCompactLabels= compactLabels;
		fDialogSettings.put(SETTINGS_COMPACT_LABELS, fCompactLabels);
		markCustomPreset();
		applyViewOptions();
	}

	private void setOnlyUnresolvedBindings(boolean onlyUnresolvedBindings) {
		fOnlyUnresolvedBindings= onlyUnresolvedBindings;
		fDialogSettings.put(SETTINGS_ONLY_UNRESOLVED_BINDINGS, fOnlyUnresolvedBindings);
		if (fViewer != null) {
			fViewer.refresh();
			applyAutoExpand();
		}
		updateStatusBar(null);
		updateOptionControls();
	}

	private void setShowHeader(boolean showHeader) {
		fShowHeader= showHeader;
		fDialogSettings.put(SETTINGS_SHOW_HEADER, fShowHeader);
		applyDisplayChrome();
		updateOptionControls();
	}

	private void setShowStatusBar(boolean showStatusBar) {
		fShowStatusBar= showStatusBar;
		fDialogSettings.put(SETTINGS_SHOW_STATUS_BAR, fShowStatusBar);
		applyDisplayChrome();
		updateOptionControls();
	}

	private void setShowTreeLines(boolean showTreeLines) {
		fShowTreeLines= showTreeLines;
		fDialogSettings.put(SETTINGS_SHOW_TREE_LINES, fShowTreeLines);
		applyViewOptions();
	}

	private void setDenseMode(boolean denseMode) {
		fDenseMode= denseMode;
		fDialogSettings.put(SETTINGS_DENSE_MODE, fDenseMode);
		applyViewOptions();
	}

	private void setVisualTheme(int visualTheme) {
		if (visualTheme < THEME_LIGHT || visualTheme > THEME_CONTRAST) {
			visualTheme= THEME_LIGHT;
		}
		fVisualTheme= visualTheme;
		fDialogSettings.put(SETTINGS_VISUAL_THEME, fVisualTheme);
		disposeVisualColors();
		createThemeColors(fParent);
		applyTheme(fParent);
		applyViewOptions();
	}

	private void setElementFilter(int elementFilter) {
		if (elementFilter < FILTER_ALL || elementFilter > FILTER_PROBLEMS) {
			elementFilter= FILTER_ALL;
		}
		fElementFilter= elementFilter;
		fDialogSettings.put(SETTINGS_ELEMENT_FILTER, fElementFilter);
		if (fViewer != null) {
			fViewer.refresh();
			applyAutoExpand();
		}
		updateStatusBar(null);
		updateOptionControls();
	}

	private void setInspectorDetail(int inspectorDetail) {
		if (inspectorDetail < INSPECTOR_SUMMARY || inspectorDetail > INSPECTOR_SOURCE) {
			inspectorDetail= INSPECTOR_TECHNICAL;
		}
		fInspectorDetail= inspectorDetail;
		fDialogSettings.put(SETTINGS_INSPECTOR_DETAIL, fInspectorDetail);
		markCustomPreset();
		updateInspector(fViewer == null ? StructuredSelection.EMPTY : fViewer.getSelection());
		updateOptionControls();
	}

	private void setShowSourcePreview(boolean showSourcePreview) {
		fShowSourcePreview= showSourcePreview;
		fDialogSettings.put(SETTINGS_SHOW_SOURCE_PREVIEW, fShowSourcePreview);
		markCustomPreset();
		updateInspector(fViewer == null ? StructuredSelection.EMPTY : fViewer.getSelection());
		updateOptionControls();
	}

	private void setShowBreadcrumb(boolean showBreadcrumb) {
		fShowBreadcrumb= showBreadcrumb;
		fDialogSettings.put(SETTINGS_SHOW_BREADCRUMB, fShowBreadcrumb);
		markCustomPreset();
		updateInspector(fViewer == null ? StructuredSelection.EMPTY : fViewer.getSelection());
		updateOptionControls();
	}

	private void setPinInspector(boolean pinInspector) {
		fPinInspector= pinInspector;
		fDialogSettings.put(SETTINGS_PIN_INSPECTOR, fPinInspector);
		updateOptionControls();
	}

	private void setOnlyMalformed(boolean onlyMalformed) {
		fOnlyMalformed= onlyMalformed;
		fDialogSettings.put(SETTINGS_ONLY_MALFORMED, fOnlyMalformed);
		markCustomPreset();
		if (fViewer != null) {
			fViewer.refresh();
			applyAutoExpand();
		}
		updateStatusBar(null);
		updateOptionControls();
	}

	private void setRangeMode(int rangeMode) {
		if (rangeMode < ASTViewLabelProvider.RANGE_NONE || rangeMode > ASTViewLabelProvider.RANGE_BOTH) {
			rangeMode= ASTViewLabelProvider.RANGE_OFFSET;
		}
		fRangeMode= rangeMode;
		fShowRanges= rangeMode != ASTViewLabelProvider.RANGE_NONE;
		fDialogSettings.put(SETTINGS_RANGE_MODE, fRangeMode);
		fDialogSettings.put(SETTINGS_SHOW_RANGES, fShowRanges);
		markCustomPreset();
		applyViewOptions();
	}

	private void setQualifiedNodeNames(boolean qualifiedNodeNames) {
		fQualifiedNodeNames= qualifiedNodeNames;
		fDialogSettings.put(SETTINGS_QUALIFIED_NODE_NAMES, fQualifiedNodeNames);
		markCustomPreset();
		applyViewOptions();
	}

	private void setShowPropertyCount(boolean showPropertyCount) {
		fShowPropertyCount= showPropertyCount;
		fDialogSettings.put(SETTINGS_SHOW_PROPERTY_COUNT, fShowPropertyCount);
		markCustomPreset();
		applyViewOptions();
	}

	private void setShowFlagHex(boolean showFlagHex) {
		fShowFlagHex= showFlagHex;
		fDialogSettings.put(SETTINGS_SHOW_FLAG_HEX, fShowFlagHex);
		markCustomPreset();
		applyViewOptions();
	}

	private void setSourceContextLines(int sourceContextLines) {
		if (sourceContextLines < 0) {
			sourceContextLines= 0;
		}
		fSourceContextLines= sourceContextLines;
		fDialogSettings.put(SETTINGS_SOURCE_CONTEXT, fSourceContextLines);
		markCustomPreset();
		updateInspector(fViewer == null ? StructuredSelection.EMPTY : fViewer.getSelection());
		updateOptionControls();
	}

	private void setSearchCaseSensitive(boolean searchCaseSensitive) {
		fSearchCaseSensitive= searchCaseSensitive;
		fDialogSettings.put(SETTINGS_SEARCH_CASE_SENSITIVE, fSearchCaseSensitive);
		markCustomPreset();
		applyViewOptions();
	}

	private void setSearchRegex(boolean searchRegex) {
		fSearchRegex= searchRegex;
		fDialogSettings.put(SETTINGS_SEARCH_REGEX, fSearchRegex);
		markCustomPreset();
		applyViewOptions();
	}

	private void setSearchScope(int searchScope) {
		if (searchScope < SEARCH_LABEL || searchScope > SEARCH_BOTH) {
			searchScope= SEARCH_BOTH;
		}
		fSearchScope= searchScope;
		fDialogSettings.put(SETTINGS_SEARCH_SCOPE, fSearchScope);
		markCustomPreset();
		applyViewOptions();
	}

	private void setMaxDepth(int maxDepth) {
		if (maxDepth < 0) {
			maxDepth= 0;
		}
		fMaxDepth= maxDepth;
		fDialogSettings.put(SETTINGS_MAX_DEPTH, fMaxDepth);
		markCustomPreset();
		applyViewOptions();
	}

	private void setHideLeafAttributes(boolean hideLeafAttributes) {
		fHideLeafAttributes= hideLeafAttributes;
		fDialogSettings.put(SETTINGS_HIDE_LEAF_ATTRIBUTES, fHideLeafAttributes);
		markCustomPreset();
		applyViewOptions();
	}

	private void setFocusSubtree(boolean focusSubtree) {
		fFocusSubtree= focusSubtree;
		fDialogSettings.put(SETTINGS_FOCUS_SUBTREE, fFocusSubtree);
		if (fFocusSubtree && fViewer != null) {
			fSubtreeRoot= getASTNodeNearSelection((IStructuredSelection) fViewer.getSelection());
			if (fSubtreeRoot == null) {
				fSubtreeRoot= fRoot;
			}
		} else {
			fSubtreeRoot= null;
		}
		markCustomPreset();
		applyViewOptions();
	}

	private void setIntelliSense(boolean intelliSense) {
		fIntelliSense= intelliSense;
		fDialogSettings.put(SETTINGS_INTELLISENSE, fIntelliSense);
		if (fSearchProposalAdapter != null) {
			fSearchProposalAdapter.setEnabled(fIntelliSense);
		}
		updateOptionControls();
	}

	private void setIntelliSenseAutoReveal(boolean autoReveal) {
		fIntelliSenseAutoReveal= autoReveal;
		fDialogSettings.put(SETTINGS_INTELLISENSE_AUTO_REVEAL, fIntelliSenseAutoReveal);
		if (fIntelliSenseAutoReveal) {
			revealFirstSearchMatch();
		}
		updateOptionControls();
	}

	private void setIntelliSenseMode(int mode) {
		if (mode < SENSE_ALL || mode > SENSE_BINDINGS) {
			mode= SENSE_ALL;
		}
		fIntelliSenseMode= mode;
		fDialogSettings.put(SETTINGS_INTELLISENSE_MODE, fIntelliSenseMode);
		if (fSearchProposalAdapter != null) {
			fSearchProposalAdapter.refresh();
		}
		updateOptionControls();
	}

	private void setShowGraph(boolean showGraph) {
		fShowGraph= showGraph;
		fDialogSettings.put(SETTINGS_SHOW_GRAPH, fShowGraph);
		applyTrayVisibility();
		updateOptionControls();
	}

	private void setGraphDepth(int graphDepth) {
		if (graphDepth < 1) {
			graphDepth= 3;
		}
		fGraphDepth= graphDepth;
		fDialogSettings.put(SETTINGS_GRAPH_DEPTH, fGraphDepth);
		redrawGraph();
		updateOptionControls();
	}

	private void setGraphOrientation(int graphOrientation) {
		if (graphOrientation < GRAPH_VERTICAL || graphOrientation > GRAPH_HORIZONTAL) {
			graphOrientation= GRAPH_VERTICAL;
		}
		fGraphOrientation= graphOrientation;
		fDialogSettings.put(SETTINGS_GRAPH_ORIENTATION, fGraphOrientation);
		redrawGraph();
		updateOptionControls();
	}

	private void setGraphSyncSelection(boolean graphSyncSelection) {
		fGraphSyncSelection= graphSyncSelection;
		fDialogSettings.put(SETTINGS_GRAPH_SYNC_SELECTION, fGraphSyncSelection);
		redrawGraph();
		updateOptionControls();
	}

	private void setGraphCompact(boolean graphCompact) {
		fGraphCompact= graphCompact;
		fDialogSettings.put(SETTINGS_GRAPH_COMPACT, fGraphCompact);
		redrawGraph();
		updateOptionControls();
	}

	private void setGraphZoom(int graphZoom) {
		if (graphZoom < 75 || graphZoom > 150) {
			graphZoom= 100;
		}
		fGraphZoom= graphZoom;
		fDialogSettings.put(SETTINGS_GRAPH_ZOOM, fGraphZoom);
		redrawGraph();
		updateOptionControls();
	}

	private void setGraphOverview(boolean graphOverview) {
		fGraphOverview= graphOverview;
		fDialogSettings.put(SETTINGS_GRAPH_OVERVIEW, fGraphOverview);
		redrawGraph();
		updateOptionControls();
	}

	private void setGraphHeatmap(boolean graphHeatmap) {
		fGraphHeatmap= graphHeatmap;
		fDialogSettings.put(SETTINGS_GRAPH_HEATMAP, fGraphHeatmap);
		redrawGraph();
		updateOptionControls();
	}

	private void applyViewPreset(int preset) {
		if (preset < PRESET_CUSTOM || preset > PRESET_SOURCE) {
			preset= PRESET_CUSTOM;
		}
		fViewPreset= preset;
		fDialogSettings.put(SETTINGS_VIEW_PRESET, fViewPreset);
		if (preset == PRESET_REVIEW) {
			fElementFilter= FILTER_AST_NODES;
			fInspectorDetail= INSPECTOR_SUMMARY;
			fRangeMode= ASTViewLabelProvider.RANGE_LINE;
			fCompactLabels= true;
			fShowPropertyCount= false;
			fOnlyMalformed= false;
			fOnlyUnresolvedBindings= false;
			fAutoExpandLevel= 2;
		} else if (preset == PRESET_DIAGNOSTICS) {
			fElementFilter= FILTER_PROBLEMS;
			fInspectorDetail= INSPECTOR_TECHNICAL;
			fRangeMode= ASTViewLabelProvider.RANGE_BOTH;
			fShowFlags= true;
			fShowFlagHex= true;
			fShowPropertyCount= true;
			fOnlyMalformed= true;
			fShowTreeLines= true;
			fAutoExpandLevel= -1;
		} else if (preset == PRESET_BINDINGS) {
			fElementFilter= FILTER_BINDINGS;
			fInspectorDetail= INSPECTOR_TECHNICAL;
			fRangeMode= ASTViewLabelProvider.RANGE_OFFSET;
			fShowFlags= false;
			fShowPropertyCount= true;
			fOnlyUnresolvedBindings= false;
			fOnlyMalformed= false;
			fAutoExpandLevel= 3;
		} else if (preset == PRESET_SOURCE) {
			fElementFilter= FILTER_ALL;
			fInspectorDetail= INSPECTOR_SOURCE;
			fRangeMode= ASTViewLabelProvider.RANGE_BOTH;
			fShowSourcePreview= true;
			fShowBreadcrumb= true;
			fSourceContextLines= 5;
			fAutoExpandLevel= 2;
		}
		persistViewPresetOptions();
		applyViewOptions();
		applyLayoutOptions();
		updateInspector(fViewer == null ? StructuredSelection.EMPTY : fViewer.getSelection());
	}

	private void persistViewPresetOptions() {
		fShowRanges= fRangeMode != ASTViewLabelProvider.RANGE_NONE;
		fDialogSettings.put(SETTINGS_ELEMENT_FILTER, fElementFilter);
		fDialogSettings.put(SETTINGS_INSPECTOR_DETAIL, fInspectorDetail);
		fDialogSettings.put(SETTINGS_RANGE_MODE, fRangeMode);
		fDialogSettings.put(SETTINGS_SHOW_RANGES, fShowRanges);
		fDialogSettings.put(SETTINGS_COMPACT_LABELS, fCompactLabels);
		fDialogSettings.put(SETTINGS_SHOW_PROPERTY_COUNT, fShowPropertyCount);
		fDialogSettings.put(SETTINGS_ONLY_MALFORMED, fOnlyMalformed);
		fDialogSettings.put(SETTINGS_ONLY_UNRESOLVED_BINDINGS, fOnlyUnresolvedBindings);
		fDialogSettings.put(SETTINGS_AUTO_EXPAND, fAutoExpandLevel);
		fDialogSettings.put(SETTINGS_SHOW_FLAGS, fShowFlags);
		fDialogSettings.put(SETTINGS_SHOW_FLAG_HEX, fShowFlagHex);
		fDialogSettings.put(SETTINGS_SHOW_TREE_LINES, fShowTreeLines);
		fDialogSettings.put(SETTINGS_SHOW_SOURCE_PREVIEW, fShowSourcePreview);
		fDialogSettings.put(SETTINGS_SHOW_BREADCRUMB, fShowBreadcrumb);
		fDialogSettings.put(SETTINGS_SOURCE_CONTEXT, fSourceContextLines);
	}

	private void applyTheme(Control control) {
		if (control == null || control.isDisposed()) {
			return;
		}
		if (control == fHeader || control.getParent() == fHeader) {
			control.setBackground(fPanelDeep);
			control.setForeground(control == fHeroTitle ? fPanel : fBorder);
		} else if (control == fGraphCanvas) {
			control.setBackground(fDiagramCanvas);
			control.setForeground(fText);
		} else if (control instanceof Tree) {
			styleTree((Tree) control);
		} else if (control instanceof Button) {
			boolean selected= ((Button) control).getSelection();
			control.setBackground(selected ? fAccentSoft : fPanel);
			control.setForeground(selected ? fAccent : fText);
		} else {
			control.setBackground(fPanel);
			control.setForeground(fText);
		}
		if (control == fParent || control == fAstPane || control == fWorkspace || control == fSash || control == fGraphStack) {
			control.setBackground(fShell);
		}
		if (control instanceof Composite) {
			Control[] children= ((Composite) control).getChildren();
			for (int i= 0; i < children.length; i++) {
				applyTheme(children[i]);
			}
		}
	}

	private boolean matchesElementFilter(Object element) {
		if (fElementFilter == FILTER_AST_NODES) {
			return element instanceof ASTNode;
		}
		if (fElementFilter == FILTER_PROPERTIES) {
			return element instanceof ASTAttribute && !(element instanceof Binding) && !(element instanceof ProblemNode);
		}
		if (fElementFilter == FILTER_BINDINGS) {
			return element instanceof Binding;
		}
		if (fElementFilter == FILTER_PROBLEMS) {
			return element instanceof ProblemNode || element instanceof ProblemsProperty;
		}
		return true;
	}

	private boolean hasElementFilterDescendant(Object element) {
		if (fViewer == null || fViewer.getContentProvider() == null) {
			return false;
		}
		try {
			Object[] children= ((ASTViewContentProvider) fViewer.getContentProvider()).getChildren(element);
			for (int i= 0; i < children.length; i++) {
				if (matchesElementFilter(children[i]) || hasElementFilterDescendant(children[i])) {
					return true;
				}
			}
		} catch (RuntimeException e) {
			return false;
		}
		return false;
	}

	private boolean isMalformedElement(Object element) {
		ASTNode node= null;
		if (element instanceof ASTNode) {
			node= (ASTNode) element;
		} else if (element instanceof ASTAttribute) {
			node= ((ASTAttribute) element).getParentASTNode();
		}
		return node != null && (node.getFlags() & ASTNode.MALFORMED) != 0;
	}

	private boolean hasMalformedDescendant(Object element) {
		if (fViewer == null || fViewer.getContentProvider() == null) {
			return false;
		}
		try {
			Object[] children= ((ASTViewContentProvider) fViewer.getContentProvider()).getChildren(element);
			for (int i= 0; i < children.length; i++) {
				if (isMalformedElement(children[i]) || hasMalformedDescendant(children[i])) {
					return true;
				}
			}
		} catch (RuntimeException e) {
			return false;
		}
		return false;
	}

	private String getElementFilterLabel() {
		if (fElementFilter == FILTER_AST_NODES) {
			return "AST nodes"; //$NON-NLS-1$
		}
		if (fElementFilter == FILTER_PROPERTIES) {
			return "properties"; //$NON-NLS-1$
		}
		if (fElementFilter == FILTER_BINDINGS) {
			return "bindings"; //$NON-NLS-1$
		}
		if (fElementFilter == FILTER_PROBLEMS) {
			return "problems"; //$NON-NLS-1$
		}
		return "all elements"; //$NON-NLS-1$
	}

	private void updateStatusBar(ISelection selection) {
		if (fStatusFilter != null && !fStatusFilter.isDisposed()) {
			String suffix= fOnlyUnresolvedBindings ? " + unresolved only" : ""; //$NON-NLS-1$ //$NON-NLS-2$
			if (fOnlyMalformed) {
				suffix+= " + malformed only"; //$NON-NLS-1$
			}
			if (fSearchPattern.length() > 0) {
				suffix+= " + " + countSearchMatches(fRoot, 0, 1000) + " matches"; //$NON-NLS-1$ //$NON-NLS-2$
			}
			fStatusFilter.setText("View: " + getElementFilterLabel() + suffix); //$NON-NLS-1$
		}
		if (fStatusTray != null && !fStatusTray.isDisposed()) {
			int trayCount= fTrayRoots == null ? 0 : fTrayRoots.size();
			fStatusTray.setText("Tray: " + trayCount + (trayCount == 1 ? " item" : " items")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
		}
		if (fStatusSelection != null && !fStatusSelection.isDisposed() && selection != null) {
			IStructuredSelection structured= (IStructuredSelection) selection;
			Object first= structured.getFirstElement();
			if (first == null) {
				fStatusSelection.setText("Selection: --"); //$NON-NLS-1$
			} else {
				ASTNode node= getASTNodeNearSelection(structured);
				if (node != null) {
					fStatusSelection.setText("Selection: " + node.getClass().getName() + " @" + node.getStartPosition()); //$NON-NLS-1$ //$NON-NLS-2$
				} else {
					fStatusSelection.setText("Selection: " + first.getClass().getName()); //$NON-NLS-1$
				}
			}
		}
	}

	private int countSearchMatches(Object element, int count, int limit) {
		if (element == null || count >= limit || fViewer == null || fViewer.getContentProvider() == null) {
			return count;
		}
		if (matchesSearch(element)) {
			count++;
		}
		Object[] children= ((ASTViewContentProvider) fViewer.getContentProvider()).getChildren(element);
		for (int i= 0; i < children.length && count < limit; i++) {
			count= countSearchMatches(children[i], count, limit);
		}
		return count;
	}

	private boolean hasUnresolvedBindingDescendant(Object element) {
		if (fViewer == null || fViewer.getContentProvider() == null) {
			return false;
		}
		try {
			Object[] children= ((ASTViewContentProvider) fViewer.getContentProvider()).getChildren(element);
			for (int i= 0; i < children.length; i++) {
				if (children[i] instanceof Binding && ((Binding) children[i]).getBinding() == null) {
					return true;
				}
				if (hasUnresolvedBindingDescendant(children[i])) {
					return true;
				}
			}
		} catch (RuntimeException e) {
			return false;
		}
		return false;
	}


	private void hookContextMenu() {
		MenuManager menuMgr = new MenuManager("#PopupMenu"); //$NON-NLS-1$
		menuMgr.setRemoveAllWhenShown(true);
		menuMgr.addMenuListener(new IMenuListener() {
			public void menuAboutToShow(IMenuManager manager) {
				ASTView.this.fillContextMenu(manager);
			}
		});
		Menu menu = menuMgr.createContextMenu(fViewer.getControl());
		fViewer.getControl().setMenu(menu);
		getSite().registerContextMenu(menuMgr, fViewer);
	}

	private void contributeToActionBars() {
		IActionBars bars = getViewSite().getActionBars();
		fillLocalPullDown(bars.getMenuManager());
		fillLocalToolBar(bars.getToolBarManager());
		bars.setGlobalActionHandler(ActionFactory.COPY.getId(), fCopyAction);
		bars.setGlobalActionHandler(ActionFactory.REFRESH.getId(), fFocusAction);
		bars.setGlobalActionHandler(ActionFactory.DELETE.getId(), fDeleteAction);
	}

	private void fillLocalPullDown(IMenuManager manager) {
		for (int i= 0; i < fASTVersionToggleActions.length; i++) {
			manager.add(fASTVersionToggleActions[i]);	
		}
		manager.add(new Separator());
		manager.add(fUseReconcilerAction);
		manager.add(fCreateBindingsAction);
		manager.add(new Separator());
		manager.add(fToggleInspectorAction);
		manager.add(fToggleTrayAction);
		manager.add(fToggleLayoutAction);
		manager.add(fShowHeaderAction);
		manager.add(fShowStatusBarAction);
		manager.add(fShowGraphAction);
		manager.add(fGraphSyncSelectionAction);
		manager.add(fGraphCompactAction);
		manager.add(fGraphOverviewAction);
		manager.add(fGraphHeatmapAction);
		manager.add(new Separator());
		for (int i= 0; i < fViewPresetActions.length; i++) {
			manager.add(fViewPresetActions[i]);
		}
		manager.add(new Separator());
		for (int i= 0; i < fAutoExpandActions.length; i++) {
			manager.add(fAutoExpandActions[i]);
		}
		for (int i= 0; i < fMaxDepthActions.length; i++) {
			manager.add(fMaxDepthActions[i]);
		}
		for (int i= 0; i < fGraphDepthActions.length; i++) {
			manager.add(fGraphDepthActions[i]);
		}
		for (int i= 0; i < fGraphOrientationActions.length; i++) {
			manager.add(fGraphOrientationActions[i]);
		}
		for (int i= 0; i < fGraphZoomActions.length; i++) {
			manager.add(fGraphZoomActions[i]);
		}
		manager.add(new Separator());
		for (int i= 0; i < fElementFilterActions.length; i++) {
			manager.add(fElementFilterActions[i]);
		}
		manager.add(new Separator());
		for (int i= 0; i < fInspectorDetailActions.length; i++) {
			manager.add(fInspectorDetailActions[i]);
		}
		manager.add(fShowSourcePreviewAction);
		manager.add(fShowBreadcrumbAction);
		manager.add(fPinInspectorAction);
		manager.add(new Separator());
		for (int i= 0; i < fRangeModeActions.length; i++) {
			manager.add(fRangeModeActions[i]);
		}
		for (int i= 0; i < fSearchScopeActions.length; i++) {
			manager.add(fSearchScopeActions[i]);
		}
		for (int i= 0; i < fIntelliSenseModeActions.length; i++) {
			manager.add(fIntelliSenseModeActions[i]);
		}
		for (int i= 0; i < fThemeActions.length; i++) {
			manager.add(fThemeActions[i]);
		}
		manager.add(new Separator());
		manager.add(fShowRangesAction);
		manager.add(fShowFlagsAction);
		manager.add(fShowFlagHexAction);
		manager.add(fHighlightSelectionAction);
		manager.add(fCompactLabelsAction);
		manager.add(fQualifiedNodeNamesAction);
		manager.add(fShowPropertyCountAction);
		manager.add(fOnlyUnresolvedBindingsAction);
		manager.add(fOnlyMalformedAction);
		manager.add(fSearchCaseSensitiveAction);
		manager.add(fSearchRegexAction);
		manager.add(fIntelliSenseAction);
		manager.add(fIntelliSenseAutoRevealAction);
		manager.add(fHideLeafAttributesAction);
		manager.add(fFocusSubtreeAction);
		manager.add(fDenseModeAction);
		manager.add(fShowTreeLinesAction);
		manager.add(new Separator());
		manager.add(fLinkWithEditor);
	}

	protected void fillContextMenu(IMenuManager manager) {
		manager.add(fFocusAction);
		manager.add(fRefreshAction);
		manager.add(fClearAction);
		manager.add(fCollapseAction);
		manager.add(fExpandAction);
		manager.add(new Separator());
		manager.add(fCopyAction);
		if (fAddToTrayAction.isEnabled())
			manager.add(fAddToTrayAction);
		manager.add(new Separator());

		fDrillDownAdapter.addNavigationActions(manager);
		// Other plug-ins can contribute there actions here
		manager.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS));
	}

	private void fillLocalToolBar(IToolBarManager manager) {
		manager.add(fFocusAction);
		manager.add(fRefreshAction);
		manager.add(fClearAction);
		manager.add(new Separator());
		fDrillDownAdapter.addNavigationActions(manager);
		manager.add(new Separator());
		manager.add(fExpandAction);
		manager.add(fCollapseAction);
		manager.add(fToggleInspectorAction);
		manager.add(fToggleTrayAction);
		manager.add(fLinkWithEditor);
	}
	
	private void setASTUptoDate(boolean isuptoDate) {
		fRefreshAction.setEnabled(!isuptoDate && fOpenable != null);
	}
	
	private void makeActions() {
		fRefreshAction = new Action() {
			public void run() {
				performRefresh();
			}
		};
		fRefreshAction.setText("&Refresh AST"); //$NON-NLS-1$
		fRefreshAction.setToolTipText("Refresh AST"); //$NON-NLS-1$
		fRefreshAction.setEnabled(false);
		ASTViewImages.setImageDescriptors(fRefreshAction, ASTViewImages.REFRESH);

		fClearAction = new Action() {
			public void run() {
				performClear();
			}
		};
		fClearAction.setText("&Clear AST"); //$NON-NLS-1$
		fClearAction.setToolTipText("Clear AST and release memory"); //$NON-NLS-1$
		fClearAction.setEnabled(false);
		ASTViewImages.setImageDescriptors(fClearAction, ASTViewImages.CLEAR);
		
		fUseReconcilerAction = new Action("&Use Reconciler", IAction.AS_CHECK_BOX) { //$NON-NLS-1$
			public void run() {
				performUseReconciler();
			}
		};
		fUseReconcilerAction.setChecked(fDoUseReconciler);
		fUseReconcilerAction.setToolTipText("Use Reconciler to create AST"); //$NON-NLS-1$
		fUseReconcilerAction.setEnabled(true);
		
		fCreateBindingsAction = new Action("&Create Bindings", IAction.AS_CHECK_BOX) { //$NON-NLS-1$
			public void run() {
				performCreateBindings();
			}
		};
		fCreateBindingsAction.setChecked(fCreateBindings);
		fCreateBindingsAction.setToolTipText("Create Bindings"); //$NON-NLS-1$
		fCreateBindingsAction.setEnabled(true);

		fFocusAction = new Action() {
			public void run() {
				performSetFocus();
			}
		};
		fFocusAction.setText("&Show AST of active editor"); //$NON-NLS-1$
		fFocusAction.setToolTipText("Show AST of active editor"); //$NON-NLS-1$
		fFocusAction.setActionDefinitionId("org.eclipse.ui.file.refresh"); //$NON-NLS-1$
		ASTViewImages.setImageDescriptors(fFocusAction, ASTViewImages.SETFOCUS);

		fCollapseAction = new Action() {
			public void run() {
				performCollapse();
			}
		};
		fCollapseAction.setText("C&ollapse"); //$NON-NLS-1$
		fCollapseAction.setToolTipText("Collapse Selected Node"); //$NON-NLS-1$
		fCollapseAction.setEnabled(false);
		ASTViewImages.setImageDescriptors(fCollapseAction, ASTViewImages.COLLAPSE);
		
		fExpandAction = new Action() {
			public void run() {
				performExpand();
			}
		};
		fExpandAction.setText("E&xpand"); //$NON-NLS-1$
		fExpandAction.setToolTipText("Expand Selected Node"); //$NON-NLS-1$
		fExpandAction.setEnabled(false);
		ASTViewImages.setImageDescriptors(fExpandAction, ASTViewImages.EXPAND);
		
		fCopyAction= new TreeCopyAction(new Tree[] {fViewer.getTree(), fTray.getTree()});
		
		fDoubleClickAction = new Action() {
			public void run() {
				performDoubleClick();
			}
		};
		
		fLinkWithEditor = new Action() {
			public void run() {
				performLinkWithEditor();
			}
		};
		fLinkWithEditor.setChecked(fDoLinkWithEditor);
		fLinkWithEditor.setText("&Link with Editor"); //$NON-NLS-1$
		fLinkWithEditor.setToolTipText("Link With Editor"); //$NON-NLS-1$
		ASTViewImages.setImageDescriptors(fLinkWithEditor, ASTViewImages.LINK_WITH_EDITOR);

		fToggleInspectorAction = new Action("&Show Inspector", IAction.AS_CHECK_BOX) { //$NON-NLS-1$
			public void run() {
				setShowInspector(isChecked());
			}
		};
		fToggleInspectorAction.setChecked(fShowInspector);
		fToggleInspectorAction.setToolTipText("Show or hide the selection inspector"); //$NON-NLS-1$
		
		fToggleTrayAction = new Action("Show Comparison &Tray", IAction.AS_CHECK_BOX) { //$NON-NLS-1$
			public void run() {
				setShowTray(isChecked());
			}
		};
		fToggleTrayAction.setChecked(fShowTray);
		fToggleTrayAction.setToolTipText("Show or hide the comparison tray"); //$NON-NLS-1$
		
		fToggleLayoutAction = new Action("&Vertical Layout", IAction.AS_CHECK_BOX) { //$NON-NLS-1$
			public void run() {
				setVerticalLayout(isChecked());
			}
		};
		fToggleLayoutAction.setChecked(fVerticalLayout);
		fToggleLayoutAction.setToolTipText("Stack the AST tree and inspector vertically"); //$NON-NLS-1$
		
		fShowRangesAction = new Action("Show Node &Ranges", IAction.AS_CHECK_BOX) { //$NON-NLS-1$
			public void run() {
				setShowRanges(isChecked());
			}
		};
		fShowRangesAction.setChecked(fShowRanges);
		
		fShowFlagsAction = new Action("Show Node &Flags", IAction.AS_CHECK_BOX) { //$NON-NLS-1$
			public void run() {
				setShowFlags(isChecked());
			}
		};
		fShowFlagsAction.setChecked(fShowFlags);
		
		fHighlightSelectionAction = new Action("&Highlight Editor Selection", IAction.AS_CHECK_BOX) { //$NON-NLS-1$
			public void run() {
				setHighlightSelection(isChecked());
			}
		};
		fHighlightSelectionAction.setChecked(fHighlightSelection);
		
		fCompactLabelsAction = new Action("&Compact Long Labels", IAction.AS_CHECK_BOX) { //$NON-NLS-1$
			public void run() {
				setCompactLabels(isChecked());
			}
		};
		fCompactLabelsAction.setChecked(fCompactLabels);
		
		fOnlyUnresolvedBindingsAction = new Action("Only &Unresolved Bindings", IAction.AS_CHECK_BOX) { //$NON-NLS-1$
			public void run() {
				setOnlyUnresolvedBindings(isChecked());
			}
		};
		fOnlyUnresolvedBindingsAction.setChecked(fOnlyUnresolvedBindings);
		
		fShowHeaderAction = new Action("Show &Header", IAction.AS_CHECK_BOX) { //$NON-NLS-1$
			public void run() {
				setShowHeader(isChecked());
			}
		};
		fShowHeaderAction.setChecked(fShowHeader);
		
		fShowStatusBarAction = new Action("Show &Status Bar", IAction.AS_CHECK_BOX) { //$NON-NLS-1$
			public void run() {
				setShowStatusBar(isChecked());
			}
		};
		fShowStatusBarAction.setChecked(fShowStatusBar);
		
		fShowTreeLinesAction = new Action("Show Tree &Lines", IAction.AS_CHECK_BOX) { //$NON-NLS-1$
			public void run() {
				setShowTreeLines(isChecked());
			}
		};
		fShowTreeLinesAction.setChecked(fShowTreeLines);
		
		fDenseModeAction = new Action("&Dense Tree Mode", IAction.AS_CHECK_BOX) { //$NON-NLS-1$
			public void run() {
				setDenseMode(isChecked());
			}
		};
		fDenseModeAction.setChecked(fDenseMode);
		
		fShowSourcePreviewAction = new Action("Show Source &Preview", IAction.AS_CHECK_BOX) { //$NON-NLS-1$
			public void run() {
				setShowSourcePreview(isChecked());
			}
		};
		fShowSourcePreviewAction.setChecked(fShowSourcePreview);
		
		fShowBreadcrumbAction = new Action("Show Inspector &Breadcrumb", IAction.AS_CHECK_BOX) { //$NON-NLS-1$
			public void run() {
				setShowBreadcrumb(isChecked());
			}
		};
		fShowBreadcrumbAction.setChecked(fShowBreadcrumb);
		
		fPinInspectorAction = new Action("&Pin Inspector", IAction.AS_CHECK_BOX) { //$NON-NLS-1$
			public void run() {
				setPinInspector(isChecked());
			}
		};
		fPinInspectorAction.setChecked(fPinInspector);
		
		fOnlyMalformedAction = new Action("Only &Malformed Nodes", IAction.AS_CHECK_BOX) { //$NON-NLS-1$
			public void run() {
				setOnlyMalformed(isChecked());
			}
		};
		fOnlyMalformedAction.setChecked(fOnlyMalformed);
		
		fQualifiedNodeNamesAction = new Action("Use &Qualified Node Names", IAction.AS_CHECK_BOX) { //$NON-NLS-1$
			public void run() {
				setQualifiedNodeNames(isChecked());
			}
		};
		fQualifiedNodeNamesAction.setChecked(fQualifiedNodeNames);
		
		fShowPropertyCountAction = new Action("Show Property &Counts", IAction.AS_CHECK_BOX) { //$NON-NLS-1$
			public void run() {
				setShowPropertyCount(isChecked());
			}
		};
		fShowPropertyCountAction.setChecked(fShowPropertyCount);
		
		fShowFlagHexAction = new Action("Show Flags as He&x", IAction.AS_CHECK_BOX) { //$NON-NLS-1$
			public void run() {
				setShowFlagHex(isChecked());
			}
		};
		fShowFlagHexAction.setChecked(fShowFlagHex);
		
		fSearchCaseSensitiveAction = new Action("Search Case &Sensitive", IAction.AS_CHECK_BOX) { //$NON-NLS-1$
			public void run() {
				setSearchCaseSensitive(isChecked());
			}
		};
		fSearchCaseSensitiveAction.setChecked(fSearchCaseSensitive);
		
		fSearchRegexAction = new Action("Search as &Regex", IAction.AS_CHECK_BOX) { //$NON-NLS-1$
			public void run() {
				setSearchRegex(isChecked());
			}
		};
		fSearchRegexAction.setChecked(fSearchRegex);
		
		fHideLeafAttributesAction = new Action("Hide Leaf &Attributes", IAction.AS_CHECK_BOX) { //$NON-NLS-1$
			public void run() {
				setHideLeafAttributes(isChecked());
			}
		};
		fHideLeafAttributesAction.setChecked(fHideLeafAttributes);
		
		fFocusSubtreeAction = new Action("Focus Selected S&ubtree", IAction.AS_CHECK_BOX) { //$NON-NLS-1$
			public void run() {
				setFocusSubtree(isChecked());
			}
		};
		fFocusSubtreeAction.setChecked(fFocusSubtree);
		
		fIntelliSenseAction = new Action("Enable &IntelliSense", IAction.AS_CHECK_BOX) { //$NON-NLS-1$
			public void run() {
				setIntelliSense(isChecked());
			}
		};
		fIntelliSenseAction.setChecked(fIntelliSense);
		
		fIntelliSenseAutoRevealAction = new Action("IntelliSense Auto &Reveal", IAction.AS_CHECK_BOX) { //$NON-NLS-1$
			public void run() {
				setIntelliSenseAutoReveal(isChecked());
			}
		};
		fIntelliSenseAutoRevealAction.setChecked(fIntelliSenseAutoReveal);
		
		fShowGraphAction = new Action("Show Dynamic &Graph", IAction.AS_CHECK_BOX) { //$NON-NLS-1$
			public void run() {
				setShowGraph(isChecked());
			}
		};
		fShowGraphAction.setChecked(fShowGraph);
		
		fGraphSyncSelectionAction = new Action("Graph Sync &Selection", IAction.AS_CHECK_BOX) { //$NON-NLS-1$
			public void run() {
				setGraphSyncSelection(isChecked());
			}
		};
		fGraphSyncSelectionAction.setChecked(fGraphSyncSelection);
		
		fGraphCompactAction = new Action("Graph &Compact Labels", IAction.AS_CHECK_BOX) { //$NON-NLS-1$
			public void run() {
				setGraphCompact(isChecked());
			}
		};
		fGraphCompactAction.setChecked(fGraphCompact);
		
		fGraphOverviewAction = new Action("Graph &Overview", IAction.AS_CHECK_BOX) { //$NON-NLS-1$
			public void run() {
				setGraphOverview(isChecked());
			}
		};
		fGraphOverviewAction.setChecked(fGraphOverview);
		
		fGraphHeatmapAction = new Action("Graph &Heatmap", IAction.AS_CHECK_BOX) { //$NON-NLS-1$
			public void run() {
				setGraphHeatmap(isChecked());
			}
		};
		fGraphHeatmapAction.setChecked(fGraphHeatmap);
		
		fAutoExpandActions= new Action[] {
				createAutoExpandAction("&Manual expansion", 0), //$NON-NLS-1$
				createAutoExpandAction("Auto-expand depth &1", 1), //$NON-NLS-1$
				createAutoExpandAction("Auto-expand depth &2", 2), //$NON-NLS-1$
				createAutoExpandAction("Auto-expand depth &3", 3), //$NON-NLS-1$
				createAutoExpandAction("Auto-expand &all", -1) //$NON-NLS-1$
		};
		fThemeActions= new Action[] {
				createThemeAction("&Light Theme", THEME_LIGHT), //$NON-NLS-1$
				createThemeAction("&Dark Theme", THEME_DARK), //$NON-NLS-1$
				createThemeAction("&High Contrast Theme", THEME_CONTRAST) //$NON-NLS-1$
		};
		fElementFilterActions= new Action[] {
				createElementFilterAction("Show &All Elements", FILTER_ALL), //$NON-NLS-1$
				createElementFilterAction("Show AST &Nodes", FILTER_AST_NODES), //$NON-NLS-1$
				createElementFilterAction("Show &Properties", FILTER_PROPERTIES), //$NON-NLS-1$
				createElementFilterAction("Show &Bindings", FILTER_BINDINGS), //$NON-NLS-1$
				createElementFilterAction("Show &Problems", FILTER_PROBLEMS) //$NON-NLS-1$
		};
		fInspectorDetailActions= new Action[] {
				createInspectorDetailAction("Inspector &Summary", INSPECTOR_SUMMARY), //$NON-NLS-1$
				createInspectorDetailAction("Inspector &Technical", INSPECTOR_TECHNICAL), //$NON-NLS-1$
				createInspectorDetailAction("Inspector S&ource", INSPECTOR_SOURCE) //$NON-NLS-1$
		};
		fRangeModeActions= new Action[] {
				createRangeModeAction("Range &Off", ASTViewLabelProvider.RANGE_NONE), //$NON-NLS-1$
				createRangeModeAction("Range &Offset", ASTViewLabelProvider.RANGE_OFFSET), //$NON-NLS-1$
				createRangeModeAction("Range &Line", ASTViewLabelProvider.RANGE_LINE), //$NON-NLS-1$
				createRangeModeAction("Range Offset + Li&ne", ASTViewLabelProvider.RANGE_BOTH) //$NON-NLS-1$
		};
		fViewPresetActions= new Action[] {
				createViewPresetAction("Preset &Custom", PRESET_CUSTOM), //$NON-NLS-1$
				createViewPresetAction("Preset &Review", PRESET_REVIEW), //$NON-NLS-1$
				createViewPresetAction("Preset &Diagnostics", PRESET_DIAGNOSTICS), //$NON-NLS-1$
				createViewPresetAction("Preset &Bindings", PRESET_BINDINGS), //$NON-NLS-1$
				createViewPresetAction("Preset &Source", PRESET_SOURCE) //$NON-NLS-1$
		};
		fSearchScopeActions= new Action[] {
				createSearchScopeAction("Search &Labels", SEARCH_LABEL), //$NON-NLS-1$
				createSearchScopeAction("Search &Classes", SEARCH_CLASS), //$NON-NLS-1$
				createSearchScopeAction("Search Label + C&lass", SEARCH_BOTH) //$NON-NLS-1$
		};
		fMaxDepthActions= new Action[] {
				createMaxDepthAction("Depth Any", 0), //$NON-NLS-1$
				createMaxDepthAction("Depth 3", 3), //$NON-NLS-1$
				createMaxDepthAction("Depth 5", 5), //$NON-NLS-1$
				createMaxDepthAction("Depth 8", 8), //$NON-NLS-1$
				createMaxDepthAction("Depth 12", 12) //$NON-NLS-1$
		};
		fIntelliSenseModeActions= new Action[] {
				createIntelliSenseModeAction("IntelliSense &All", SENSE_ALL), //$NON-NLS-1$
				createIntelliSenseModeAction("IntelliSense &Nodes", SENSE_NODES), //$NON-NLS-1$
				createIntelliSenseModeAction("IntelliSense &Properties", SENSE_PROPERTIES), //$NON-NLS-1$
				createIntelliSenseModeAction("IntelliSense &Bindings", SENSE_BINDINGS) //$NON-NLS-1$
		};
		fGraphDepthActions= new Action[] {
				createGraphDepthAction("Graph Depth &2", 2), //$NON-NLS-1$
				createGraphDepthAction("Graph Depth &3", 3), //$NON-NLS-1$
				createGraphDepthAction("Graph Depth &4", 4), //$NON-NLS-1$
				createGraphDepthAction("Graph Depth &6", 6) //$NON-NLS-1$
		};
		fGraphOrientationActions= new Action[] {
				createGraphOrientationAction("Graph &Top-down", GRAPH_VERTICAL), //$NON-NLS-1$
				createGraphOrientationAction("Graph &Left-right", GRAPH_HORIZONTAL) //$NON-NLS-1$
		};
		fGraphZoomActions= new Action[] {
				createGraphZoomAction("Graph Zoom &75%", 75), //$NON-NLS-1$
				createGraphZoomAction("Graph Zoom &100%", 100), //$NON-NLS-1$
				createGraphZoomAction("Graph Zoom &125%", 125), //$NON-NLS-1$
				createGraphZoomAction("Graph Zoom &150%", 150) //$NON-NLS-1$
		};
			
		fASTVersionToggleActions= new ASTLevelToggle[] {
				new ASTLevelToggle("AST Level &2.0", AST.JLS2), //$NON-NLS-1$
				new ASTLevelToggle("AST Level &3.0", AST.JLS3), //$NON-NLS-1$
				new ASTLevelToggle("AST Level &4", AST.JLS4), //$NON-NLS-1$
				new ASTLevelToggle("AST Level &8", AST.JLS8), //$NON-NLS-1$
				new ASTLevelToggle("AST Level &9", AST.JLS9), //$NON-NLS-1$
				new ASTLevelToggle("AST Level 1&0", AST.JLS10), //$NON-NLS-1$
				new ASTLevelToggle("AST Level 1&1", AST.JLS11), //$NON-NLS-1$
				new ASTLevelToggle("AST Level 1&2", AST.JLS12), //$NON-NLS-1$
				new ASTLevelToggle("AST Level 1&3", AST.JLS13), //$NON-NLS-1$
				new ASTLevelToggle("AST Level 1&4", AST.JLS14), //$NON-NLS-1$
				new ASTLevelToggle("AST Level 1&5", AST.JLS15), //$NON-NLS-1$
				new ASTLevelToggle("AST Level 1&6", AST.JLS16), //$NON-NLS-1$
				new ASTLevelToggle("AST Level 1&7", AST.JLS17), //$NON-NLS-1$
				new ASTLevelToggle("AST Level 1&8", AST.JLS18), //$NON-NLS-1$
				new ASTLevelToggle("AST Level 1&9", AST.JLS19), //$NON-NLS-1$
				new ASTLevelToggle("AST Level 2&0", AST.JLS20), //$NON-NLS-1$
				new ASTLevelToggle("AST Level 2&1", AST.JLS21), //$NON-NLS-1$
				new ASTLevelToggle("AST Level 2&2", AST.JLS22), //$NON-NLS-1$
				new ASTLevelToggle("AST Level 2&3", AST.JLS23), //$NON-NLS-1$
				new ASTLevelToggle("AST Level 2&4", AST.JLS24), //$NON-NLS-1$
				new ASTLevelToggle("AST Level 2&5", AST.JLS25) //$NON-NLS-1$
		};
		
		fAddToTrayAction= new Action() {
			public void run() {
				performAddToTray();
			}
		};
		fAddToTrayAction.setText("&Add to Comparison Tray"); //$NON-NLS-1$
		fAddToTrayAction.setToolTipText("Add Selected Node to Comparison Tray"); //$NON-NLS-1$
		fAddToTrayAction.setEnabled(false);
		ASTViewImages.setImageDescriptors(fAddToTrayAction, ASTViewImages.ADD_TO_TRAY);
		
		fDeleteAction= new Action() {
			public void run() {
				performDelete();
			}
		};
		fDeleteAction.setText("&Delete"); //$NON-NLS-1$
		fDeleteAction.setToolTipText("Delete Binding from Tray"); //$NON-NLS-1$
		fDeleteAction.setEnabled(false);
		fDeleteAction.setImageDescriptor(ASTViewPlugin.getDefault().getWorkbench().getSharedImages().getImageDescriptor(ISharedImages.IMG_TOOL_DELETE));
		fDeleteAction.setId(ActionFactory.DELETE.getId());
		fDeleteAction.setActionDefinitionId(IWorkbenchActionDefinitionIds.DELETE);
	}

	private Action createAutoExpandAction(String label, final int level) {
		Action action= new Action(label, IAction.AS_RADIO_BUTTON) {
			public void run() {
				setAutoExpandLevel(level);
			}
		};
		action.setChecked(fAutoExpandLevel == level);
		return action;
	}

	private Action createThemeAction(String label, final int theme) {
		Action action= new Action(label, IAction.AS_RADIO_BUTTON) {
			public void run() {
				setVisualTheme(theme);
			}
		};
		action.setChecked(fVisualTheme == theme);
		return action;
	}

	private Action createElementFilterAction(String label, final int filter) {
		Action action= new Action(label, IAction.AS_RADIO_BUTTON) {
			public void run() {
				setElementFilter(filter);
			}
		};
		action.setChecked(fElementFilter == filter);
		return action;
	}

	private Action createInspectorDetailAction(String label, final int detail) {
		Action action= new Action(label, IAction.AS_RADIO_BUTTON) {
			public void run() {
				setInspectorDetail(detail);
			}
		};
		action.setChecked(fInspectorDetail == detail);
		return action;
	}

	private Action createRangeModeAction(String label, final int rangeMode) {
		Action action= new Action(label, IAction.AS_RADIO_BUTTON) {
			public void run() {
				setRangeMode(rangeMode);
			}
		};
		action.setChecked(fRangeMode == rangeMode);
		return action;
	}

	private Action createViewPresetAction(String label, final int preset) {
		Action action= new Action(label, IAction.AS_RADIO_BUTTON) {
			public void run() {
				applyViewPreset(preset);
			}
		};
		action.setChecked(fViewPreset == preset);
		return action;
	}

	private Action createSearchScopeAction(String label, final int scope) {
		Action action= new Action(label, IAction.AS_RADIO_BUTTON) {
			public void run() {
				setSearchScope(scope);
			}
		};
		action.setChecked(fSearchScope == scope);
		return action;
	}

	private Action createMaxDepthAction(String label, final int depth) {
		Action action= new Action(label, IAction.AS_RADIO_BUTTON) {
			public void run() {
				setMaxDepth(depth);
			}
		};
		action.setChecked(fMaxDepth == depth);
		return action;
	}

	private Action createIntelliSenseModeAction(String label, final int mode) {
		Action action= new Action(label, IAction.AS_RADIO_BUTTON) {
			public void run() {
				setIntelliSenseMode(mode);
			}
		};
		action.setChecked(fIntelliSenseMode == mode);
		return action;
	}

	private Action createGraphDepthAction(String label, final int depth) {
		Action action= new Action(label, IAction.AS_RADIO_BUTTON) {
			public void run() {
				setGraphDepth(depth);
			}
		};
		action.setChecked(fGraphDepth == depth);
		return action;
	}

	private Action createGraphOrientationAction(String label, final int orientation) {
		Action action= new Action(label, IAction.AS_RADIO_BUTTON) {
			public void run() {
				setGraphOrientation(orientation);
			}
		};
		action.setChecked(fGraphOrientation == orientation);
		return action;
	}

	private Action createGraphZoomAction(String label, final int zoom) {
		Action action= new Action(label, IAction.AS_RADIO_BUTTON) {
			public void run() {
				setGraphZoom(zoom);
			}
		};
		action.setChecked(fGraphZoom == zoom);
		return action;
	}
	
	private void refreshAST() throws CoreException {
		ASTNode node= getASTNodeNearSelection((IStructuredSelection) fViewer.getSelection());
		int offset= 0;
		int length= 0;
		if (node != null) {
			offset= node.getStartPosition();
			length= node.getLength();
		}

		internalSetInput(fOpenable, offset, length, getCurrentASTLevel());
	}
		
	protected void setASTLevel(int level, boolean doRefresh) {
		int oldLevel= fCurrentASTLevel;
		fCurrentASTLevel= level;

		fDialogSettings.put(SETTINGS_JLS, fCurrentASTLevel);
		
		if (doRefresh && fOpenable != null && oldLevel != fCurrentASTLevel) {
			try {
				refreshAST();
			} catch (CoreException e) {
				showAndLogError("Could not set AST to new level.", e); //$NON-NLS-1$
				// set back to old level
				fCurrentASTLevel= oldLevel;
			}
		}
		// update action state
		for (int i= 0; i < fASTVersionToggleActions.length; i++) {
			ASTLevelToggle curr= fASTVersionToggleActions[i];
			curr.setChecked(curr.getLevel() == fCurrentASTLevel);
		}
		updateMetrics(fMetricNodes == null ? "--" : fMetricNodes.getText(), getModeLabel(), fMetricHealth == null ? "Idle" : fMetricHealth.getText()); //$NON-NLS-1$ //$NON-NLS-2$
	}
	
	
	private ASTNode getASTNodeNearSelection(IStructuredSelection selection) {
		Object elem= selection.getFirstElement();
		if (elem instanceof ASTAttribute) {
			return ((ASTAttribute) elem).getParentASTNode();
		} else if (elem instanceof ASTNode) {
			return (ASTNode) elem;
		}
		return null;
	}
	
	private void installModificationListener() {
		if (fEditor != null && fEditor.getDocumentProvider() != null) {
			fCurrentDocument= fEditor.getDocumentProvider().getDocument(fEditor.getEditorInput());
			if (fCurrentDocument != null) {
				fCurrentDocument.addDocumentListener(fSuperListener);
			}
		}
	}
	
	private void uninstallModificationListener() {
		if (fCurrentDocument != null) {
			fCurrentDocument.removeDocumentListener(fSuperListener);
			fCurrentDocument= null;
		}
	}
		
	protected void handleDocumentDisposed(IDocument document) {
		uninstallModificationListener();
	}
	
	protected void handleDocumentChanged(IDocument document) {
		setASTUptoDate(false);
	}
	
	protected void handleSelectionChanged(ISelection selection) {
		fExpandAction.setEnabled(!selection.isEmpty());
		fCollapseAction.setEnabled(!selection.isEmpty());
		fCopyAction.setEnabled(!selection.isEmpty());
		updateInspector(selection);
		updateStatusBar(selection);
		if (fGraphSyncSelection) {
			redrawGraph();
		}
		
		boolean addEnabled= false;
		IStructuredSelection structuredSelection= (IStructuredSelection) selection;
		if (structuredSelection.size() == 1) {
			Object first= structuredSelection.getFirstElement();
			if (first instanceof Binding)
				addEnabled= ((Binding) first).getBinding() != null && fViewer.getTree().isFocusControl();
		}
		fAddToTrayAction.setEnabled(addEnabled);
	}

	private void updateInspector(ISelection selection) {
		if (fPinInspector) {
			return;
		}
		Object first= null;
		if (selection instanceof IStructuredSelection) {
			first= ((IStructuredSelection) selection).getFirstElement();
		}
		if (fInspectorTitle == null || fInspectorTitle.isDisposed()) {
			return;
		}
		if (first == null) {
			fInspectorTitle.setText("No selection"); //$NON-NLS-1$
			fInspectorKind.setText("Kind: --"); //$NON-NLS-1$
			fInspectorRange.setText("Range: --"); //$NON-NLS-1$
			fInspectorExtra.setText("Details: select an AST item to inspect it here."); //$NON-NLS-1$
			fInspectorStats.setText("Stats: --"); //$NON-NLS-1$
			fInspectorPath.setText("Path: --"); //$NON-NLS-1$
			fInspectorPreview.setText("Source preview: --"); //$NON-NLS-1$
			return;
		}
		String label= fLabelProvider != null ? fLabelProvider.getText(first) : String.valueOf(first);
		fInspectorTitle.setText(label.length() == 0 ? first.getClass().getName() : label);
		fInspectorKind.setText("Kind: " + first.getClass().getName()); //$NON-NLS-1$
		
		ASTNode node= getASTNodeNearSelection(new StructuredSelection(first));
		if (node != null) {
			int line= fRoot == null ? -1 : fRoot.getLineNumber(node.getStartPosition());
			fInspectorRange.setText("Range: " + node.getStartPosition() + " .. " + (node.getStartPosition() + node.getLength()) + " (" + node.getLength() + " chars, line " + line + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
			fInspectorExtra.setText(getInspectorDetails(node));
			fInspectorStats.setText(getInspectorStats(node));
			fInspectorPath.setText(fShowBreadcrumb ? "Path: " + getNodePath(node) : "Path: hidden"); //$NON-NLS-1$ //$NON-NLS-2$
			fInspectorPreview.setText(getSourcePreview(node));
		} else if (first instanceof Binding) {
			Binding binding= (Binding) first;
			fInspectorRange.setText("Range: binding metadata"); //$NON-NLS-1$
			fInspectorExtra.setText("Details: " + (binding.getBinding() == null ? "unresolved binding" : binding.getBinding().getName())); //$NON-NLS-1$ //$NON-NLS-2$
			fInspectorStats.setText("Stats: binding=" + (binding.getBinding() == null ? "unresolved" : "resolved")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
			fInspectorPath.setText("Path: binding attribute"); //$NON-NLS-1$
			fInspectorPreview.setText("Source preview: select an AST node for source."); //$NON-NLS-1$
		} else {
			fInspectorRange.setText("Range: --"); //$NON-NLS-1$
			fInspectorExtra.setText("Details: " + label); //$NON-NLS-1$
			fInspectorStats.setText("Stats: attribute"); //$NON-NLS-1$
			fInspectorPath.setText("Path: attribute"); //$NON-NLS-1$
			fInspectorPreview.setText("Source preview: select an AST node for source."); //$NON-NLS-1$
		}
		applyInspectorDetailVisibility();
		fInspectorTitle.getParent().layout(true, true);
	}

	private String getInspectorDetails(ASTNode node) {
		if (fInspectorDetail == INSPECTOR_SUMMARY) {
			return "Details: parent=" + getSimpleClassName(node.getParent()) + ", malformed=" + ((node.getFlags() & ASTNode.MALFORMED) != 0); //$NON-NLS-1$ //$NON-NLS-2$
		}
		return "Details: parent=" + getSimpleClassName(node.getParent()) + ", location=" + node.getLocationInParent() + ", flags=" + node.getFlags(); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
	}

	private String getInspectorStats(ASTNode node) {
		Object[] children= new ASTViewContentProvider().getChildren(node);
		return "Stats: depth=" + getNodeDepth(node) + ", children=" + children.length + ", subtree=" + countSubtreeNodes(node); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
	}

	private int getNodeDepth(ASTNode node) {
		int depth= 0;
		ASTNode current= node;
		while (current != null && current.getParent() != null) {
			depth++;
			current= current.getParent();
		}
		return depth;
	}

	private int countSubtreeNodes(ASTNode node) {
		final int[] count= new int[] { 0 };
		node.accept(new org.eclipse.jdt.core.dom.ASTVisitor() {
			public void preVisit(ASTNode visited) {
				count[0]++;
			}
		});
		return count[0];
	}

	private String getNodePath(ASTNode node) {
		ArrayList names= new ArrayList();
		ASTNode current= node;
		while (current != null) {
			names.add(0, getSimpleClassName(current));
			current= current.getParent();
		}
		StringBuffer buf= new StringBuffer();
		for (int i= 0; i < names.size(); i++) {
			if (i > 0) {
				buf.append(" > "); //$NON-NLS-1$
			}
			buf.append(names.get(i));
		}
		return buf.toString();
	}

	private String getSourcePreview(ASTNode node) {
		if (!fShowSourcePreview && fInspectorDetail != INSPECTOR_SOURCE) {
			return "Source preview: hidden"; //$NON-NLS-1$
		}
		IDocument document= getCurrentDocument();
		if (document == null || node.getStartPosition() < 0) {
			return "Source preview: unavailable"; //$NON-NLS-1$
		}
		try {
			int start= node.getStartPosition();
			int end= start + Math.max(node.getLength(), 0);
			if (fSourceContextLines > 0) {
				int startLine= Math.max(document.getLineOfOffset(start) - fSourceContextLines, 0);
				int endLine= Math.min(document.getLineOfOffset(Math.max(end - 1, start)) + fSourceContextLines, document.getNumberOfLines() - 1);
				start= document.getLineOffset(startLine);
				end= document.getLineOffset(endLine) + document.getLineLength(endLine);
			}
			int length= Math.min(end - start, 1600);
			String source= document.get(start, length);
			source= source.replace('\r', '\n');
			while (source.indexOf("\n\n\n") != -1) { //$NON-NLS-1$
				source= source.replaceAll("\n\n\n", "\n\n"); //$NON-NLS-1$ //$NON-NLS-2$
			}
			if (end - start > length) {
				source+= "\n..."; //$NON-NLS-1$
			}
			return source;
		} catch (BadLocationException e) {
			return "Source preview: invalid range"; //$NON-NLS-1$
		}
	}

	private IDocument getCurrentDocument() {
		if (fCurrentDocument != null) {
			return fCurrentDocument;
		}
		if (fEditor != null && fEditor.getDocumentProvider() != null) {
			return fEditor.getDocumentProvider().getDocument(fEditor.getEditorInput());
		}
		return null;
	}

	private String getSimpleClassName(Object object) {
		if (object == null) {
			return "<root>"; //$NON-NLS-1$
		}
		String name= object.getClass().getName();
		int dot= name.lastIndexOf('.');
		return dot == -1 ? name : name.substring(dot + 1);
	}

	private void applyInspectorDetailVisibility() {
		boolean showTechnical= fInspectorDetail != INSPECTOR_SUMMARY;
		boolean showSource= fShowSourcePreview || fInspectorDetail == INSPECTOR_SOURCE;
		setControlVisible(fInspectorStats, showTechnical);
		setControlVisible(fInspectorPath, fShowBreadcrumb && showTechnical);
		setControlVisible(fInspectorPreview, showSource);
	}

	protected void handleEditorPostSelectionChanged(IWorkbenchPart part, ISelection selection) {
		if (!fDoLinkWithEditor || !(selection instanceof ITextSelection)) {
			return;
		}
		if (fRoot == null || part != fEditor) {
			if (part instanceof ITextEditor && (EditorUtility.getJavaInput((ITextEditor) part) != null)) {
				try {
					setInput((ITextEditor) part);
				} catch (CoreException e) {
					setContentDescription(e.getStatus().getMessage());
				}
			}
			
		} else { // fRoot != null && part == fEditor
			doLinkWithEditor(selection);
		}
	}
	
	private void doLinkWithEditor(ISelection selection) {
		ITextSelection textSelection= (ITextSelection) selection;
		int offset= textSelection.getOffset();
		int length= textSelection.getLength();
		if (fLabelProvider != null) {
			fLabelProvider.setSelectedRange(offset, length);
			fViewer.refresh();
		}
		
		NodeFinder finder= new NodeFinder(offset, length);
		fRoot.accept(finder);
		ASTNode covering= finder.getCoveringNode();
		if (covering != null) {
			fViewer.reveal(covering);
			fViewer.setSelection(new StructuredSelection(covering));
		}
	}

	protected void handleDoubleClick(DoubleClickEvent event) {
		fDoubleClickAction.run();
	}

	protected void performLinkWithEditor() {
		fDoLinkWithEditor= fLinkWithEditor.isChecked();
		fDialogSettings.put(SETTINGS_LINK_WITH_EDITOR, fDoLinkWithEditor);
		
		if (fDoLinkWithEditor && fEditor != null)
			doLinkWithEditor(fEditor.getSelectionProvider().getSelection());
	}

	protected void performCollapse() {
		IStructuredSelection selection= (IStructuredSelection) fViewer.getSelection();
		if (selection.isEmpty()) {
			fViewer.collapseAll();
		} else {
			Object[] selected= selection.toArray();
			fViewer.getTree().setRedraw(false);
			for (int i= 0; i < selected.length; i++) {
				fViewer.collapseToLevel(selected[i], AbstractTreeViewer.ALL_LEVELS);
			}
			fViewer.getTree().setRedraw(true);
		}
	}

	protected void performExpand() {	
		IStructuredSelection selection= (IStructuredSelection) fViewer.getSelection();
		if (selection.isEmpty()) {
			fViewer.expandToLevel(3);
		} else {
			Object[] selected= selection.toArray();
			fViewer.getTree().setRedraw(false);
			for (int i= 0; i < selected.length; i++) {
				fViewer.expandToLevel(selected[i], 3);
			}
			fViewer.getTree().setRedraw(true);
		}
	}

	protected void performSetFocus() {
		IEditorPart part= EditorUtility.getActiveEditor();
		if (part instanceof ITextEditor) {
			try {
				setInput((ITextEditor) part);
			} catch (CoreException e) {
				showAndLogError("Could not set AST view input ", e); //$NON-NLS-1$
			}
		}
	}
	
	protected void performRefresh() {
		if (fOpenable != null) {
			try {
				refreshAST();
			} catch (CoreException e) {
				showAndLogError("Could not set AST view input ", e); //$NON-NLS-1$
			}
		}
	}

	protected void performClear() {
		fOpenable= null;
		try {
			setInput(null);
		} catch (CoreException e) {
			showAndLogError("Could not reset AST view ", e); //$NON-NLS-1$
		}
		resetView(null);
	}

	private void showAndLogError(String message, CoreException e) {
		ASTViewPlugin.log(message, e);
		ErrorDialog.openError(getSite().getShell(), "AST View", message, e.getStatus()); //$NON-NLS-1$
	}
	
	private void showAndLogError(String message, Exception e) {
		IStatus status= new Status(IStatus.ERROR, ASTViewPlugin.getPluginId(), 0, message, e);
		ASTViewPlugin.log(status);
		ErrorDialog.openError(getSite().getShell(), "AST View", null, status); //$NON-NLS-1$
	}
	
	protected void performUseReconciler() {
		fDoUseReconciler= fUseReconcilerAction.isChecked();
		fDialogSettings.put(SETTINGS_USE_RECONCILER, fDoUseReconciler);
		updateMetrics(fMetricNodes == null ? "--" : fMetricNodes.getText(), getModeLabel(), fMetricHealth == null ? "Idle" : fMetricHealth.getText()); //$NON-NLS-1$ //$NON-NLS-2$
		
		performRefresh();
	}
	
	protected void performCreateBindings() {
		fCreateBindings= fCreateBindingsAction.isChecked();
		fDialogSettings.put(SETTINGS_NO_BINDINGS, !fCreateBindings);
		updateMetrics(fMetricNodes == null ? "--" : fMetricNodes.getText(), getModeLabel(), fMetricHealth == null ? "Idle" : fMetricHealth.getText()); //$NON-NLS-1$ //$NON-NLS-2$
		performRefresh();
	}
	
	protected void performDoubleClick() {
		ISelection selection = fViewer.getSelection();
		Object obj = ((IStructuredSelection) selection).getFirstElement();

		boolean isTrippleClick= (obj == fPreviousDouble);
		fPreviousDouble= isTrippleClick ? null : obj;
		
		if (obj instanceof ExceptionAttribute) {
			RuntimeException exception= ((ExceptionAttribute) obj).getException();
			if (exception != null) {
				String label= ((ExceptionAttribute) obj).getLabel();
				showAndLogError("An error occurred while calculating an AST View Label:\n" + label, exception); //$NON-NLS-1$
				return;
			}
		}
		
		ASTNode node= null;
		if (obj instanceof ASTNode) {
			node= (ASTNode) obj;
		} else if (obj instanceof NodeProperty) {
			Object val= ((NodeProperty) obj).getNode();
			if (val instanceof ASTNode) {
				node= (ASTNode) val;
			}
		} else if (obj instanceof Binding) {
			IBinding binding= ((Binding) obj).getBinding();
			ASTNode declaring= fRoot.findDeclaringNode(binding);
			if (declaring != null) {
				fViewer.reveal(declaring);
				fViewer.setSelection(new StructuredSelection(declaring));
			}
			return;
		} else if (obj instanceof ProblemNode) {
			ProblemNode problemNode= (ProblemNode) obj;
			EditorUtility.selectInEditor(fEditor, problemNode.getOffset(), problemNode.getLength());
			return;
		} else if (obj instanceof JavaElement) {
			IJavaElement javaElement= ((JavaElement) obj).getJavaElement();
			if (javaElement instanceof IPackageFragment) {
				ShowInPackageViewAction showInPackageViewAction= new ShowInPackageViewAction(getViewSite());
				showInPackageViewAction.run(javaElement);
			} else {
				try {
					IEditorPart editorPart= JavaUI.openInEditor(javaElement);
					if (editorPart != null)
						JavaUI.revealInEditor(editorPart, javaElement);
				} catch (PartInitException e) {
					showAndLogError("Could not open editor.", e); //$NON-NLS-1$
				} catch (JavaModelException e) {
					showAndLogError("Could not open editor.", e); //$NON-NLS-1$
				}
			}
			return;
		}
		
		if (node != null) {
			int offset= isTrippleClick ? fRoot.getExtendedStartPosition(node) : node.getStartPosition();
			int length= isTrippleClick ? fRoot.getExtendedLength(node) : node.getLength();

			EditorUtility.selectInEditor(fEditor, offset, length);
		}
	}

	protected void performAddToTray() {
		IStructuredSelection selection= (IStructuredSelection) fViewer.getSelection();
		Object firstElement= selection.getFirstElement();
		if (! fTrayRoots.contains(firstElement)) {
			fTrayRoots.add(firstElement);
			fTray.setInput(fTrayRoots);
		}
		updateStatusBar(null);
		if (!fShowTray) {
			setShowTray(true);
		}
		if (fSash.getMaximizedControl() != null) {
			fSash.setMaximizedControl(null);
			applyTrayVisibility();
		}
		setTraySelection(selection);
	}
	
	private void setTraySelection(IStructuredSelection selection) {
		fTray.setSelection(selection, true);
		TreeItem[] itemSelection= fTray.getTree().getSelection();
		if (itemSelection.length > 0)
			fTray.getTree().setTopItem(itemSelection[0]);
	}

	protected void performTrayDoubleClick() {
		IStructuredSelection selection= (IStructuredSelection) fTray.getSelection();
		if (selection.size() != 1)
			return;
		Object obj = selection.getFirstElement();
		if (obj instanceof ExceptionAttribute) {
			RuntimeException exception= ((ExceptionAttribute) obj).getException();
			if (exception != null) {
				String label= ((ExceptionAttribute) obj).getLabel();
				showAndLogError("An error occurred while calculating an AST View Label:\n" + label, exception); //$NON-NLS-1$
				return;
			}
		}
		if (obj instanceof Binding) {
			Binding binding= (Binding) obj;
			fViewer.setSelection(new StructuredSelection(binding), true);
		}
	}
		
	protected void performDelete() {
		IStructuredSelection selection= (IStructuredSelection) fTray.getSelection();
		if (selection.size() != 1)
			return;
		Object obj = selection.getFirstElement();
		if (obj instanceof Binding) {
			int index= fTrayRoots.indexOf(obj);
			if (index != -1) {
				fTrayRoots.remove(index);
				fTray.setInput(fTrayRoots);
				updateStatusBar(null);
				int newSize= fTrayRoots.size();
				if (newSize == 0)
					return;
				else if (index == newSize)
					setTraySelection(new StructuredSelection(fTrayRoots.get(newSize - 1)));
				else
					setTraySelection(new StructuredSelection(fTrayRoots.get(index)));
			}
		}
	}
	
	public void setFocus() {
		fViewer.getControl().setFocus();
	}

	public ShowInContext getShowInContext() {
		return new ShowInContext(null, getSite().getSelectionProvider().getSelection());
	}
}
