/*
* Copyright (c) 2004 Christopher Lenz 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:
* Christopher Lenz - initial API and implementation
*
* $Id: StructuredTextEditor.java,v 1.2 2006-10-21 23:13:54 pombredanne Exp $
*/
package net.sourceforge.phpeclipse.ui.editor;
import net.sourceforge.phpeclipse.core.model.ISourceModel;
import net.sourceforge.phpeclipse.core.model.ISourceReference;
import net.sourceforge.phpeclipse.ui.text.IReconcilingParticipant;
import net.sourceforge.phpeclipse.ui.views.outline.ModelBasedOutlinePage;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.editors.text.TextEditor;
import org.eclipse.ui.texteditor.IUpdate;
import org.eclipse.ui.views.contentoutline.IContentOutlinePage;
/**
* Abstract base class for editors that keep a source model synchronized with
* the textual contants being edited.
*/
public abstract class StructuredTextEditor extends TextEditor implements
IReconcilingParticipant {
// Inner Classes -----------------------------------------------------------
/**
* Listens to changes to the selection in the outline page, and changes the
* selection and highlight range in the editor accordingly.
*/
private class OutlineSelectionChangedListener implements
ISelectionChangedListener {
/*
* @see ISelectionChangedListener#selectionChanged(SelectionChangedEvent)
*/
public void selectionChanged(SelectionChangedEvent event) {
IStructuredSelection selection = (IStructuredSelection) event
.getSelection();
if (selection.isEmpty()) {
resetHighlightRange();
} else {
ISourceReference element = (ISourceReference) selection
.getFirstElement();
highlightElement(element, true);
}
}
}
// Instance Variables ------------------------------------------------------
/**
* The associated outline page.
*/
private IContentOutlinePage outlinePage;
/**
* Listens to changes in the outline page's selection to update the editor
* selection and highlight range.
*/
private ISelectionChangedListener outlinePageSelectionListener;
// TextEditor Implementation -----------------------------------------------
/*
* @see org.eclipse.core.runtime.IAdaptable#getAdapter(java.lang.Class)
*/
public Object getAdapter(Class adapter) {
if (adapter.equals(IContentOutlinePage.class)) {
if (outlinePage == null) {
outlinePage = createOutlinePage();
outlinePageSelectionListener = new OutlineSelectionChangedListener();
outlinePage
.addSelectionChangedListener(outlinePageSelectionListener);
}
return outlinePage;
}
return super.getAdapter(adapter);
}
/*
* @see org.eclipse.ui.texteditor.AbstractTextEditor#handleCursorPositionChanged()
*/
protected void handleCursorPositionChanged() {
super.handleCursorPositionChanged();
highlightElement(computeHighlightRangeSourceReference(), false);
synchronizeOutlinePageSelection();
}
// IReconcilingParticipant Implementation ----------------------------------
/*
* @see IReconcilingParticipant#reconciled()
*/
public void reconciled() {
Shell shell = getSite().getShell();
if ((shell != null) && !shell.isDisposed()) {
shell.getDisplay().asyncExec(new Runnable() {
public void run() {
if (outlinePage instanceof IUpdate) {
((IUpdate) outlinePage).update();
}
synchronizeOutlinePageSelection();
}
});
}
}
// Public Methods ----------------------------------------------------------
/**
* Computes and returns the source reference that includes the caret and
* serves as provider for the outline page selection and the editor range
* indication.
*
* @return the computed source reference
*/
public ISourceReference computeHighlightRangeSourceReference() {
ISourceViewer sourceViewer = getSourceViewer();
if (sourceViewer == null) {
return null;
}
StyledText styledText = sourceViewer.getTextWidget();
if ((styledText == null) || styledText.isDisposed()) {
return null;
}
int offset = sourceViewer.getVisibleRegion().getOffset();
int caret = offset + styledText.getCaretOffset();
return getElementAt(caret);
}
/**
* Returns the source model element at the specified offset.
*
* @param offset
* the offset into the document
* @return the element at the given offset, or null if no model
* is available or there is no element at the offset
*/
public ISourceReference getElementAt(int offset) {
ISourceReference retVal = null;
ISourceModel model = getSourceModel();
if (model != null) {
ISourceReference elements[] = model.getElements();
retVal = getElementAt(model, elements, offset);
}
return retVal;
}
/**
* Returns the structure source model corresponding to the document
* currently being edited.
*
* Concrete implementations must implement this method to return the model
* appropriate to the content being edited.
*
* @return the source model
*/
public abstract ISourceModel getSourceModel();
/**
* Informs the editor that its outliner has been closed.
*
* TODO There must be a more elegant way to get notified when the outline
* page was closed. Otherwise move this method into an interface
*/
public void outlinePageClosed() {
if (outlinePage != null) {
outlinePage
.removeSelectionChangedListener(outlinePageSelectionListener);
outlinePage = null;
resetHighlightRange();
}
}
/**
* Synchronizes the outliner selection with the given element position in
* the editor.
*
* @param element
* the java element to select
*/
public void synchronizeOutlinePage(ISourceReference element) {
if (outlinePage != null) {
outlinePage
.removeSelectionChangedListener(outlinePageSelectionListener);
if (outlinePage instanceof ModelBasedOutlinePage) {
((ModelBasedOutlinePage) outlinePage).select(element);
}
outlinePage
.addSelectionChangedListener(outlinePageSelectionListener);
}
}
/**
* Synchronizes the outliner selection with the currently highlighted source
* reference.
*/
public void synchronizeOutlinePage() {
ISourceReference element = computeHighlightRangeSourceReference();
synchronizeOutlinePage(element);
}
// Protected Methods -------------------------------------------------------
protected abstract IContentOutlinePage createOutlinePage();
/**
* Highlights the given element.
*
* @param element
* the element that should be highlighted
* @param moveCursor
* whether the cursor should be moved to the element
*/
protected final void highlightElement(ISourceReference element,
boolean moveCursor) {
if (element != null) {
IRegion highlightRegion = element.getSourceRegion();
setHighlightRange(highlightRegion.getOffset(), highlightRegion
.getLength(), moveCursor);
} else {
resetHighlightRange();
}
}
/**
* Returns whether the outline page is currently linked with the editor,
* meaning that its selection should automatically be updated to reflect the
* current cursor position.
*
* @return true if the outline page is linked with the editor,
* false otherwise
*/
protected abstract boolean isOutlineLinkedWithEditor();
// Private Methods ---------------------------------------------------------
/**
* Recursively searches the specified list of elements managed by the given
* model for the element that covers the specified offfset with minimal
* padding.
*
* @param model
* the source model
* @param elements
* the current list of elements
* @param offset
* the offset into the document
* @return the model element at the specified offset, or null if
* no element could be found
*/
private static ISourceReference getElementAt(ISourceModel model,
ISourceReference elements[], int offset) {
ISourceReference retVal = null;
for (int i = 0; i < elements.length; i++) {
ISourceReference element = elements[i];
IRegion region = element.getSourceRegion();
if ((offset > region.getOffset())
&& (offset < (region.getOffset() + region.getLength()))) {
ISourceReference[] children = model.getChildren(element);
if (children.length > 0) {
retVal = getElementAt(model, children, offset);
if (retVal != null) {
break;
}
}
if (retVal == null) {
retVal = element;
}
}
}
return retVal;
}
private void synchronizeOutlinePageSelection() {
IPreferenceStore store = getPreferenceStore();
if (store != null) {
boolean linkWithEditor = isOutlineLinkedWithEditor();
if (linkWithEditor) {
synchronizeOutlinePage(computeHighlightRangeSourceReference());
}
}
}
}