/*****************************************************************************
 * Copyright (c) 2004, 2020 IBM Corporation and others. All rights reserved. This
 * program and the accompanying materials are made available under the terms
 * of the Eclipse Public License 2.0 which accompanies this distribution, and
 * is available at https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 * 
 * Contributors: IBM Corporation - initial API and implementation
 ****************************************************************************/
package org.eclipse.wst.xml.ui.internal.tabletree;


import org.eclipse.core.resources.IMarker;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExecutableExtension;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IStatusLineManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.action.ToolBarManager;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.ITextInputListener;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.util.ConfigureColumns;
import org.eclipse.jface.viewers.AbstractTreeViewer;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.viewers.IPostSelectionProvider;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.window.SameShellProvider;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.ui.IEditorActionBarContributor;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IEditorSite;
import org.eclipse.ui.IMemento;
import org.eclipse.ui.INavigationLocation;
import org.eclipse.ui.INavigationLocationProvider;
import org.eclipse.ui.IPartListener;
import org.eclipse.ui.IPartService;
import org.eclipse.ui.IPersistableEditor;
import org.eclipse.ui.IPropertyListener;
import org.eclipse.ui.IWindowListener;
import org.eclipse.ui.IWorkbenchActionConstants;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.ide.IDE;
import org.eclipse.ui.ide.IGotoMarker;
import org.eclipse.ui.part.MultiPageEditorActionBarContributor;
import org.eclipse.ui.part.MultiPageEditorPart;
import org.eclipse.ui.part.MultiPageEditorSite;
import org.eclipse.ui.part.MultiPageSelectionProvider;
import org.eclipse.ui.progress.UIJob;
import org.eclipse.ui.texteditor.IDocumentProvider;
import org.eclipse.wst.sse.core.StructuredModelManager;
import org.eclipse.wst.sse.core.internal.provisional.INodeNotifier;
import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion;
import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegion;
import org.eclipse.wst.sse.ui.StructuredTextEditor;
import org.eclipse.wst.sse.ui.internal.contentoutline.IJFaceNodeAdapter;
import org.eclipse.wst.xml.core.internal.provisional.IXMLPreferenceNames;
import org.eclipse.wst.xml.core.internal.provisional.contenttype.ContentTypeIdForXML;
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMDocument;
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel;
import org.eclipse.wst.xml.ui.internal.ImageDescriptorRegistry;
import org.eclipse.wst.xml.ui.internal.Logger;
import org.eclipse.wst.xml.ui.internal.XMLUIPlugin;
import org.eclipse.wst.xml.ui.internal.contentoutline.JFaceNodeLabelProvider;
import org.w3c.dom.Attr;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

public class XMLMultiPageEditorPart extends MultiPageEditorPart implements INavigationLocationProvider, IPersistableEditor {

	/**
	 * Internal part activation listener, copied from AbstractTextEditor
	 */
	class ActivationListener implements IPartListener, IWindowListener {

		/**
		 * The maximum number of children the root nodes can have for the
		 * design page to auto expand the root nodes
		 */
		private static final int MAX_NUM_CHILD_NODES_FOR_AUTO_EXPAND = 500;
		
		/** Cache of the active workbench part. */
		private IWorkbenchPart fActivePart;
		/** Indicates whether activation handling is currently be done. */
		private boolean fIsHandlingActivation = false;
		/**
		 * The part service.
		 * 
		 * @since 3.1
		 */
		private IPartService fPartService;

		/**
		 * Creates this activation listener.
		 * 
		 * @param partService
		 *            the part service on which to add the part listener
		 * @since 3.1
		 */
		public ActivationListener(IPartService partService) {
			fPartService = partService;
			fPartService.addPartListener(this);
			PlatformUI.getWorkbench().addWindowListener(this);
		}

		/**
		 * Disposes this activation listener.
		 * 
		 * @since 3.1
		 */
		public void dispose() {
			fPartService.removePartListener(this);
			PlatformUI.getWorkbench().removeWindowListener(this);
			fPartService = null;
		}

		/*
		 * @see IPartListener#partActivated(org.eclipse.ui.IWorkbenchPart)
		 */
		public void partActivated(IWorkbenchPart part) {
			fActivePart = part;
			handleActivation();
		}

		/*
		 * @see IPartListener#partBroughtToTop(org.eclipse.ui.IWorkbenchPart)
		 */
		public void partBroughtToTop(IWorkbenchPart part) {
			// do nothing
		}

		/*
		 * @see IPartListener#partClosed(org.eclipse.ui.IWorkbenchPart)
		 */
		public void partClosed(IWorkbenchPart part) {
			// do nothing
		}

