KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > jface > text > source > projection > ProjectionViewer


1 /*******************************************************************************
2  * Copyright (c) 2000, 2007 IBM Corporation and others.
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Eclipse Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/epl-v10.html
7  *
8  * Contributors:
9  * IBM Corporation - initial API and implementation
10  *******************************************************************************/

11 package org.eclipse.jface.text.source.projection;
12
13
14 import java.util.ArrayList JavaDoc;
15 import java.util.Iterator JavaDoc;
16 import java.util.List JavaDoc;
17
18 import org.eclipse.swt.SWTError;
19 import org.eclipse.swt.custom.ST;
20 import org.eclipse.swt.custom.StyledText;
21 import org.eclipse.swt.dnd.Clipboard;
22 import org.eclipse.swt.dnd.DND;
23 import org.eclipse.swt.dnd.TextTransfer;
24 import org.eclipse.swt.dnd.Transfer;
25 import org.eclipse.swt.events.VerifyEvent;
26 import org.eclipse.swt.graphics.Point;
27 import org.eclipse.swt.widgets.Composite;
28 import org.eclipse.swt.widgets.Display;
29
30 import org.eclipse.core.runtime.Assert;
31 import org.eclipse.core.runtime.NullProgressMonitor;
32
33 import org.eclipse.jface.text.BadLocationException;
34 import org.eclipse.jface.text.DocumentEvent;
35 import org.eclipse.jface.text.FindReplaceDocumentAdapter;
36 import org.eclipse.jface.text.IDocument;
37 import org.eclipse.jface.text.IDocumentInformationMappingExtension;
38 import org.eclipse.jface.text.IDocumentListener;
39 import org.eclipse.jface.text.IRegion;
40 import org.eclipse.jface.text.ISlaveDocumentManager;
41 import org.eclipse.jface.text.ITextViewerExtension5;
42 import org.eclipse.jface.text.Position;
43 import org.eclipse.jface.text.Region;
44 import org.eclipse.jface.text.TextUtilities;
45 import org.eclipse.jface.text.projection.ProjectionDocument;
46 import org.eclipse.jface.text.projection.ProjectionDocumentEvent;
47 import org.eclipse.jface.text.projection.ProjectionDocumentManager;
48 import org.eclipse.jface.text.source.Annotation;
49 import org.eclipse.jface.text.source.AnnotationModelEvent;
50 import org.eclipse.jface.text.source.CompositeRuler;
51 import org.eclipse.jface.text.source.IAnnotationModel;
52 import org.eclipse.jface.text.source.IAnnotationModelExtension;
53 import org.eclipse.jface.text.source.IAnnotationModelListener;
54 import org.eclipse.jface.text.source.IAnnotationModelListenerExtension;
55 import org.eclipse.jface.text.source.IOverviewRuler;
56 import org.eclipse.jface.text.source.IVerticalRuler;
57 import org.eclipse.jface.text.source.IVerticalRulerColumn;
58 import org.eclipse.jface.text.source.SourceViewer;
59
60
61 /**
62  * A projection source viewer is a source viewer which supports multiple visible
63  * regions which can dynamically be changed.
64  * <p>
65  * A projection source viewer uses a <code>ProjectionDocumentManager</code>
66  * for the management of the visible document.</p>
67  * <p>
68  * NOTE: The <code>ProjectionViewer</code> only supports projections that cover full lines.
69  * </p>
70  * <p>
71  * This class should not be subclassed.</p>
72  *
73  * @since 3.0
74  */

