misc changes
[phpeclipse.git] / net.sourceforge.phpeclipse.ui / src / net / sourceforge / phpeclipse / ui / editor / StructuredTextEditor.java
1 /*
2  * Copyright (c) 2004 Christopher Lenz and others
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Common Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/cpl-v10.html
7  * 
8  * Contributors:
9  *     Christopher Lenz - initial API and implementation
10  * 
11  * $Id: StructuredTextEditor.java,v 1.1 2004-09-02 18:26:30 jsurfer Exp $
12  */
13
14 package net.sourceforge.phpeclipse.ui.editor;
15
16 import net.sourceforge.phpeclipse.core.model.ISourceModel;
17 import net.sourceforge.phpeclipse.core.model.ISourceReference;
18 import net.sourceforge.phpeclipse.ui.text.IReconcilingParticipant;
19 import net.sourceforge.phpeclipse.ui.views.outline.ModelBasedOutlinePage;
20
21 import org.eclipse.jface.preference.IPreferenceStore;
22 import org.eclipse.jface.text.IRegion;
23 import org.eclipse.jface.text.source.ISourceViewer;
24 import org.eclipse.jface.viewers.ISelectionChangedListener;
25 import org.eclipse.jface.viewers.IStructuredSelection;
26 import org.eclipse.jface.viewers.SelectionChangedEvent;
27 import org.eclipse.swt.custom.StyledText;
28 import org.eclipse.swt.widgets.Shell;
29 import org.eclipse.ui.editors.text.TextEditor;
30 import org.eclipse.ui.texteditor.IUpdate;
31 import org.eclipse.ui.views.contentoutline.IContentOutlinePage;
32
33 /**
34  * Abstract base class for editors that keep a source model synchronized with
35  * the textual contants being edited.
36  */
37 public abstract class StructuredTextEditor extends TextEditor
38         implements IReconcilingParticipant {
39
40         // Inner Classes -----------------------------------------------------------
41
42     /**
43      * Listens to changes to the selection in the outline page, and changes the
44      * selection and highlight range in the editor accordingly.
45      */
46     private class OutlineSelectionChangedListener
47         implements ISelectionChangedListener {
48
49         /*
50          * @see ISelectionChangedListener#selectionChanged(SelectionChangedEvent)
51          */
52         public void selectionChanged(SelectionChangedEvent event) {
53             IStructuredSelection selection =
54                 (IStructuredSelection) event.getSelection();
55             if (selection.isEmpty()) {
56                 resetHighlightRange();
57             } else {
58                 ISourceReference element = (ISourceReference)
59                                         selection.getFirstElement();
60                 highlightElement(element, true);
61             }
62         }
63
64     }
65
66         // Instance Variables ------------------------------------------------------
67
68         /**
69          * The associated outline page.
70          */
71         private IContentOutlinePage outlinePage;
72
73         /**
74          * Listens to changes in the outline page's selection to update the editor
75          * selection and highlight range.
76          */
77         private ISelectionChangedListener outlinePageSelectionListener;
78
79         // TextEditor Implementation -----------------------------------------------
80
81         /*
82          * @see org.eclipse.core.runtime.IAdaptable#getAdapter(java.lang.Class)
83          */
84         public Object getAdapter(Class adapter) {
85                 if (adapter.equals(IContentOutlinePage.class)) {
86                         if (outlinePage == null) {
87                                 outlinePage = createOutlinePage();
88                 outlinePageSelectionListener =
89                     new OutlineSelectionChangedListener();
90                 outlinePage.addSelectionChangedListener(
91                 outlinePageSelectionListener);
92                         }
93                         return outlinePage;
94                 }
95                 return super.getAdapter(adapter);
96         }
97
98         /*
99          * @see org.eclipse.ui.texteditor.AbstractTextEditor#handleCursorPositionChanged()
100          */
101         protected void handleCursorPositionChanged() {
102                 super.handleCursorPositionChanged();
103                 highlightElement(computeHighlightRangeSourceReference(), false);
104                 synchronizeOutlinePageSelection();
105         }
106
107         // IReconcilingParticipant Implementation ----------------------------------
108
109         /* 
110          * @see IReconcilingParticipant#reconciled()
111          */
112         public void reconciled() {
113                 Shell shell = getSite().getShell();
114                 if ((shell != null) && !shell.isDisposed()) {
115                         shell.getDisplay().asyncExec(new Runnable() {
116                                 public void run() {
117                                         if (outlinePage instanceof IUpdate) {
118                                                 ((IUpdate) outlinePage).update();
119                                         }
120                                         synchronizeOutlinePageSelection();
121                                 }
122                         });
123                 }
124         }
125
126         // Public Methods ----------------------------------------------------------
127
128         /**
129          * Computes and returns the source reference that includes the caret and
130          * serves as provider for the outline page selection and the editor range
131          * indication.
132          * 
133          * @return the computed source reference
134          */
135         public ISourceReference computeHighlightRangeSourceReference() {
136                 ISourceViewer sourceViewer = getSourceViewer();
137                 if (sourceViewer == null) {
138                         return null;
139                 }
140                 StyledText styledText = sourceViewer.getTextWidget();
141                 if ((styledText == null) || styledText.isDisposed()) {
142                         return null;
143                 }
144                 int offset = sourceViewer.getVisibleRegion().getOffset();
145                 int caret = offset + styledText.getCaretOffset();
146
147                 return getElementAt(caret);
148         }
149
150         /**
151          * Returns the source model element at the specified offset.
152          * 
153          * @param offset the offset into the document
154          * @return the element at the given offset, or <tt>null</tt> if no model is
155          *         available or there is no element at the offset
156          */
157         public ISourceReference getElementAt(int offset) {
158                 ISourceReference retVal = null;
159                 ISourceModel model = getSourceModel();
160                 if (model != null) {
161                         ISourceReference elements[] = model.getElements();
162                         retVal = getElementAt(model, elements, offset);
163                 }
164                 return retVal;
165         }
166
167         /**
168          * Returns the structure source model corresponding to the document
169          * currently being edited.
170          * 
171          * Concrete implementations must implement this method to return the model
172          * appropriate to the content being edited.
173          * 
174          * @return the source model
175          */
176         public abstract ISourceModel getSourceModel();
177
178         /**
179          * Informs the editor that its outliner has been closed.
180          * 
181          * TODO There must be a more elegant way to get notified when the outline 
182          *      page was closed. Otherwise move this method into an interface
183          */
184         public void outlinePageClosed() {
185                 if (outlinePage != null) {
186                         outlinePage.removeSelectionChangedListener(
187                                         outlinePageSelectionListener);
188                         outlinePage = null;
189                         resetHighlightRange();
190                 }
191         }
192
193         /**
194          * Synchronizes the outliner selection with the given element position in 
195          * the editor.
196          * 
197          * @param element the java element to select
198          */
199         public void synchronizeOutlinePage(ISourceReference element) {
200                 if (outlinePage != null) {
201                         outlinePage.removeSelectionChangedListener(
202                                 outlinePageSelectionListener);
203                         if (outlinePage instanceof ModelBasedOutlinePage) {
204                                 ((ModelBasedOutlinePage) outlinePage).select(element);
205                         }
206                         outlinePage.addSelectionChangedListener(
207                                 outlinePageSelectionListener);
208                 }
209         }
210
211         /**
212          * Synchronizes the outliner selection with the currently highlighted source
213          * reference.
214          */
215         public void synchronizeOutlinePage() {
216                 ISourceReference element = computeHighlightRangeSourceReference();
217                 synchronizeOutlinePage(element);
218         }
219
220         // Protected Methods -------------------------------------------------------
221
222         protected abstract IContentOutlinePage createOutlinePage();
223
224         /**
225          * Highlights the given element.
226          * 
227          * @param element the element that should be highlighted
228          * @param moveCursor whether the cursor should be moved to the element
229          */
230         protected final void highlightElement(ISourceReference element,
231                         boolean moveCursor) {
232                 if (element != null) {
233                         IRegion highlightRegion = element.getSourceRegion();
234                         setHighlightRange(highlightRegion.getOffset(),
235                                 highlightRegion.getLength(), moveCursor);
236                 } else {
237                         resetHighlightRange();
238                 }
239         }
240
241         /**
242          * Returns whether the outline page is currently linked with the editor,
243          * meaning that its selection should automatically be updated to reflect the
244          * current cursor position.
245          * 
246          * @return <tt>true</tt> if the outline page is linked with the editor,
247          *         <tt>false</tt> otherwise
248          */
249         protected abstract boolean isOutlineLinkedWithEditor();
250
251         // Private Methods ---------------------------------------------------------
252
253         /**
254          * Recursively searches the specified list of elements managed by the given
255          * model for the element that covers the specified offfset with minimal 
256          * padding.
257          * 
258          * @param model the source model
259          * @param elements the current list of elements
260          * @param offset the offset into the document
261          * @return the model element at the specified offset, or <tt>null</tt> if
262          *         no element could be found
263          */
264         private static ISourceReference getElementAt(
265                         ISourceModel model, ISourceReference elements[], int offset) {
266                 ISourceReference retVal = null;
267                 for (int i = 0; i < elements.length; i++) {
268                         ISourceReference element = elements[i];
269                         IRegion region = element.getSourceRegion();
270                         if ((offset > region.getOffset())
271                          && (offset < (region.getOffset() + region.getLength()))) {
272                                 ISourceReference[] children = model.getChildren(element);
273                                 if (children.length > 0) {
274                                         retVal = getElementAt(model, children, offset);
275                                         if (retVal != null) {
276                                                 break;
277                                         }
278                                 }
279                                 if (retVal == null) {
280                                         retVal = element;
281                                 }
282                         }
283                 }
284                 return retVal;
285         }
286
287         private void synchronizeOutlinePageSelection() {
288                 IPreferenceStore store = getPreferenceStore();
289                 if (store != null) {
290                         boolean linkWithEditor = isOutlineLinkedWithEditor();
291                         if (linkWithEditor) {
292                                 synchronizeOutlinePage(computeHighlightRangeSourceReference());
293                         }
294                 }
295         }
296
297 }