		/*
		 * @see IPartListener#partDeactivated(org.eclipse.ui.IWorkbenchPart)
		 */
		public void partDeactivated(IWorkbenchPart part) {
			fActivePart = null;
		}

		/*
		 * @see IPartListener#partOpened(org.eclipse.ui.IWorkbenchPart)
		 */
		@Override
		public void partOpened(IWorkbenchPart part) {
			if (fDesignViewer instanceof AbstractTreeViewer) {
				IDocument document = getDocument();
				if (document != null) {
					IStructuredModel model = StructuredModelManager.getModelManager().getExistingModelForRead(document);
					try {
						if (model instanceof IDOMModel) {
							IDOMDocument modelDocument = ((IDOMModel) model).getDocument();
							NodeList rootChildren = modelDocument.getChildNodes();

							boolean tooManyChildren = (rootChildren.getLength() > MAX_NUM_CHILD_NODES_FOR_AUTO_EXPAND);
							/*
							 * For each root (there should really only be one
							 * real root but there are also could be empty
							 * text regions and doc type at the root level)
							 * determine if it has to many children or not to
							 * auto expand
							 */
							for (int i = 0; i < rootChildren.getLength() && !tooManyChildren; ++i) {
								tooManyChildren = (rootChildren.item(i).getChildNodes().getLength() > MAX_NUM_CHILD_NODES_FOR_AUTO_EXPAND);
							}

							/*
							 * if root node does not have to many children
							 * then auto expand the root node
							 */
							if (!tooManyChildren) {
								((AbstractTreeViewer) fDesignViewer).expandToLevel(2);
							}
						}
					}
					finally {
						if (model != null) {
							model.releaseFromRead();
						}
					}
				}
			}
		}

		/**
		 * Handles the activation triggering a element state check in the
		 * editor.
		 */
		void handleActivation() {
			if (fIsHandlingActivation || (getTextEditor() == null)) {
				return;
			}

			if (fActivePart == XMLMultiPageEditorPart.this) {
				fIsHandlingActivation = true;
				try {
					getTextEditor().safelySanityCheckState(getEditorInput());
				}
				finally {
					fIsHandlingActivation = false;
				}
			}
		}

		/*
		 * @see org.eclipse.ui.IWindowListener#windowActivated(org.eclipse.ui.IWorkbenchWindow)
		 * @since 3.1
		 */
		public void windowActivated(IWorkbenchWindow window) {
			if (window == getEditorSite().getWorkbenchWindow()) {
				/*
				 * Workaround for problem described in
				 * http://dev.eclipse.org/bugs/show_bug.cgi?id=11731 Will be
				 * removed when SWT has solved the problem.
				 */
				window.getShell().getDisplay().asyncExec(() -> handleActivation());
			}
		}

		/*
		 * @see org.eclipse.ui.IWindowListener#windowDeactivated(org.eclipse.ui.IWorkbenchWindow)
		 * @since 3.1
		 */
		public void windowDeactivated(IWorkbenchWindow window) {
			// do nothing
		}

		/*
		 * @see org.eclipse.ui.IWindowListener#windowClosed(org.eclipse.ui.IWorkbenchWindow)
		 * @since 3.1
		 */
		public void windowClosed(IWorkbenchWindow window) {
			// do nothing
		}

		/*
		 * @see org.eclipse.ui.IWindowListener#windowOpened(org.eclipse.ui.IWorkbenchWindow)
		 * @since 3.1
		 */
		public void windowOpened(IWorkbenchWindow window) {
			// do nothing
		}
	}

	/**
	 * Listens for selection from the source page, applying it to the design
	 * viewer.
	 */
	private class TextEditorPostSelectionAdapter extends UIJob implements ISelectionChangedListener {
		boolean forcePostSelection = false;
		ISelection selection = null;

		public TextEditorPostSelectionAdapter() {
			super(getTitle());
			setUser(true);
		}

		public IStatus runInUIThread(IProgressMonitor monitor) {
			if (selection != null) {
				fDesignViewer.getSelectionProvider().setSelection(selection);
			}
			return Status.OK_STATUS;
		}

		public void selectionChanged(SelectionChangedEvent event) {
			if ((fDesignViewer != null) && ((getActivePage() != fDesignPageIndex) || !XMLMultiPageEditorPart.this.equals(getSite().getPage().getActivePart()))) {
				if (forcePostSelection) {
					selection = event.getSelection();
					schedule(200);
				}
				else {
					fDesignViewer.getSelectionProvider().setSelection(event.getSelection());
				}
			}
		}
	}

	private class PageInitializationData {
		IConfigurationElement fElement;
		String fPropertyName;
		Object fData;

		PageInitializationData(IConfigurationElement cfig, String propertyName, Object data) {
			super();
			fElement = cfig;
			fPropertyName = propertyName;
			fData = data;
		}