75 public class ProjectionViewer extends SourceViewer implements ITextViewerExtension5 {
76
77     private static final int BASE= INFORMATION; // see ISourceViewer.INFORMATION
78

79     /** Operation constant for the expand operation. */
80     public static final int EXPAND= BASE + 1;
81     /** Operation constant for the collapse operation. */
82     public static final int COLLAPSE= BASE + 2;
83     /** Operation constant for the toggle projection operation. */
84     public static final int TOGGLE= BASE + 3;
85     /** Operation constant for the expand all operation. */
86     public static final int EXPAND_ALL= BASE + 4;
87     /**
88      * Operation constant for the collapse all operation.
89      *
90      * @since 3.2
91      */

92     public static final int COLLAPSE_ALL= BASE + 5;
93
94     /**
95      * Internal listener to changes of the annotation model.
96      */

97     private class AnnotationModelListener implements IAnnotationModelListener, IAnnotationModelListenerExtension {
98
99         /*
100          * @see org.eclipse.jface.text.source.IAnnotationModelListener#modelChanged(org.eclipse.jface.text.source.IAnnotationModel)
101          */

102         public void modelChanged(IAnnotationModel model) {
103             processModelChanged(model, null);
104         }
105
106         /*
107          * @see org.eclipse.jface.text.source.IAnnotationModelListenerExtension#modelChanged(org.eclipse.jface.text.source.AnnotationModelEvent)
108          */

109         public void modelChanged(AnnotationModelEvent event) {
110             processModelChanged(event.getAnnotationModel(), event);
111         }
112
113         private void processModelChanged(IAnnotationModel model, AnnotationModelEvent event) {
114             if (model == fProjectionAnnotationModel) {
115
116                 if (fProjectionSummary != null)
117                     fProjectionSummary.updateSummaries(new NullProgressMonitor());
118                 processCatchupRequest(event);
119
120             } else if (model == getAnnotationModel() && fProjectionSummary != null)
121                 fProjectionSummary.updateSummaries(new NullProgressMonitor());
122         }
123     }
124
125     /**
126      * Executes the 'replaceVisibleDocument' operation when called the first time. Self-destructs afterwards.
127      */

128     private class ReplaceVisibleDocumentExecutor implements IDocumentListener {
129
130         private IDocument fSlaveDocument;
131         private IDocument fExecutionTrigger;
132
133         /**
134          * Creates a new executor in order to free the given slave document.
135          *
136          * @param slaveDocument the slave document to free
137          */

138         public ReplaceVisibleDocumentExecutor(IDocument slaveDocument) {
139             fSlaveDocument= slaveDocument;
140         }
141
142         /**
143          * Installs this executor on the given trigger document.
144          *
145          * @param executionTrigger the trigger document
146          */

147         public void install(IDocument executionTrigger) {
148             if (executionTrigger != null && fSlaveDocument != null) {
149                 fExecutionTrigger= executionTrigger;
150                 fExecutionTrigger.addDocumentListener(this);
151             }
152         }
153
154         /*
155          * @see org.eclipse.jface.text.IDocumentListener#documentAboutToBeChanged(org.eclipse.jface.text.DocumentEvent)
156          */

157         public void documentAboutToBeChanged(DocumentEvent event) {
158         }
159
160         /*
161          * @see org.eclipse.jface.text.IDocumentListener#documentChanged(org.eclipse.jface.text.DocumentEvent)
162          */

163         public void documentChanged(DocumentEvent event) {
164             fExecutionTrigger.removeDocumentListener(this);
165             executeReplaceVisibleDocument(fSlaveDocument);
166         }
167     }
168
169     /**
170      * A command representing a change of the projection document. This can be either
171      * adding a master document range, removing a master document change, or invalidating
172      * the viewer text presentation.
173      */

174     private static class ProjectionCommand {
175
176         final static int ADD= 0;
177         final static int REMOVE= 1;
178         final static int INVALIDATE_PRESENTATION= 2;
179
180         ProjectionDocument fProjection;
181         int fType;
182         int fOffset;
183         int fLength;
184
185         ProjectionCommand(ProjectionDocument projection, int type, int offset, int length) {
186             fProjection= projection;
187             fType= type;
188             fOffset= offset;
189             fLength= length;
190         }
191
192         ProjectionCommand(int offset, int length) {
193             fType= INVALIDATE_PRESENTATION;
194             fOffset= offset;
195             fLength= length;
196         }
197
198         int computeExpectedCosts() {
199
200             switch(fType) {
201                 case ADD: {
202                     try {
203                         IRegion[] gaps= fProjection.computeUnprojectedMasterRegions(fOffset, fLength);
204                         return gaps == null ? 0 : gaps.length;
205                     } catch (BadLocationException x) {
206                     }
207                     break;
208                 }
209                 case REMOVE: {
210                     try {
211                         IRegion[] fragments= fProjection.computeProjectedMasterRegions(fOffset, fLength);
212                         return fragments == null ? 0 : fragments.length;
213                     } catch (BadLocationException x) {
214                     }
215                     break;
216                 }
217             }
218             return 0;
219         }
220     }
221
222     /**
223      * The queue of projection command objects.
224      */

225     private static class ProjectionCommandQueue {
226
227         final static int REDRAW_COSTS= 15;
228         final static int INVALIDATION_COSTS= 10;
229
230         List JavaDoc fList= new ArrayList JavaDoc(15);
231         int fExpectedExecutionCosts= -1;
232
233
234         void add(ProjectionCommand command) {
235             fList.add(command);
236         }
237
238         Iterator JavaDoc iterator() {
239             return fList.iterator();
240         }
241
242         void clear() {
243             fList.clear();
244             fExpectedExecutionCosts= -1;
245         }
246
247         boolean passedRedrawCostsThreshold() {
248             if (fExpectedExecutionCosts == -1)
249                 computeExpectedExecutionCosts();
250             return fExpectedExecutionCosts > REDRAW_COSTS;
251         }
252
253         boolean passedInvalidationCostsThreshold() {
254             if (fExpectedExecutionCosts == -1)
255                 computeExpectedExecutionCosts();
256             return fExpectedExecutionCosts > INVALIDATION_COSTS;
257         }
258
259         private void computeExpectedExecutionCosts() {
260             int max_costs= Math.max(REDRAW_COSTS, INVALIDATION_COSTS);
261             fExpectedExecutionCosts= fList.size();
262             if (fExpectedExecutionCosts <= max_costs) {
263                 ProjectionCommand command;
264                 Iterator JavaDoc e= fList.iterator();
265                 while (e.hasNext()) {
266                     command= (ProjectionCommand) e.next();
267                     fExpectedExecutionCosts += command.computeExpectedCosts();
268                     if (fExpectedExecutionCosts > max_costs)
269                         break;
270                 }
271             }
272         }
273     }
274
275     /** The projection annotation model used by this viewer. */
276     private ProjectionAnnotationModel fProjectionAnnotationModel;
277     /** The annotation model listener */
278     private IAnnotationModelListener fAnnotationModelListener= new AnnotationModelListener();
279     /** The projection summary. */
280     private ProjectionSummary fProjectionSummary;
281     /** Indication that an annotation world change has not yet been processed. */
282     private boolean fPendingAnnotationWorldChange= false;
283     /** Indication whether projection changes in the visible document should be considered. */
284     private boolean fHandleProjectionChanges= true;
285     /** The list of projection listeners. */
286     private List JavaDoc fProjectionListeners;
287     /** Internal lock for protecting the list of pending requests */
288     private Object JavaDoc fLock= new Object JavaDoc();
289     /** The list of pending requests */
290     private List JavaDoc fPendingRequests= new ArrayList JavaDoc();
291     /** The replace-visible-document execution trigger */
292     private IDocument fReplaceVisibleDocumentExecutionTrigger;
293     /** <code>true</code> if projection was on the last time we switched to segmented mode. */
294     private boolean fWasProjectionEnabled;
295     /** The queue of projection commands used to assess the costs of projection changes. */
296     private ProjectionCommandQueue fCommandQueue;
297     /**
298      * The amount of lines deleted by the last document event issued by the
299      * visible document event.
300      * @since 3.1
301      */

302     private int fDeletedLines;
303
304
305     /**
306      * Creates a new projection source viewer.
307      *
308      * @param parent the SWT parent control
309      * @param ruler the vertical ruler
310      * @param overviewRuler the overview ruler
311      * @param showsAnnotationOverview <code>true</code> if the overview ruler should be shown
312      * @param styles the SWT style bits
313      */

314     public ProjectionViewer(Composite parent, IVerticalRuler ruler, IOverviewRuler overviewRuler, boolean showsAnnotationOverview, int styles) {
315         super(parent, ruler, overviewRuler, showsAnnotationOverview, styles);
316     }
317
318     /**
319      * Sets the projection summary for this viewer.
320      *
321      * @param projectionSummary the projection summary.
322      */

323     public void setProjectionSummary(ProjectionSummary projectionSummary) {
324         fProjectionSummary= projectionSummary;
325     }
326
327     /**
328      * Adds the projection annotation model to the given annotation model.
329      *
330      * @param model the model to which the projection annotation model is added
331      */

332     private void addProjectionAnnotationModel(IAnnotationModel model) {
333         if (model instanceof IAnnotationModelExtension) {
334             IAnnotationModelExtension extension= (IAnnotationModelExtension) model;
335             extension.addAnnotationModel(ProjectionSupport.PROJECTION, fProjectionAnnotationModel);
336             model.addAnnotationModelListener(fAnnotationModelListener);
337         }
338     }
339
340     /**
341      * Removes the projection annotation model from the given annotation model.
342      *
343      * @param model the mode from which the projection annotation model is removed
344      * @return the removed projection annotation model or <code>null</code> if there was none
345      */

346     private IAnnotationModel removeProjectionAnnotationModel(IAnnotationModel model) {
347         if (model instanceof IAnnotationModelExtension) {
348             model.removeAnnotationModelListener(fAnnotationModelListener);
349             IAnnotationModelExtension extension= (IAnnotationModelExtension) model;
350             return extension.removeAnnotationModel(ProjectionSupport.PROJECTION);
351         }
352         return null;
353     }
354
355     /*
356      * @see org.eclipse.jface.text.source.SourceViewer#setDocument(org.eclipse.jface.text.IDocument, org.eclipse.jface.text.source.IAnnotationModel, int, int)
357      */

358     public void setDocument(IDocument document, IAnnotationModel annotationModel, int modelRangeOffset, int modelRangeLength) {
359         boolean wasProjectionEnabled= false;
360
361         synchronized (fLock) {
362             fPendingRequests.clear();
363         }
364
365         if (fProjectionAnnotationModel != null) {
366             wasProjectionEnabled= removeProjectionAnnotationModel(getVisualAnnotationModel()) != null;
367             fProjectionAnnotationModel= null;
368         }
369
370         super.setDocument(document, annotationModel, modelRangeOffset, modelRangeLength);
371
372         if (wasProjectionEnabled && document != null)
373             enableProjection();
374     }
375
376     /*
377      * @see org.eclipse.jface.text.source.SourceViewer#createVisualAnnotationModel(org.eclipse.jface.text.source.IAnnotationModel)
378      */

379     protected IAnnotationModel createVisualAnnotationModel(IAnnotationModel annotationModel) {
380         IAnnotationModel model= super.createVisualAnnotationModel(annotationModel);
381         fProjectionAnnotationModel= new ProjectionAnnotationModel();
382         return model;
383     }
384
385     /**
386      * Returns the projection annotation model.
387      *
388      * @return the projection annotation model
389      */

390     public ProjectionAnnotationModel getProjectionAnnotationModel() {
391         IAnnotationModel model= getVisualAnnotationModel();
392         if (model instanceof IAnnotationModelExtension) {
393             IAnnotationModelExtension extension= (IAnnotationModelExtension) model;
394             return (ProjectionAnnotationModel) extension.getAnnotationModel(ProjectionSupport.PROJECTION);
395         }
396         return null;
397     }
398
399     /*
400      * @see org.eclipse.jface.text.TextViewer#createSlaveDocumentManager()
401      */

402     protected ISlaveDocumentManager createSlaveDocumentManager() {
403         return new ProjectionDocumentManager();
404     }
405
406     /*
407      * @see org.eclipse.jface.text.TextViewer#updateSlaveDocument(org.eclipse.jface.text.IDocument, int, int)
408      */

409     protected boolean updateSlaveDocument(IDocument slaveDocument, int modelRangeOffset, int modelRangeLength) throws BadLocationException {
410         if (slaveDocument instanceof ProjectionDocument) {
411             ProjectionDocument projection= (ProjectionDocument) slaveDocument;
412
413             int offset= modelRangeOffset;
414             int length= modelRangeLength;
415
416             if (!isProjectionMode()) {
417                 // mimic original TextViewer behavior
418
IDocument master= projection.getMasterDocument();
419                 int line= master.getLineOfOffset(modelRangeOffset);
420                 offset= master.getLineOffset(line);
421                 length= (modelRangeOffset - offset) + modelRangeLength;
422
423             }
424
425             try {
426                 fHandleProjectionChanges= false;
427                 projection.replaceMasterDocumentRanges(offset, length);
428             } finally {
429                 fHandleProjectionChanges= true;
430             }
431             return true;
432         }
433         return false;
434     }
435
436     /**
437      * Adds a projection annotation listener to this viewer. The listener may
438      * not be <code>null</code>. If the listener is already registered, this method
439      * does not have any effect.
440      *
441      * @param listener the listener to add
442      */

443     public void addProjectionListener(IProjectionListener listener) {
444
445         Assert.isNotNull(listener);
446
447         if (fProjectionListeners == null)
448             fProjectionListeners= new ArrayList JavaDoc();
449
450         if (!fProjectionListeners.contains(listener))
451             fProjectionListeners.add(listener);
452     }
453
454     /**
455      * Removes the given listener from this viewer. The listener may not be
456      * <code>null</code>. If the listener is not registered with this viewer,
457      * this method is without effect.
458      *
459      * @param listener the listener to remove
460      */

461     public void removeProjectionListener(IProjectionListener listener) {
462
463         Assert.isNotNull(listener);
464
465         if (fProjectionListeners != null) {
466             fProjectionListeners.remove(listener);
467             if (fProjectionListeners.size() == 0)
468                 fProjectionListeners= null;
469         }
470     }
471
472     /**
473      * Notifies all registered projection listeners
474      * that projection mode has been enabled.
475      */

476     protected void fireProjectionEnabled() {
477         if (fProjectionListeners != null) {
478             Iterator JavaDoc e= new ArrayList JavaDoc(fProjectionListeners).iterator();
479             while (e.hasNext()) {
480                 IProjectionListener l= (IProjectionListener) e.next();
481                 l.projectionEnabled();
482             }
483         }
484     }
485
486     /**
487      * Notifies all registered projection listeners
488      * that projection mode has been disabled.
489      */

490     protected void fireProjectionDisabled() {
491         if (fProjectionListeners != null) {
492             Iterator JavaDoc e= new ArrayList JavaDoc(fProjectionListeners).iterator();
493             while (e.hasNext()) {
494                 IProjectionListener l= (IProjectionListener) e.next();
495                 l.projectionDisabled();
496             }
497         }
498     }
499
500     /**
501      * Returns whether this viewer is in projection mode.
502      *
503      * @return <code>true</code> if this viewer is in projection mode,
504      * <code>false</code> otherwise
505      */

506     public final boolean isProjectionMode() {
507         return getProjectionAnnotationModel() != null;
508     }
509
510     /**
511      * Disables the projection mode.
512      */

513     public final void disableProjection() {
514         if (isProjectionMode()) {
515             removeProjectionAnnotationModel(getVisualAnnotationModel());
516             fProjectionAnnotationModel.removeAllAnnotations();
517             fFindReplaceDocumentAdapter= null;
518             fireProjectionDisabled();
519         }
520     }
521
522     /**
523      * Enables the projection mode.
524      */

525     public final void enableProjection() {
526         if (!isProjectionMode()) {
527             addProjectionAnnotationModel(getVisualAnnotationModel());
528             fFindReplaceDocumentAdapter= null;
529             fireProjectionEnabled();
530         }
531     }
532
533     private void expandAll() {
534         int offset= 0;
535         IDocument doc= getDocument();
536         int length= doc == null ? 0 : doc.getLength();
537         if (isProjectionMode()) {
538             fProjectionAnnotationModel.expandAll(offset, length);
539         }
540     }
541
542     private void expand() {
543         if (isProjectionMode()) {
544             Position found= null;
545             Annotation bestMatch= null;
546             Point selection= getSelectedRange();
547             for (Iterator JavaDoc e= fProjectionAnnotationModel.getAnnotationIterator(); e.hasNext();) {
548                 ProjectionAnnotation annotation= (ProjectionAnnotation) e.next();
549                 if (annotation.isCollapsed()) {
550                     Position position= fProjectionAnnotationModel.getPosition(annotation);
551                     // take the first most fine grained match
552
if (position != null && touches(selection, position))
553                         if (found == null || position.includes(found.offset) && position.includes(found.offset + found.length)) {
554                             found= position;
555                             bestMatch= annotation;
556                         }
557                 }
558             }
559
560             if (bestMatch != null) {
561                 fProjectionAnnotationModel.expand(bestMatch);
562                 revealRange(selection.x, selection.y);
563             }
564         }
565     }
566
567     private boolean touches(Point selection, Position position) {
568         return position.overlapsWith(selection.x, selection.y) || selection.y == 0 && position.offset + position.length == selection.x + selection.y;
569     }
570
571     private void collapse() {
572         if (isProjectionMode()) {
573             Position found= null;
574             Annotation bestMatch= null;
575             Point selection= getSelectedRange();
576             for (Iterator JavaDoc e= fProjectionAnnotationModel.getAnnotationIterator(); e.hasNext();) {
577                 ProjectionAnnotation annotation= (ProjectionAnnotation) e.next();
578                 if (!annotation.isCollapsed()) {
579                     Position position= fProjectionAnnotationModel.getPosition(annotation);
580                     // take the first most fine grained match
581
if (position != null && touches(selection, position))
582                         if (found == null || found.includes(position.offset) && found.includes(position.offset + position.length)) {
583                             found= position;
584                             bestMatch= annotation;
585                         }
586                 }
587             }
588
589             if (bestMatch != null) {
590                 fProjectionAnnotationModel.collapse(bestMatch);
591                 revealRange(selection.x, selection.y);
592             }
593         }
594     }
595
596     /*
597      * @since 3.2
598      */

599     private void collapseAll() {
600         int offset= 0;
601         IDocument doc= getDocument();
602         int length= doc == null ? 0 : doc.getLength();
603         if (isProjectionMode()) {
604             fProjectionAnnotationModel.collapseAll(offset, length);
605         }
606     }
607
608     /**
609      * Adds the given master range to the given projection document. While the
610      * modification is processed, the viewer no longer handles projection
611      * changes, as it is causing them.
612      *
613      * @param projection the projection document
614      * @param offset the offset in the master document
615      * @param length the length in the master document
616      * @throws BadLocationException in case the specified range is invalid
617      *
618      * @see ProjectionDocument#addMasterDocumentRange(int, int)
619      */

620     private void addMasterDocumentRange(ProjectionDocument projection, int offset, int length) throws BadLocationException {
621
622         if (fCommandQueue != null) {
623             fCommandQueue.add(new ProjectionCommand(projection, ProjectionCommand.ADD, offset, length));
624         } else {
625             try {
626                 fHandleProjectionChanges= false;
627                 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=108258
628
// make sure the document range is strictly line based
629
int end= offset + length;
630                 offset= toLineStart(projection.getMasterDocument(), offset, false);
631                 length= toLineStart(projection.getMasterDocument(), end, true) - offset;
632                 projection.addMasterDocumentRange(offset, length);
633             } finally {
634                 fHandleProjectionChanges= true;
635             }
636         }
637     }
638
639     /**
640      * Removes the given master range from the given projection document. While the
641      * modification is processed, the viewer no longer handles projection
642      * changes, as it is causing them.
643      *
644      * @param projection the projection document
645      * @param offset the offset in the master document
646      * @param length the length in the master document
647      * @throws BadLocationException in case the specified range is invalid
648      *
649      * @see ProjectionDocument#removeMasterDocumentRange(int, int)
650      */

651     private void removeMasterDocumentRange(ProjectionDocument projection, int offset, int length) throws BadLocationException {
652         if (fCommandQueue != null) {
653             fCommandQueue.add(new ProjectionCommand(projection, ProjectionCommand.REMOVE, offset, length));
654         } else {
655             try {
656                 fHandleProjectionChanges= false;
657                 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=108258
658
// make sure the document range is strictly line based
659
int end= offset + length;
660                 offset= toLineStart(projection.getMasterDocument(), offset, false);
661                 length= toLineStart(projection.getMasterDocument(), end, true) - offset;
662                 projection.removeMasterDocumentRange(offset, length);
663             } finally {
664                 fHandleProjectionChanges= true;
665             }
666         }
667     }
668     
669     /**
670      * Returns the first line offset &lt;= <code>offset</code>. If <code>testLastLine</code>
671      * is <code>true</code> and the offset is on last line then <code>offset</code> is returned.
672      *
673      * @param document the document
674      * @param offset the master document offset
675      * @param testLastLine <code>true</code> if the test for the last line should be performed
676      * @return the closest line offset &gt;= <code>offset</code>
677      * @throws BadLocationException if the offset is invalid
678      * @since 3.2
679      */

680     private int toLineStart(IDocument document, int offset, boolean testLastLine) throws BadLocationException {
681         if (document == null)
682             return offset;
683         
684         if (testLastLine && offset >= document.getLineInformationOfOffset(document.getLength() - 1).getOffset())
685             return offset;
686         
687         return document.getLineInformationOfOffset(offset).getOffset();
688     }
689
690     /*
691      * @see org.eclipse.jface.text.TextViewer#setVisibleRegion(int, int)
692      */

693     public void setVisibleRegion(int start, int length) {
694         if (!isSegmented())
695             fWasProjectionEnabled= isProjectionMode();
696         disableProjection();
697         super.setVisibleRegion(start, length);
698     }
699
700     /*
701      * @see org.eclipse.jface.text.TextViewer#setVisibleDocument(org.eclipse.jface.text.IDocument)
702      */

703     protected void setVisibleDocument(IDocument document) {
704         if (!isProjectionMode()) {
705             super.setVisibleDocument(document);
706             return;
707         }
708
709         // In projection mode we don't want to throw away the find/replace document adapter
710
FindReplaceDocumentAdapter adapter= fFindReplaceDocumentAdapter;
711         super.setVisibleDocument(document);
712         fFindReplaceDocumentAdapter= adapter;
713     }
714
715     /*
716      * @see org.eclipse.jface.text.TextViewer#resetVisibleRegion()
717      */

718     public void resetVisibleRegion() {
719         super.resetVisibleRegion();
720         if (fWasProjectionEnabled)
721             enableProjection();
722     }
723
724     /*
725      * @see org.eclipse.jface.text.ITextViewer#getVisibleRegion()
726      */

727     public IRegion getVisibleRegion() {
728         disableProjection();
729         IRegion visibleRegion= getModelCoverage();
730         if (visibleRegion == null)
731             visibleRegion= new Region(0, 0);
732
733         return visibleRegion;
734     }
735
736     /*
737      * @see org.eclipse.jface.text.ITextViewer#overlapsWithVisibleRegion(int,int)
738      */

739     public boolean overlapsWithVisibleRegion(int offset, int length) {
740         disableProjection();
741         IRegion coverage= getModelCoverage();
742         if (coverage == null)
743             return false;
744
745         boolean appending= (offset == coverage.getOffset() + coverage.getLength()) && length == 0;
746         return appending || TextUtilities.overlaps(coverage, new Region(offset, length));
747     }
748
749     /**
750      * Replace the visible document with the given document. Maintains the
751      * scroll offset and the selection.
752      *
753      * @param slave the visible document
754      */

755     private void replaceVisibleDocument(IDocument slave) {
756         if (fReplaceVisibleDocumentExecutionTrigger != null) {
757             ReplaceVisibleDocumentExecutor executor= new ReplaceVisibleDocumentExecutor(slave);
758             executor.install(fReplaceVisibleDocumentExecutionTrigger);
759         } else
760             executeReplaceVisibleDocument(slave);
761     }
762
763
764     private void executeReplaceVisibleDocument(IDocument visibleDocument) {
765         StyledText textWidget= getTextWidget();
766         try {
767             if (textWidget != null && !textWidget.isDisposed())
768                 textWidget.setRedraw(false);
769
770             int topIndex= getTopIndex();
771             Point selection= getSelectedRange();
772             setVisibleDocument(visibleDocument);
773             Point currentSelection= getSelectedRange();
774             if (currentSelection.x != selection.x || currentSelection.y != selection.y)
775                 setSelectedRange(selection.x, selection.y);
776             setTopIndex(topIndex);
777
778         } finally {
779             if (textWidget != null && !textWidget.isDisposed())
780                 textWidget.setRedraw(true);
781         }
782     }
783
784     /**
785      * Hides the given range by collapsing it. If requested, a redraw request is issued.
786      *
787      * @param offset the offset of the range to hide
788      * @param length the length of the range to hide
789      * @param fireRedraw <code>true</code> if a redraw request should be issued, <code>false</code> otherwise
790      * @throws BadLocationException in case the range is invalid
791      */

792     private void collapse(int offset, int length, boolean fireRedraw) throws BadLocationException {
793         ProjectionDocument projection= null;
794
795         IDocument visibleDocument= getVisibleDocument();
796         if (visibleDocument instanceof ProjectionDocument)
797             projection= (ProjectionDocument) visibleDocument;
798         else {
799             IDocument master= getDocument();
800             IDocument slave= createSlaveDocument(getDocument());
801             if (slave instanceof ProjectionDocument) {
802                 projection= (ProjectionDocument) slave;
803                 addMasterDocumentRange(projection, 0, master.getLength());
804                 replaceVisibleDocument(projection);
805             }
806         }
807
808         if (projection != null)
809             removeMasterDocumentRange(projection, offset, length);
810
811         if (projection != null && fireRedraw) {
812             // repaint line above to get the folding box
813
IDocument document= getDocument();
814             int line= document.getLineOfOffset(offset);
815             if (line > 0) {
816                 IRegion info= document.getLineInformation(line - 1);
817                 internalInvalidateTextPresentation(info.getOffset(), info.getLength());
818             }
819         }
820     }
821
822     /**
823      * Makes the given range visible again while not changing the folding state of any contained
824      * ranges. If requested, a redraw request is issued.
825      *
826      * @param offset the offset of the range to be expanded
827      * @param length the length of the range to be expanded
828      * @param fireRedraw <code>true</code> if a redraw request should be issued,
829      * <code>false</code> otherwise
830      * @throws BadLocationException in case the range is invalid
831      */

832     private void expand(int offset, int length, boolean fireRedraw) throws BadLocationException {
833         IDocument slave= getVisibleDocument();
834         if (slave instanceof ProjectionDocument) {
835             ProjectionDocument projection= (ProjectionDocument) slave;
836
837             // expand
838
addMasterDocumentRange(projection, offset, length);
839
840             // collapse contained regions
841
ProjectionAnnotation[] collapsed= computeCollapsedNestedAnnotations(offset, length);
842             if (collapsed != null) {
843                 for (int i= 0; i < collapsed.length; i++) {
844                     IRegion[] regions= computeCollapsedRegions(fProjectionAnnotationModel.getPosition(collapsed[i]));
845                     if (regions != null)
846                         for (int j= 0; j < regions.length; j++)
847                             removeMasterDocumentRange(projection, regions[j].getOffset(), regions[j].getLength());
848                 }
849             }
850
851             // redraw if requested
852
if (fireRedraw)
853                 internalInvalidateTextPresentation(offset, length);
854         }
855     }
856
857     /**
858      * Processes the request for catch up with the annotation model in the UI thread. If the current
859      * thread is not the UI thread or there are pending catch up requests, a new request is posted.
860      *
861      * @param event the annotation model event
862      */

863     protected final void processCatchupRequest(AnnotationModelEvent event) {
864         if (Display.getCurrent() != null) {
865             boolean run= false;
866             synchronized (fLock) {
867                 run= fPendingRequests.isEmpty();
868             }
869             if (run) {
870
871                 try {
872                     catchupWithProjectionAnnotationModel(event);
873                 } catch (BadLocationException x) {
874                     throw new IllegalArgumentException JavaDoc();
875                 }
876
877             } else
878                 postCatchupRequest(event);
879         } else {
880             postCatchupRequest(event);
881         }
882     }
883
884     /**
885      * Posts the request for catch up with the annotation model into the UI thread.
886      *
887      * @param event the annotation model event
888      */

889     protected final void postCatchupRequest(final AnnotationModelEvent event) {
890         synchronized (fLock) {
891             fPendingRequests.add(event);
892             if (fPendingRequests.size() == 1) {
893                 StyledText widget= getTextWidget();
894                 if (widget != null) {
895                     Display display= widget.getDisplay();
896                     if (display != null) {
897                         display.asyncExec(new Runnable JavaDoc() {
898                             public void run() {
899                                 try {
900                                     while (true) {
901                                         AnnotationModelEvent ame= null;
902                                         synchronized (fLock) {
903                                             if (fPendingRequests.size() == 0)
904                                                 return;
905                                             ame= (AnnotationModelEvent) fPendingRequests.remove(0);
906                                         }
907                                         catchupWithProjectionAnnotationModel(ame);
908                                     }
909                                 } catch (BadLocationException x) {
910                                     try {
911                                         catchupWithProjectionAnnotationModel(null);
912                                     } catch (BadLocationException x1) {
913                                         throw new IllegalArgumentException JavaDoc();
914                                     } finally {
915                                         synchronized (fLock) {
916                                             fPendingRequests.clear();
917                                         }
918                                     }
919                                 }
920                             }
921                         });
922                     }
923                 }
924             }
925         }
926     }
927     
928     /**
929      * Tests whether the visible document's master document
930      * is identical to this viewer's document.
931      *
932      * @return <code>true</code> if the visible document's master is
933      * identical to this viewer's document
934      * @since 3.1
935      */

936     private boolean isVisibleMasterDocumentSameAsDocument() {
937         IDocument visibleDocument= getVisibleDocument();
938         return (visibleDocument instanceof ProjectionDocument) && ((ProjectionDocument)visibleDocument).getMasterDocument() == getDocument();
939     }
940
941     /**
942      * Adapts the slave visual document of this viewer to the changes described
943      * in the annotation model event. When the event is <code>null</code>,
944      * this is identical to a world change event.
945      *
946      * @param event the annotation model event or <code>null</code>
947      * @exception BadLocationException in case the annotation model event is no longer in synchronization with the document
948      */

949     private void catchupWithProjectionAnnotationModel(AnnotationModelEvent event) throws BadLocationException {
950         
951         if (event == null || !isVisibleMasterDocumentSameAsDocument()) {
952
953             fPendingAnnotationWorldChange= false;
954             reinitializeProjection();
955
956         } else if (event.isWorldChange()) {
957
958             if (event.isValid()) {
959                 fPendingAnnotationWorldChange= false;
960                 reinitializeProjection();
961             } else
962                 fPendingAnnotationWorldChange= true;
963
964         } else if (fPendingAnnotationWorldChange) {
965             if (event.isValid()) {
966                 fPendingAnnotationWorldChange= false;
967                 reinitializeProjection();
968             }
969         } else {
970             
971             Annotation[] addedAnnotations= event.getAddedAnnotations();
972             Annotation[] changedAnnotation= event.getChangedAnnotations();
973             Annotation[] removedAnnotations= event.getRemovedAnnotations();
974             
975             fCommandQueue= new ProjectionCommandQueue();
976             
977             boolean isRedrawing= redraws();
978             int topIndex= isRedrawing ? getTopIndex() : -1;
979             
980             processDeletions(event, removedAnnotations, true);
981             List JavaDoc coverage= new ArrayList JavaDoc();
982             processChanges(addedAnnotations, true, coverage);
983             processChanges(changedAnnotation, true, coverage);
984             
985             ProjectionCommandQueue commandQueue= fCommandQueue;
986             fCommandQueue= null;
987             
988             if (commandQueue.passedRedrawCostsThreshold()) {
989                 setRedraw(false);
990                 try {
991                     executeProjectionCommands(commandQueue, false);
992                 } catch (IllegalArgumentException JavaDoc x) {
993                     reinitializeProjection();
994                 } finally {
995                     setRedraw(true, topIndex);
996                 }
997             } else {
998                 try {
999                     boolean fireRedraw= !commandQueue.passedInvalidationCostsThreshold();
1000                    executeProjectionCommands(commandQueue, fireRedraw);
1001                    if (!fireRedraw)
1002                        invalidateTextPresentation();
1003                } catch (IllegalArgumentException JavaDoc x) {
1004                    reinitializeProjection();
1005                }
1006            }
1007        }
1008    }
1009
1010    private void executeProjectionCommands(ProjectionCommandQueue commandQueue, boolean fireRedraw) throws BadLocationException {
1011
1012        ProjectionCommand command;
1013        Iterator JavaDoc e= commandQueue.iterator();
1014        while (e.hasNext()) {
1015            command= (ProjectionCommand) e.next();
1016            switch (command.fType) {
1017                case ProjectionCommand.ADD:
1018                    addMasterDocumentRange(command.fProjection, command.fOffset, command.fLength);
1019                    break;
1020                case ProjectionCommand.REMOVE:
1021                    removeMasterDocumentRange(command.fProjection, command.fOffset, command.fLength);
1022                    break;
1023                case ProjectionCommand.INVALIDATE_PRESENTATION:
1024                    if (fireRedraw)
1025                        invalidateTextPresentation(command.fOffset, command.fLength);
1026                    break;
1027            }
1028        }
1029
1030        commandQueue.clear();
1031    }
1032
1033    private boolean covers(int offset, int length, Position position) {
1034        if (!(position.offset == offset && position.length == length) && !position.isDeleted())
1035            return offset <= position.getOffset() && position.getOffset() + position.getLength() <= offset + length;
1036        return false;
1037    }
1038
1039    private ProjectionAnnotation[] computeCollapsedNestedAnnotations(int offset, int length) {
1040        List JavaDoc annotations= new ArrayList JavaDoc(5);
1041        Iterator JavaDoc e= fProjectionAnnotationModel.getAnnotationIterator();
1042        while (e.hasNext()) {
1043            ProjectionAnnotation annotation= (ProjectionAnnotation) e.next();
1044            if (annotation.isCollapsed()) {
1045                Position position= fProjectionAnnotationModel.getPosition(annotation);
1046                if (position == null) {
1047                    // annotation might already be deleted, we will be informed later on about this deletion
1048
continue;
1049                }
1050                if (covers(offset, length, position))
1051                    annotations.add(annotation);
1052            }
1053        }
1054
1055        if (annotations.size() > 0) {
1056            ProjectionAnnotation[] result= new ProjectionAnnotation[annotations.size()];
1057            annotations.toArray(result);
1058            return result;
1059        }
1060
1061        return null;
1062    }
1063
1064    private void internalInvalidateTextPresentation(int offset, int length) {
1065        if (fCommandQueue != null) {
1066            fCommandQueue.add(new ProjectionCommand(offset, length));
1067        } else {
1068            invalidateTextPresentation(offset, length);
1069        }
1070    }
1071
1072    /*
1073     * We pass the removed annotation into this method for performance reasons only. Otherwise, they could be fetch from the event.
1074     */

1075    private void processDeletions(AnnotationModelEvent event, Annotation[] removedAnnotations, boolean fireRedraw) throws BadLocationException {
1076        for (int i= 0; i < removedAnnotations.length; i++) {
1077            ProjectionAnnotation annotation= (ProjectionAnnotation) removedAnnotations[i];
1078            if (annotation.isCollapsed()) {
1079                Position expanded= event.getPositionOfRemovedAnnotation(annotation);
1080                expand(expanded.getOffset(), expanded.getLength(), fireRedraw);
1081            }
1082        }
1083    }
1084
1085    /**
1086     * Computes the region that must be collapsed when the given position is the
1087     * position of an expanded projection annotation.
1088     *
1089     * @param position the position
1090     * @return the range that must be collapsed
1091     */

1092    public IRegion computeCollapsedRegion(Position position) {
1093        try {
1094            IDocument document= getDocument();
1095            if (document == null)
1096                return null;
1097            
1098            int line= document.getLineOfOffset(position.getOffset());
1099            int offset= document.getLineOffset(line + 1);
1100
1101            int length= position.getLength() - (offset - position.getOffset());
1102            if (length > 0)
1103                return new Region(offset, length);
1104        } catch (BadLocationException x) {
1105        }
1106
1107        return null;
1108    }
1109
1110    /**
1111     * Computes the regions that must be collapsed when the given position is
1112     * the position of an expanded projection annotation.
1113     *
1114     * @param position the position
1115     * @return the ranges that must be collapsed, or <code>null</code> if
1116     * there are none
1117     * @since 3.1
1118     */

1119    IRegion[] computeCollapsedRegions(Position position) {
1120        try {
1121            IDocument document= getDocument();
1122            if (document == null)
1123                return null;
1124
1125            if (position instanceof IProjectionPosition) {
1126                IProjectionPosition projPosition= (IProjectionPosition) position;
1127                return projPosition.computeProjectionRegions(document);
1128            }
1129
1130            int line= document.getLineOfOffset(position.getOffset());
1131            int offset= document.getLineOffset(line + 1);
1132
1133            int length= position.getLength() - (offset - position.getOffset());
1134            if (length > 0)
1135                return new IRegion[] {new Region(offset, length)};
1136
1137            return null;
1138        } catch (BadLocationException x) {
1139            return null;
1140        }
1141    }
1142
1143    /**
1144     * Computes the collapsed region anchor for the given position. Assuming
1145     * that the position is the position of an expanded projection annotation,
1146     * the anchor is the region that is still visible after the projection
1147     * annotation has been collapsed.
1148     *
1149     * @param position the position
1150     * @return the collapsed region anchor
1151     */

1152    public Position computeCollapsedRegionAnchor(Position position) {
1153        try {
1154            IDocument document= getDocument();
1155            if (document == null)
1156                return null;
1157
1158            int captionOffset= position.getOffset();
1159            if (position instanceof IProjectionPosition)
1160                captionOffset+= ((IProjectionPosition) position).computeCaptionOffset(document);
1161
1162            IRegion lineInfo= document.getLineInformationOfOffset(captionOffset);
1163            return new Position(lineInfo.getOffset() + lineInfo.getLength(), 0);
1164        } catch (BadLocationException x) {
1165        }
1166        return null;
1167    }
1168
1169    private void processChanges(Annotation[] annotations, boolean fireRedraw, List JavaDoc coverage) throws BadLocationException {
1170        for (int i= 0; i < annotations.length; i++) {
1171            ProjectionAnnotation annotation= (ProjectionAnnotation) annotations[i];
1172            Position position= fProjectionAnnotationModel.getPosition(annotation);
1173
1174            if (position == null)
1175                continue;
1176
1177            if (!covers(coverage, position)) {
1178                if (annotation.isCollapsed()) {
1179                    coverage.add(position);
1180                    IRegion[] regions= computeCollapsedRegions(position);
1181                    if (regions != null)
1182                        for (int j= 0; j < regions.length; j++)
1183                            collapse(regions[j].getOffset(), regions[j].getLength(), fireRedraw);
1184                } else {
1185                    expand(position.getOffset(), position.getLength(), fireRedraw);
1186                }
1187            }
1188        }
1189    }
1190
1191    private boolean covers(List JavaDoc coverage, Position position) {
1192        Iterator JavaDoc e= coverage.iterator();
1193        while (e.hasNext()) {
1194            Position p= (Position) e.next();
1195            if (p.getOffset() <= position.getOffset() && position.getOffset() + position.getLength() <= p.getOffset() + p.getLength())
1196                return true;
1197        }
1198        return false;
1199    }
1200
1201    /**
1202     * Forces this viewer to throw away any old state and to initialize its content
1203     * from its projection annotation model.
1204     *
1205     * @throws BadLocationException in case something goes wrong during initialization
1206     */

1207    public final void reinitializeProjection() throws BadLocationException {
1208
1209        ProjectionDocument projection= null;
1210
1211        ISlaveDocumentManager manager= getSlaveDocumentManager();
1212        if (manager != null) {
1213            IDocument master= getDocument();
1214            if (master != null) {
1215                IDocument slave= manager.createSlaveDocument(master);
1216                if (slave instanceof ProjectionDocument) {
1217                    projection= (ProjectionDocument) slave;
1218                    addMasterDocumentRange(projection, 0, master.getLength());
1219                }
1220            }
1221        }
1222
1223        if (projection != null) {
1224            Iterator JavaDoc e= fProjectionAnnotationModel.getAnnotationIterator();
1225            while (e.hasNext()) {
1226                ProjectionAnnotation annotation= (ProjectionAnnotation) e.next();
1227                if (annotation.isCollapsed()) {
1228                    Position position= fProjectionAnnotationModel.getPosition(annotation);
1229                    if (position != null) {
1230                        IRegion[] regions= computeCollapsedRegions(position);
1231                        if (regions != null)
1232                            for (int i= 0; i < regions.length; i++)
1233                                removeMasterDocumentRange(projection, regions[i].getOffset(), regions[i].getLength());
1234                    }
1235                }
1236            }
1237
1238        }
1239
1240        replaceVisibleDocument(projection);
1241    }
1242
1243    /*
1244     * @see org.eclipse.jface.text.TextViewer#handleVerifyEvent(org.eclipse.swt.events.VerifyEvent)
1245     */

1246    protected void handleVerifyEvent(VerifyEvent e) {
1247        IRegion modelRange= event2ModelRange(e);
1248        if (exposeModelRange(modelRange))
1249            e.doit= false;
1250        else
1251            super.handleVerifyEvent(e);
1252    }
1253
1254    /**
1255     * Adds the give column as last column to this viewer's vertical ruler.
1256     *
1257     * @param column the column to be added
1258     */

1259    public void addVerticalRulerColumn(IVerticalRulerColumn column) {
1260        IVerticalRuler ruler= getVerticalRuler();
1261        if (ruler instanceof CompositeRuler) {
1262            CompositeRuler compositeRuler= (CompositeRuler) ruler;
1263            compositeRuler.addDecorator(99, column);
1264        }
1265    }
1266
1267    /**
1268     * Removes the give column from this viewer's vertical ruler.
1269     *
1270     * @param column the column to be removed
1271     */

1272    public void removeVerticalRulerColumn(IVerticalRulerColumn column) {
1273        IVerticalRuler ruler= getVerticalRuler();
1274        if (ruler instanceof CompositeRuler) {
1275            CompositeRuler compositeRuler= (CompositeRuler) ruler;
1276            compositeRuler.removeDecorator(column);
1277        }
1278    }
1279
1280    /*
1281     * @see org.eclipse.jface.text.ITextViewerExtension5#exposeModelRange(org.eclipse.jface.text.IRegion)
1282     */

1283    public boolean exposeModelRange(IRegion modelRange) {
1284        if (isProjectionMode())
1285            return fProjectionAnnotationModel.expandAll(modelRange.getOffset(), modelRange.getLength());
1286
1287        if (!overlapsWithVisibleRegion(modelRange.getOffset(), modelRange.getLength())) {
1288            resetVisibleRegion();
1289            return true;
1290        }
1291
1292        return false;
1293    }
1294
1295    /*
1296     * @see org.eclipse.jface.text.source.SourceViewer#setRangeIndication(int, int, boolean)
1297     */

1298    public void setRangeIndication(int offset, int length, boolean moveCursor) {
1299
1300        if (getRangeIndication() != null) {
1301            List JavaDoc expand= new ArrayList JavaDoc(2);
1302            if (moveCursor && fProjectionAnnotationModel != null) {
1303                
1304                // expand the immediate effected collapsed regions
1305
Iterator JavaDoc iterator= fProjectionAnnotationModel.getAnnotationIterator();
1306                while (iterator.hasNext()) {
1307                    ProjectionAnnotation annotation= (ProjectionAnnotation) iterator.next();
1308                    if (annotation.isCollapsed() && willAutoExpand(fProjectionAnnotationModel.getPosition(annotation), offset, length))
1309                        expand.add(annotation);
1310                }
1311                
1312                if (!expand.isEmpty()) {
1313                    Iterator JavaDoc e= expand.iterator();
1314                    while (e.hasNext())
1315                        fProjectionAnnotationModel.expand((Annotation) e.next());
1316                }
1317            }
1318        }
1319
1320        super.setRangeIndication(offset, length, moveCursor);
1321    }
1322
1323    private boolean willAutoExpand(Position position, int offset, int length) {
1324        if (position == null || position.isDeleted())
1325            return false;
1326        // right or left boundary
1327
if (position.getOffset() == offset || position.getOffset() + position.getLength() == offset + length)
1328            return true;
1329        // completely embedded in given position
1330
if (position.getOffset() < offset && offset + length < position.getOffset() + position.getLength())
1331            return true;
1332        return false;
1333    }
1334
1335    /*
1336     * @see org.eclipse.jface.text.source.SourceViewer#handleDispose()
1337     * @since 3.0
1338     */

1339    protected void handleDispose() {
1340        fWasProjectionEnabled= false;
1341        super.handleDispose();
1342    }
1343
1344    /*
1345     * @see org.eclipse.jface.text.TextViewer#handleVisibleDocumentAboutToBeChanged(org.eclipse.jface.text.DocumentEvent)
1346     */

1347    protected void handleVisibleDocumentChanged(DocumentEvent event) {
1348        if (fHandleProjectionChanges && event instanceof ProjectionDocumentEvent && isProjectionMode()) {
1349            ProjectionDocumentEvent e= (ProjectionDocumentEvent) event;
1350
1351            DocumentEvent master= e.getMasterEvent();
1352            if (master != null)
1353                fReplaceVisibleDocumentExecutionTrigger= master.getDocument();
1354
1355            try {
1356
1357                int replaceLength= e.getText() == null ? 0 : e.getText().length();
1358                if (ProjectionDocumentEvent.PROJECTION_CHANGE == e.getChangeType()) {
1359                    if (e.getLength() == 0 && replaceLength != 0)
1360                        fProjectionAnnotationModel.expandAll(e.getMasterOffset(), e.getMasterLength());
1361                } else if (master != null && (replaceLength > 0 || fDeletedLines > 1)) {
1362                    try {
1363                        int numberOfLines= e.getDocument().getNumberOfLines(e.getOffset(), replaceLength);
1364                        if (numberOfLines > 1 || fDeletedLines > 1)
1365                            fProjectionAnnotationModel.expandAll(master.getOffset(), master.getLength());
1366                    } catch (BadLocationException x) {
1367                    }
1368                }
1369
1370            } finally {
1371                fReplaceVisibleDocumentExecutionTrigger= null;
1372            }
1373
1374        }
1375    }
1376
1377    /*
1378     * @see org.eclipse.jface.text.TextViewer#handleVisibleDocumentAboutToBeChanged(org.eclipse.jface.text.DocumentEvent)
1379     * @since 3.1
1380     */

1381    protected void handleVisibleDocumentAboutToBeChanged(DocumentEvent event) {
1382        if (fHandleProjectionChanges && event instanceof ProjectionDocumentEvent && isProjectionMode()) {
1383            int deletedLines;
1384            try {
1385                deletedLines= event.getDocument().getNumberOfLines(event.getOffset(), event.getLength());
1386            } catch (BadLocationException e1) {
1387                deletedLines= 0;
1388            }
1389            fDeletedLines= deletedLines;
1390        }
1391    }
1392
1393    /*
1394     * @see org.eclipse.jface.text.ITextViewerExtension5#getCoveredModelRanges(org.eclipse.jface.text.IRegion)
1395     */

1396    public IRegion[] getCoveredModelRanges(IRegion modelRange) {
1397        if (fInformationMapping == null)
1398            return new IRegion[] { new Region(modelRange.getOffset(), modelRange.getLength()) };
1399
1400        if (fInformationMapping instanceof IDocumentInformationMappingExtension) {
1401            IDocumentInformationMappingExtension extension= (IDocumentInformationMappingExtension) fInformationMapping;
1402            try {
1403                return extension.getExactCoverage(modelRange);
1404            } catch (BadLocationException x) {
1405            }
1406        }
1407
1408        return null;
1409    }
1410
1411    /*
1412     * @see org.eclipse.jface.text.ITextOperationTarget#doOperation(int)
1413     */

1414    public void doOperation(int operation) {
1415        switch (operation) {
1416            case TOGGLE:
1417                if (canDoOperation(TOGGLE)) {
1418                    if (!isProjectionMode()) {
1419                        enableProjection();
1420                    } else {
1421                        expandAll();
1422                        disableProjection();
1423                    }
1424                    return;
1425                }
1426        }
1427
1428        if (!isProjectionMode()) {
1429            super.doOperation(operation);
1430            return;
1431        }
1432
1433        StyledText textWidget= getTextWidget();
1434        if (textWidget == null)
1435            return;
1436
1437        Point selection= null;
1438        switch (operation) {
1439
1440            case CUT:
1441
1442                if (redraws()) {
1443                    selection= getSelectedRange();
1444                    if (exposeModelRange(new Region(selection.x, selection.y)))
1445                        return;
1446                    
1447                    if (selection.y == 0)
1448                        copyMarkedRegion(true);
1449                    else
1450                        copyToClipboard(selection.x, selection.y, true, textWidget);
1451
1452                    selection= textWidget.getSelectionRange();
1453                    fireSelectionChanged(selection.x, selection.y);
1454                }
1455                break;
1456
1457            case COPY:
1458
1459                if (redraws()) {
1460                    selection= getSelectedRange();
1461                    if (selection.y == 0)
1462                        copyMarkedRegion(false);
1463                    else
1464                        copyToClipboard(selection.x, selection.y, false, textWidget);
1465                }
1466                break;
1467
1468            case DELETE:
1469
1470                if (redraws()) {
1471                    try {
1472                        selection= getSelectedRange();
1473                        Point widgetSelection= textWidget.getSelectionRange();
1474                        if (selection.y == 0 || selection.y == widgetSelection.y)
1475                            getTextWidget().invokeAction(ST.DELETE_NEXT);
1476                        else
1477                            deleteTextRange(selection.x, selection.y, textWidget);
1478
1479                        selection= textWidget.getSelectionRange();
1480                        fireSelectionChanged(selection.x, selection.y);
1481
1482                    } catch (BadLocationException x) {
1483                        // ignore
1484
}
1485                }
1486                break;
1487
1488
1489            case EXPAND_ALL:
1490                if (redraws())
1491                    expandAll();
1492                break;
1493
1494            case EXPAND:
1495                if (redraws()) {
1496                    expand();
1497                }
1498                break;
1499
1500            case COLLAPSE_ALL:
1501                if (redraws())
1502                    collapseAll();
1503                break;
1504                
1505            case COLLAPSE:
1506                if (redraws()) {
1507                    collapse();
1508                }
1509                break;
1510
1511            default:
1512                super.doOperation(operation);
1513        }
1514    }
1515
1516    /*
1517     * @see org.eclipse.jface.text.source.SourceViewer#canDoOperation(int)
1518     */

1519    public boolean canDoOperation(int operation) {
1520
1521        switch (operation) {
1522            case COLLAPSE:
1523            case COLLAPSE_ALL:
1524            case EXPAND:
1525            case EXPAND_ALL:
1526                return isProjectionMode();
1527            case TOGGLE:
1528                return isProjectionMode() || !isSegmented();
1529        }
1530
1531        return super.canDoOperation(operation);
1532    }
1533
1534    private boolean isSegmented() {
1535        IDocument document= getDocument();
1536        int length= document == null ? 0 : document.getLength();
1537        IRegion visible= getModelCoverage();
1538        boolean isSegmented= visible != null && !visible.equals(new Region(0, length));
1539        return isSegmented;
1540    }
1541
1542    private IRegion getMarkedRegion() {
1543        if (getTextWidget() == null)
1544            return null;
1545
1546        if (fMarkPosition == null || fMarkPosition.isDeleted())
1547            return null;
1548
1549        int start= fMarkPosition.getOffset();
1550        int end= getSelectedRange().x;
1551
1552        return start > end ? new Region (end, start - end) : new Region(start, end - start);
1553    }
1554
1555    /*
1556     * @see org.eclipse.jface.text.TextViewer#copyMarkedRegion(boolean)
1557     */

1558    protected void copyMarkedRegion(boolean delete) {
1559        IRegion markedRegion= getMarkedRegion();
1560        if (markedRegion != null)
1561            copyToClipboard(markedRegion.getOffset(), markedRegion.getLength(), delete, getTextWidget());
1562    }
1563
1564    private void copyToClipboard(int offset, int length, boolean delete, StyledText textWidget) {
1565
1566        String JavaDoc copyText= null;
1567
1568        try {
1569            IDocument document= getDocument();
1570            copyText= document.get(offset, length);
1571        } catch (BadLocationException ex) {
1572            // XXX: should log here, but JFace Text has no Log
1573
// As a fallback solution let the widget handle this
1574
textWidget.copy();
1575        }
1576
1577        if (copyText != null && copyText.equals(textWidget.getSelectionText())) {
1578            /*
1579             * XXX: Reduce pain of https://bugs.eclipse.org/bugs/show_bug.cgi?id=64498
1580             * by letting the widget handle the copy operation in this special case.
1581             */

1582            textWidget.copy();
1583        } else if (copyText != null) {
1584
1585            Clipboard clipboard= new Clipboard(textWidget.getDisplay());
1586
1587            try {
1588                Transfer[] dataTypes= new Transfer[] { TextTransfer.getInstance() };
1589                Object JavaDoc[] data= new Object JavaDoc[] { copyText };
1590                try {
1591                    clipboard.setContents(data, dataTypes);
1592                } catch (SWTError e) {
1593                    if (e.code != DND.ERROR_CANNOT_SET_CLIPBOARD)
1594                        throw e;
1595                    /*
1596                     * TODO see https://bugs.eclipse.org/bugs/show_bug.cgi?id=59459
1597                     * we should either log and/or inform the user
1598                     * silently fail for now.
1599                     */

1600                    return;
1601                }
1602
1603            } finally {
1604                clipboard.dispose();
1605            }
1606        }
1607
1608        if (delete) {
1609            try {
1610                deleteTextRange(offset, length, textWidget);
1611            } catch (BadLocationException x) {
1612                // XXX: should log here, but JFace Text has no Log
1613
}
1614        }
1615    }
1616
1617    private void deleteTextRange(int offset, int length, StyledText textWidget) throws BadLocationException {
1618        getDocument().replace(offset, length, ""); //$NON-NLS-1$
1619
int widgetCaret= modelOffset2WidgetOffset(offset);
1620        if (widgetCaret > -1)
1621            textWidget.setSelection(widgetCaret);
1622    }
1623
1624    /**
1625     * Adapts the behavior of the super class to respect line based folding.
1626     *
1627     * @param widgetSelection the widget selection
1628     * @return the model selection while respecting line based folding
1629     */

1630    protected Point widgetSelection2ModelSelection(Point widgetSelection) {
1631
1632        if (!isProjectionMode())
1633            return super.widgetSelection2ModelSelection(widgetSelection);
1634
1635        /*
1636         * There is one requirement that governs preservation of logical
1637         * positions:
1638         *
1639         * 1) a selection with widget_length == 0 should never expand to have
1640         * model_length > 0.
1641         *
1642         * There are a number of ambiguities to resolve with projection regions.
1643         * A projected region P has a widget-length of zero. Its widget offset
1644         * may interact with the selection S in various ways:
1645         *
1646         * A) P.widget_offset lies at the caret, S.widget_length is zero.
1647         * Requirement 1 applies. S is *behind* P (done so by widgetRange2ModelRange).
1648         *
1649         * B) P.widget_offset lies inside the widget selection. This case is
1650         * easy: P is included in S, which is automatically done so by
1651         * widgetRange2ModelRange.
1652         *
1653         * C) P.widget_offset lies at S.widget_end: This is
1654         * arguable - our policy is to include P if it belongs to a projection
1655         * annotation that overlaps with the widget selection.
1656         *
1657         * D) P.widget_offset lies at S.widget_offset: Arguable - our policy
1658         * is to include P if it belongs to a projection annotation that
1659         * overlaps with the widget selection
1660         */

1661        IRegion modelSelection= widgetRange2ModelRange(new Region(widgetSelection.x, widgetSelection.y));
1662        if (modelSelection == null)
1663            return null;
1664
1665        int modelOffset= modelSelection.getOffset();
1666        int modelEndOffset= modelOffset + modelSelection.getLength();
1667
1668        /* Case A: never expand a zero-length selection. S is *behind* P. */
1669        if (widgetSelection.y == 0)
1670            return new Point(modelEndOffset, 0);
1671
1672        int widgetSelectionExclusiveEnd= widgetSelection.x + widgetSelection.y;
1673        Position[] annotationPositions= computeOverlappingAnnotationPositions(modelSelection);
1674        for (int i= 0; i < annotationPositions.length; i++) {
1675            IRegion[] regions= computeCollapsedRegions(annotationPositions[i]);
1676            if (regions == null)
1677                continue;
1678            for (int j= 0; j < regions.length; j++) {
1679                IRegion modelRange= regions[j];
1680                IRegion widgetRange= modelRange2ClosestWidgetRange(modelRange);
1681                // only take collapsed ranges, i.e. widget length is 0
1682
if (widgetRange != null && widgetRange.getLength() == 0) {
1683                    int widgetOffset= widgetRange.getOffset();
1684                    // D) region is collapsed at S.widget_offset
1685
if (widgetOffset == widgetSelection.x)
1686                        modelOffset= Math.min(modelOffset, modelRange.getOffset());
1687                    // C) region is collapsed at S.widget_end
1688
else if (widgetOffset == widgetSelectionExclusiveEnd)
1689                        modelEndOffset= Math.max(modelEndOffset, modelRange.getOffset() + modelRange.getLength());
1690                }
1691            }
1692        }
1693        return new Point(modelOffset, modelEndOffset - modelOffset);
1694    }
1695
1696    /**
1697     * Returns the positions of all annotations that intersect with
1698     * <code>modelSelection</code> and that are at least partly visible.
1699     * @param modelSelection a model range
1700     * @return the positions of all annotations that intersect with
1701     * <code>modelSelection</code>
1702     * @since 3.1
1703     */

1704    private Position[] computeOverlappingAnnotationPositions(IRegion modelSelection) {
1705        List JavaDoc positions= new ArrayList JavaDoc();
1706        for (Iterator JavaDoc e= fProjectionAnnotationModel.getAnnotationIterator(); e.hasNext();) {
1707            ProjectionAnnotation annotation= (ProjectionAnnotation) e.next();
1708            Position position= fProjectionAnnotationModel.getPosition(annotation);
1709            if (position != null && position.overlapsWith(modelSelection.getOffset(), modelSelection.getLength()) && modelRange2WidgetRange(position) != null)
1710                positions.add(position);
1711        }
1712        return (Position[]) positions.toArray(new Position[positions.size()]);
1713    }
1714
1715    /*
1716     * @see org.eclipse.jface.text.TextViewer#getFindReplaceDocumentAdapter()
1717     */

1718    protected FindReplaceDocumentAdapter getFindReplaceDocumentAdapter() {
1719        if (fFindReplaceDocumentAdapter == null) {
1720            IDocument document= isProjectionMode() ? getDocument() : getVisibleDocument();
1721            fFindReplaceDocumentAdapter= new FindReplaceDocumentAdapter(document);
1722        }
1723        return fFindReplaceDocumentAdapter;
1724    }
1725
1726    /*
1727     * @see org.eclipse.jface.text.TextViewer#findAndSelect(int, java.lang.String, boolean, boolean, boolean, boolean)
1728     */

1729    protected int findAndSelect(int startPosition, String JavaDoc findString, boolean forwardSearch, boolean caseSensitive, boolean wholeWord, boolean regExSearch) {
1730
1731        if (!isProjectionMode())
1732            return super.findAndSelect(startPosition, findString, forwardSearch, caseSensitive, wholeWord, regExSearch);
1733
1734        StyledText textWidget= getTextWidget();
1735        if (textWidget == null)
1736            return -1;
1737
1738        try {
1739
1740            IRegion matchRegion= getFindReplaceDocumentAdapter().find(startPosition, findString, forwardSearch, caseSensitive, wholeWord, regExSearch);
1741            if (matchRegion != null) {
1742                exposeModelRange(matchRegion);
1743                revealRange(matchRegion.getOffset(), matchRegion.getLength());
1744                setSelectedRange(matchRegion.getOffset(), matchRegion.getLength());
1745                return matchRegion.getOffset();
1746            }
1747
1748        } catch (BadLocationException x) {
1749        }
1750
1751        return -1;
1752    }
1753
1754    /*
1755     * @see org.eclipse.jface.text.TextViewer#findAndSelectInRange(int, java.lang.String, boolean, boolean, boolean, int, int, boolean)
1756     */

1757    protected int findAndSelectInRange(int startPosition, String JavaDoc findString, boolean forwardSearch, boolean caseSensitive, boolean wholeWord, int rangeOffset, int rangeLength, boolean regExSearch) {
1758
1759        if (!isProjectionMode())
1760            return super.findAndSelectInRange(startPosition, findString, forwardSearch, caseSensitive, wholeWord, rangeOffset, rangeLength, regExSearch);
1761
1762        StyledText textWidget= getTextWidget();
1763        if (textWidget == null)
1764            return -1;
1765
1766        try {
1767
1768            int modelOffset= startPosition;
1769            if (forwardSearch && (startPosition == -1 || startPosition < rangeOffset)) {
1770                modelOffset= rangeOffset;
1771            } else if (!forwardSearch && (startPosition == -1 || startPosition > rangeOffset + rangeLength)) {
1772                modelOffset= rangeOffset + rangeLength;
1773            }
1774
1775            IRegion matchRegion= getFindReplaceDocumentAdapter().find(modelOffset, findString, forwardSearch, caseSensitive, wholeWord, regExSearch);
1776            if (matchRegion != null) {
1777                int offset= matchRegion.getOffset();
1778                int length= matchRegion.getLength();
1779                if (rangeOffset <= offset && offset + length <= rangeOffset + rangeLength) {
1780                    exposeModelRange(matchRegion);
1781                    revealRange(offset, length);
1782                    setSelectedRange(offset, length);
1783                    return offset;
1784                }
1785            }
1786
1787        } catch (BadLocationException x) {
1788        }
1789
1790        return -1;
1791    }
1792}
1793
Popular Tags