		void sendInitializationData(IExecutableExtension executableExtension) {
			if (fData != null) {
				try {
					executableExtension.setInitializationData(fElement, fPropertyName, fData);
				}
				catch (CoreException e) {
					Logger.logException(e);
				}
			}
		}
	}

	/**
	 * Internal IPropertyListener on the source page
	 */
	class PropertyListener implements IPropertyListener {
		@Override
		public void propertyChanged(Object source, int propId) {
			switch (propId) {
				// had to implement input changed "listener" so that
				// StructuredTextEditor could tell it containing editor that
				// the input has change, when a 'resource moved' event is
				// found.
				case IEditorPart.PROP_INPUT : {
					if (source == getTextEditor() && fDesignViewer instanceof XMLTableTreeViewer) {
						IStructuredModel model = StructuredModelManager.getModelManager().getExistingModelForRead(getDocument());
						try {
							if (model instanceof IDOMModel) {
								IDOMDocument modelDocument = ((IDOMModel) model).getDocument();
								Object designInput = ((XMLTableTreeViewer) fDesignViewer).getInput();
								if (modelDocument != designInput)
									setInput(getTextEditor().getEditorInput());
							}
						}
						finally {
							if (model != null)
								model.releaseFromRead();
						}
					}
					break;
				}
				case IEditorPart.PROP_DIRTY : {
					if (source == getTextEditor() && ((IEditorPart)source).getEditorInput() != getEditorInput()) {
						setInput(getTextEditor().getEditorInput());
						/*
						 * title should always change when input changes.
						 * create runnable for following post call
						 */
						/*
						 * Update is just to post things on the display
						 * queue (thread). We have to do this to get the
						 * dirty property to get updated after other
						 * things on the queue are executed.
						 */
						getTextEditor().getAdapter(Control.class).getDisplay().asyncExec(() -> {
							_firePropertyChange(IWorkbenchPart.PROP_TITLE);
						});
					}
					break;
				}
				case IWorkbenchPart.PROP_TITLE : {
					// update the input if the title is changed
					if (source == getTextEditor() && ((IEditorPart)source).getEditorInput() != getEditorInput()) {
						setInput(getTextEditor().getEditorInput());
					}
					break;
				}
				default : {
					// propagate changes. Is this needed? Answer: Yes.
					if (source == getTextEditor()) {
						_firePropertyChange(propId);
					}
					break;
				}
			}

		}
	}

	class TextInputListener implements ITextInputListener {
		public void inputDocumentAboutToBeChanged(IDocument oldInput, IDocument newInput) {
			// do nothing
		}

		public void inputDocumentChanged(IDocument oldInput, IDocument newInput) {
			if ((fDesignViewer != null) && (newInput != null)) {
				fDesignViewer.setDocument(newInput);
			}
		}
	}

	class StatusLineLabelProvider extends JFaceNodeLabelProvider {
		public StatusLineLabelProvider() {
			super();
		}

		@Override
		public String getText(Object element) {
			if (element == null)
				return null;

			Node node = (Node) element;
			if (node.getNodeType() == Node.ATTRIBUTE_NODE) {
				return getText(((Attr) node).getOwnerElement());
			}

			StringBuilder s = new StringBuilder();
			if (node.getNodeType() != Node.DOCUMENT_NODE) {
				while (node instanceof INodeNotifier) {
					INodeNotifier notifier = (INodeNotifier) node;
					if (node.getNodeType() != Node.DOCUMENT_NODE) {
						IJFaceNodeAdapter adapter = (IJFaceNodeAdapter) notifier.getAdapterFor(IJFaceNodeAdapter.class);
						if (adapter != null) {
							s.insert(0, adapter.getLabelText(node));
						}
					}
					node = node.getParentNode();
					if (node != null && node.getNodeType() != Node.DOCUMENT_NODE)
						s.insert(0, IPath.SEPARATOR);
				}
			}
			return s.toString();
		}

		@Override
		public Image getImage(Object element) {
			if (element == null)
				return null;

			Node node = (Node) element;
			if (node.getNodeType() == Node.ATTRIBUTE_NODE) {
				return getImage(((Attr) node).getOwnerElement());
			}
			return super.getImage(element);
		}
	}

	/** The design page index. */
	private int fDesignPageIndex;

	/** The design viewer */
	IDesignViewer fDesignViewer;
	/** Any container for the design viewer */
	private Composite fDesignContainer;

	private ActivationListener fActivationListener;

	IPropertyListener fPropertyListener = null;

	/** The source page index. */
	int fSourcePageIndex;

	/** The text editor. */
	private StructuredTextEditor fTextEditor;

	private TextEditorPostSelectionAdapter fTextEditorSelectionListener;

	private ILabelProvider fStatusLineLabelProvider;

	private PageInitializationData fPageInitializer;
	
	private ToolBarManager fToolbarManager;
	private ToolBarManager fEditorManager;

	/** Context menu manager */
	private MenuManager fMenuManager;

	private boolean fAllocateToolbar = true;

	private TextInputListener fTextInputListener;
	
	/**
	 * StructuredTextMultiPageEditorPart constructor comment.
	 */
	public XMLMultiPageEditorPart() {
		super();
		fStatusLineLabelProvider = new StatusLineLabelProvider();
	}

	/*
	 * This method is just to make firePropertyChanged accessible from some
	 * (anonymous) inner classes.
	 */
	void _firePropertyChange(int property) {
		super.firePropertyChange(property);
	}

	/**
	 * Adds the source page of the multi-page editor.
	 */
	private void addSourcePage() throws PartInitException {
		fSourcePageIndex = addPage(fTextEditor, getEditorInput());
		setPageText(fSourcePageIndex, XMLEditorMessages.XMLMultiPageEditorPart_0);

		firePropertyChange(PROP_TITLE);

		// Changes to the Text Viewer's document instance should also
		// force an
		// input refresh
		fTextInputListener = new TextInputListener();
		fTextEditor.getTextViewer().addTextInputListener(fTextInputListener);
	}

	/**
	 * Connects the design viewer with the viewer selection manager. Should be
	 * done after createSourcePage() is done because we need to get the
	 * ViewerSelectionManager from the TextEditor. setModel is also done here
	 * because getModel() needs to reference the TextEditor.
	 */
	private void connectDesignPage() {
		if (fDesignViewer != null) {
			fDesignViewer.setDocument(getDocument());
		}

		/*
		 * Connect selection from the Design page to the selection provider
		 * for the XMLMultiPageEditorPart so that selection changes in the
		 * Design page will propagate across the workbench
		 */
		if (fDesignViewer.getSelectionProvider() instanceof IPostSelectionProvider) {
			((IPostSelectionProvider) fDesignViewer.getSelectionProvider()).addPostSelectionChangedListener(new ISelectionChangedListener() {
				public void selectionChanged(SelectionChangedEvent event) {
					if (getActivePage() != fSourcePageIndex) {
						((MultiPageSelectionProvider) getSite().getSelectionProvider()).firePostSelectionChanged(event);
					}
				}
			});
		}
		fDesignViewer.getSelectionProvider().addSelectionChangedListener(new ISelectionChangedListener() {
			public void selectionChanged(SelectionChangedEvent event) {
				if (getActivePage() != fSourcePageIndex) {
					((MultiPageSelectionProvider) getSite().getSelectionProvider()).fireSelectionChanged(event);
				}
			}
		});

		fDesignViewer.getSelectionProvider().addSelectionChangedListener(new ISelectionChangedListener() {
			public void selectionChanged(SelectionChangedEvent event) {
				if (getActivePage() != fSourcePageIndex) {
					try {
						updateStatusLine(event.getSelection());
					}
					catch (Exception exception) {
						Logger.logException(exception);
					}						
				}
			}
		});

		/*
		 * Handle double-click in the Design page by selecting the
		 * corresponding amount of text in the Source page.
		 * 
		 * Warning: This implies more knowledge of the design viewer's underlying
		 * Control than expressed in the IDesignViewer interface
		 */
		fDesignViewer.getControl().addListener(SWT.MouseDoubleClick, new Listener() {
			public void handleEvent(Event event) {
				ISelection selection = fDesignViewer.getSelectionProvider().getSelection();
				int start = -1;
				int length = -1;
				if (selection instanceof IStructuredSelection) {
					/*
					 * selection goes from the start of the first object to
					 * the end of the last
					 */
					IStructuredSelection structuredSelection = (IStructuredSelection) selection;
					Object o = structuredSelection.getFirstElement();
					Object o2 = null;
					if (structuredSelection.size() > 1) {
						o2 = structuredSelection.toArray()[structuredSelection.size() - 1];
					}
					else {
						o2 = o;
					}
					if (o instanceof IndexedRegion) {
						start = ((IndexedRegion) o).getStartOffset();
						length = ((IndexedRegion) o2).getEndOffset() - start;
					}
					else if (o2 instanceof ITextRegion) {
						start = ((ITextRegion) o).getStart();
						length = ((ITextRegion) o2).getEnd() - start;
					}
				}
				else if (selection instanceof ITextSelection) {
					start = ((ITextSelection) selection).getOffset();
					length = ((ITextSelection) selection).getLength();
				}
				if ((start > -1) && (length > -1)) {
					getTextEditor().selectAndReveal(start, length);
				}
			}
		});

		/*
		 * Connect selection from the Source page to the selection provider of
		 * the Design page so that selection in the Source page will drive
		 * selection in the Design page. Prefer post selection.
		 */
		ISelectionProvider provider = getTextEditor().getSelectionProvider();
		if (fTextEditorSelectionListener == null) {
			fTextEditorSelectionListener = new TextEditorPostSelectionAdapter();
		}
		if (provider instanceof IPostSelectionProvider) {
			fTextEditorSelectionListener.forcePostSelection = false;
			((IPostSelectionProvider) provider).addPostSelectionChangedListener(fTextEditorSelectionListener);
		}
		else {
			fTextEditorSelectionListener.forcePostSelection = true;
			provider.addSelectionChangedListener(fTextEditorSelectionListener);
		}
	}

	/**
	 * Create and Add the Design Page using a registered factory
	 * 
	 */
	private void createAndAddDesignPage() {
		IDesignViewer designViewer = createDesignPage();

		fDesignViewer = designViewer;
		// note: By adding the design page as a Control instead of an
		// IEditorPart, page switches will indicate
		// a "null" active editor when the design page is made active
		if (fDesignContainer != null)
			fDesignPageIndex = addPage(fDesignContainer);
		else
			fDesignPageIndex = addPage(designViewer.getControl());
			
		setPageText(fDesignPageIndex, designViewer.getTitle());
	}

	protected IDesignViewer createDesignPage() {		
		Composite container = getDesignContainer(getContainer());

		XMLTableTreeViewer tableTreeViewer = new XMLTableTreeViewer(container);
		tableTreeViewer.getControl().setLayoutData(new GridData(GridData.FILL_BOTH));
		// Set the default info-pop for XML design viewer.
		XMLUIPlugin.getInstance().getWorkbench().getHelpSystem().setHelp(tableTreeViewer.getControl(), XMLTableTreeHelpContextIds.XML_DESIGN_VIEW_HELPID);

		// Toolbar wasn't allocated
		if (fToolbarManager != null && fEditorManager != null) {
			addToolBarActions(tableTreeViewer);
			addEditorActions(tableTreeViewer);
		}
		return tableTreeViewer;
	}

	protected Composite getDesignContainer(Composite defaultContainer) {
		Composite container = defaultContainer;
		// create a container to hold the toolbar if it should be created
		if (fAllocateToolbar) {
			container = new Composite(defaultContainer, SWT.NONE);
			GridLayout layout = new GridLayout();
			layout.marginHeight = 0;
			layout.verticalSpacing = 0;
			layout.marginWidth = 0;
			container.setLayout(layout);

			Composite toolbarContainer = new Composite(container, SWT.NONE);
			layout = new GridLayout();
			layout.marginHeight = 0;
			layout.verticalSpacing = 0;
			layout.marginWidth = 0;
			layout.horizontalSpacing = 0;
			layout.numColumns = 2;
			toolbarContainer.setLayout(layout);
			toolbarContainer.setLayoutData(new GridData(GridData.END, GridData.VERTICAL_ALIGN_BEGINNING, true, false));

			ToolBar tb = new ToolBar(toolbarContainer, SWT.FLAT);
			fToolbarManager = new ToolBarManager(tb);
			tb.setLayoutData(new GridData(GridData.END, GridData.VERTICAL_ALIGN_BEGINNING, true, false));
			tb = new ToolBar(toolbarContainer, SWT.FLAT);
			fEditorManager = new ToolBarManager(tb);
			tb.setLayoutData(new GridData(GridData.END, GridData.VERTICAL_ALIGN_BEGINNING, true, false));
			fDesignContainer = container;
		}
		return container;
	}

	private class EditorActions extends Action {
		private ToolBar fToolbar;

		public EditorActions(ToolBar toolbar) {
			fToolbar = toolbar;
		}

		@Override
		public ImageDescriptor getImageDescriptor() {
			return XMLEditorPluginImageHelper.getInstance().getImageDescriptor(XMLEditorPluginImageHelper.EDITOR_MENU);
		}

		@Override
		public String getToolTipText() {
			return XMLEditorMessages.EditorMenu_tooltip;
		}

		@Override
		public void run() {
			Menu menu = fMenuManager.createContextMenu(fDesignContainer);
			Point size = fToolbar.getSize();
			Point location = fToolbar.toDisplay(0, size.y);
			menu.setLocation(location.x, location.y);
			menu.setVisible(true);
		}
	}

	private void addToolBarActions(IDesignViewer viewer) {
		if (viewer instanceof AbstractTreeViewer) {
			ViewerExpandCollapseAction expand = new ViewerExpandCollapseAction(true);
			ViewerExpandCollapseAction collapse = new ViewerExpandCollapseAction(false);
			fToolbarManager.add(expand);
			fToolbarManager.add(collapse);
			fToolbarManager.update(true);
	
	
			expand.setViewer((AbstractTreeViewer) viewer);
			collapse.setViewer((AbstractTreeViewer) viewer);
		}
	}

	private void addEditorActions(IDesignViewer viewer) {
		if (viewer instanceof AbstractTreeViewer) {
			final Tree tree = (Tree) ((AbstractTreeViewer) viewer).getControl();
			fMenuManager = new MenuManager();
			fMenuManager.add(new Action(XMLEditorMessages.ConfigureColumns_label) {
				public void run() {
					ConfigureColumns.forTree(tree , new SameShellProvider(tree));
				}
			});
			getSite().registerContextMenu("org.eclipse.wst.xml.ui.editor", fMenuManager, getSite().getSelectionProvider()); //$NON-NLS-1$
			fMenuManager.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS));

			fEditorManager.add(new EditorActions(fEditorManager.getControl()));
			fEditorManager.update(true);
		}
	}

	/**
	 * Creates the pages of this multi-page editor.
	 * <p>
	 * Subclasses of <code>MultiPageEditor</code> must implement this method.
	 * </p>
	 */
	protected void createPages() {
		try {
			// source page MUST be created before design page, now
			createSourcePage();

			createAndAddDesignPage();
			addSourcePage();
			connectDesignPage();

			// set the active editor in the action bar contributor first
			// before setactivepage calls action bar contributor's
			// setactivepage (bug141013 - remove when bug151488 is fixed)
			IEditorActionBarContributor contributor = getEditorSite().getActionBarContributor();
			if (contributor instanceof MultiPageEditorActionBarContributor) {
				((MultiPageEditorActionBarContributor) contributor).setActiveEditor(this);
			}

			int activePageIndex = getPreferenceStore().getInt(getEditorSite().getId() + "." + IXMLPreferenceNames.LAST_ACTIVE_PAGE); //$NON-NLS-1$;
			if ((activePageIndex >= 0) && (activePageIndex < getPageCount())) {
				setActivePage(activePageIndex);
			}
			else {
				setActivePage(fSourcePageIndex);
			}

			setPageImage(fDesignPageIndex, ImageDescriptorRegistry.getImage(ImageDescriptor.createFromURL(Platform.getBundle(XMLUIPlugin.ID).getResource("icons/full/elcl16/hierarchicalLayout.png"))));
			setPageImage(fSourcePageIndex, ImageDescriptorRegistry.getImage(ImageDescriptor.createFromURL(Platform.getBundle(XMLUIPlugin.ID).getResource("icons/full/obj16/text.gif"))));
		}
		catch (PartInitException e) {
			Logger.logException(e);
			throw new RuntimeException(e);
		}
	}

	/**
	 * @see org.eclipse.ui.part.MultiPageEditorPart#createSite(org.eclipse.ui.IEditorPart)
	 */
	@Override
	protected IEditorSite createSite(IEditorPart editor) {
		IEditorSite site = null;
		if (editor == fTextEditor) {
			site = new MultiPageEditorSite(this, editor) {
				/**
				 * @see org.eclipse.ui.part.MultiPageEditorSite#getActionBarContributor()
				 */
				public IEditorActionBarContributor getActionBarContributor() {
					IEditorActionBarContributor contributor = super.getActionBarContributor();
					IEditorActionBarContributor multiContributor = XMLMultiPageEditorPart.this.getEditorSite().getActionBarContributor();
					if (multiContributor instanceof XMLMultiPageEditorActionBarContributor) {
						contributor = ((XMLMultiPageEditorActionBarContributor) multiContributor).sourceViewerActionContributor;
					}
					return contributor;
				}

				@Override
				public String getId() {
					// sets this id so nested editor is considered xml source
					// page
					return ContentTypeIdForXML.ContentTypeID_XML + ".source"; //$NON-NLS-1$;
				}
			};
		}
		else {
			site = super.createSite(editor);
		}
		return site;
	}

	/**
	 * Creates the source page of the multi-page editor.
	 */
	protected void createSourcePage() throws PartInitException {
		/*
		 * https://bugs.eclipse.org/bugs/show_bug.cgi?id=134301 - XML editor
		 * does not remember font settings
		 * 
		 * @see
		 * org.eclipse.ui.texteditor.AbstractTextEditor#getSymbolicFontName()
		 */
		fPageInitializer.sendInitializationData(fTextEditor);

		if (fPropertyListener == null) {
			fPropertyListener = new PropertyListener();
		}
		fTextEditor.addPropertyListener(fPropertyListener);
	}

	/**
	 * Method createTextEditor.
	 * 
	 * @return StructuredTextEditor
	 */
	private StructuredTextEditor createTextEditor() {
		return new StructuredTextEditor();
	}

	private void disconnectDesignPage() {
		if (fDesignViewer != null) {
			fDesignViewer.setDocument(null);
		}
	}

	@Override
	public void dispose() {
		Logger.trace("Source Editor", "XMLMultiPageEditorPart::dispose entry"); //$NON-NLS-1$ //$NON-NLS-2$

		if (fTextInputListener != null) {
			fTextEditor.getTextViewer().removeTextInputListener(fTextInputListener);
			fTextInputListener = null;
		}
		disconnectDesignPage();
		fDesignViewer = null;

		if (fActivationListener != null) {
			fActivationListener.dispose();
			fActivationListener = null;
		}

		if (fMenuManager != null) {
			fMenuManager.removeAll();
			fMenuManager.dispose();
			fMenuManager = null;
		}

		if (fPropertyListener != null) {
			fTextEditor.removePropertyListener(fPropertyListener);
			fPropertyListener = null;
		}
		
		if (fEditorManager != null) {
			fEditorManager.dispose();
			fEditorManager = null;
		}
		if (fToolbarManager != null) {
			fToolbarManager.dispose();
			fToolbarManager = null;
		}
		// moved to last when added window ... seems like
		// we'd be in danger of losing some data, like site,
		// or something.
		super.dispose();

		fTextEditor = null;
		fPageInitializer = null;
		
		Logger.trace("Source Editor", "StructuredTextMultiPageEditorPart::dispose exit"); //$NON-NLS-1$ //$NON-NLS-2$

	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ui.ISaveablePart#doSave(org.eclipse.core.runtime.IProgressMonitor)
	 */
	public void doSave(IProgressMonitor monitor) {
		fTextEditor.doSave(monitor);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ui.ISaveablePart#doSaveAs()
	 */
	public void doSaveAs() {
		fTextEditor.doSaveAs();
		/*
		 * Update the design viewer since the editor input would have changed
		 * to the new file.
		 */
		if (fDesignViewer != null) {
			fDesignViewer.setDocument(getDocument());
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.core.runtime.IAdaptable#getAdapter(java.lang.Class)
	 */
	@SuppressWarnings("unchecked")
	@Override
	public <T> T getAdapter(Class<T> key) {
		T result = null;

		// we extend superclass, not override it, so allow it first
		// chance to satisfy request.
		result = super.getAdapter(key);

		if (result == null) {
			if (key == IDesignViewer.class) {
				result = (T) fDesignViewer;

			}
			else if (key.equals(IGotoMarker.class)) {
				result = (T) (IGotoMarker) marker -> XMLMultiPageEditorPart.this.gotoMarker(marker);
			}
			else {
				/*
				 * DMW: I'm bullet-proofing this because its been reported (on
				 * very early versions) a null pointer sometimes happens here
				 * on startup, when an editor has been left open when
				 * workbench shutdown.
				 */
				if (fTextEditor != null) {
					result = fTextEditor.getAdapter(key);
				}
			}
		}
		return result;
	}

	private IDocument getDocument() {
		IDocument document = null;
		if (fTextEditor != null) {
			final IDocumentProvider provider = fTextEditor.getDocumentProvider();
			if (provider != null) {
				document = provider.getDocument(fTextEditor.getEditorInput());
			}
		}
		return document;
	}

	private IPreferenceStore getPreferenceStore() {
		return XMLUIPlugin.getDefault().getPreferenceStore();
	}

	StructuredTextEditor getTextEditor() {
		return fTextEditor;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ui.IWorkbenchPart#getTitle()
	 */
	public String getTitle() {
		String title = null;
		if (getTextEditor() == null) {
			if (getEditorInput() != null) {
				title = getEditorInput().getName();
			}
		}
		else {
			title = getTextEditor().getTitle();
		}
		if (title == null) {
			title = getPartName();
		}
		return title;
	}

	void gotoMarker(IMarker marker) {
		setActivePage(fSourcePageIndex);
		IDE.gotoMarker(fTextEditor, marker);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ui.IEditorPart#init(org.eclipse.ui.IEditorSite,
	 * org.eclipse.ui.IEditorInput)
	 */
	@Override
	public void init(IEditorSite site, IEditorInput input) throws PartInitException {
		try {
			super.init(site, input);
			fTextEditor = createTextEditor();
			fTextEditor.setEditorPart(this);
			// we want to listen for our own activation
			fActivationListener = new ActivationListener(site.getWorkbenchWindow().getPartService());
		}
		catch (Exception e) {
			Logger.logException("exception initializing " + getClass().getName(), e); //$NON-NLS-1$
		}
		setPartName(input.getName());
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ui.ISaveablePart#isSaveAsAllowed()
	 */
	public boolean isSaveAsAllowed() {
		return (fTextEditor != null) && fTextEditor.isSaveAsAllowed();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ui.ISaveablePart#isSaveOnCloseNeeded()
	 */
	@Override
	public boolean isSaveOnCloseNeeded() {
		// overriding super class since it does a lowly isDirty!
		if (fTextEditor != null) {
			return fTextEditor.isSaveOnCloseNeeded();
		}
		return isDirty();
	}
	
	/**
	 * Prevents the creation of the in-editor toolbar, if called before
	 * createPageContainer() during editor initialization.
	 */
	protected final void noToolbar() {
		fAllocateToolbar = false;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ui.part.MultiPageEditorPart#pageChange(int)
	 */
	@Override
	protected void pageChange(int newPageIndex) {
		if (newPageIndex == fSourcePageIndex && fDesignViewer != null) {
			ISelectionProvider provider = fDesignViewer.getSelectionProvider();
			if (provider != null) {
				getTextEditor().getSelectionProvider().setSelection(provider.getSelection());
			}
		}

		super.pageChange(newPageIndex);
		saveLastActivePageIndex(newPageIndex);

		if (newPageIndex == fDesignPageIndex && fDesignViewer != null) {
			// design page isn't an IEditorPart, therefore we have to send
			// selection changes ourselves
			ISelectionProvider selectionProvider = fDesignViewer.getSelectionProvider();
			if (selectionProvider != null) {
				SelectionChangedEvent event = new SelectionChangedEvent(selectionProvider, selectionProvider.getSelection());
				((MultiPageSelectionProvider) getSite().getSelectionProvider()).fireSelectionChanged(event);
				((MultiPageSelectionProvider) getSite().getSelectionProvider()).firePostSelectionChanged(event);
			}
		}
	}

	private void saveLastActivePageIndex(int newPageIndex) {
		// save the last active page index to preference manager
		getPreferenceStore().setValue(getEditorSite().getId() + "." + IXMLPreferenceNames.LAST_ACTIVE_PAGE, newPageIndex); //$NON-NLS-1$
	}

	@Override
	public void setFocus() {
		super.setFocus();
		Control control = fDesignViewer.getControl();
		control.setFocus();
		// 271382 - Focus not set properly after activating XML editor
		control.forceFocus();
	}

	@Override
	public void setInitializationData(IConfigurationElement cfig, String propertyName, Object data) {
		super.setInitializationData(cfig, propertyName, data);
		fPageInitializer = new PageInitializationData(cfig, propertyName, data);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ui.part.EditorPart#setInput(org.eclipse.ui.IEditorInput)
	 */
	@Override
	protected void setInput(IEditorInput input) {
		/*
		 * If driven from the Source page, it's "model" may not be up to date
		 * with (or even exist for) the input just yet. Later notification
		 * from the TextViewer could set us straight, although it's not
		 * guaranteed to happen after the model has been created.
		 */
		super.setInput(input);
		if (fDesignViewer != null) {
			fDesignViewer.setDocument(getDocument());
		}
		setPartName(input.getName());
	}

	void updateStatusLine(ISelection selection) {
		IStatusLineManager statusLineManager = getEditorSite().getActionBars().getStatusLineManager();
		if (fStatusLineLabelProvider != null && statusLineManager != null) {
			String text = null;
			Image image = null;
			if (selection instanceof IStructuredSelection && !selection.isEmpty()) {
				Object firstElement = ((IStructuredSelection) selection).getFirstElement();
				if (firstElement != null) {
					text = fStatusLineLabelProvider.getText(firstElement);
					image = fStatusLineLabelProvider.getImage((firstElement));
				}
			}
			if (image == null) {
				statusLineManager.setMessage(text);
			}
			else {
				statusLineManager.setMessage(image, text);
			}
		}
	}

	public INavigationLocation createEmptyNavigationLocation() {
		if (getActivePage() == fDesignPageIndex) {
			return new DesignPageNavigationLocation(this, fDesignViewer, false);
		}
		// Makes sure that the text editor is returned
		return fTextEditor.createEmptyNavigationLocation();
	}

	public INavigationLocation createNavigationLocation() {
		if (getActivePage() == fDesignPageIndex) {
			return new DesignPageNavigationLocation(this, fDesignViewer, true);
		}
		// Makes sure that the text editor is returned
		return fTextEditor.createNavigationLocation();
	}

	public void saveState(IMemento memento) {
		if (fTextEditor != null) {
			fTextEditor.saveState(memento);
		}
	}

	public void restoreState(IMemento memento) {
		if (fTextEditor != null) {
			fTextEditor.restoreState(memento);
		}
	}
}
