KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > jface > text > source > AnnotationPainter


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;
12
13
14 import java.util.ArrayList JavaDoc;
15 import java.util.Arrays JavaDoc;
16 import java.util.Collection JavaDoc;
17 import java.util.HashMap JavaDoc;
18 import java.util.HashSet JavaDoc;
19 import java.util.Iterator JavaDoc;
20 import java.util.LinkedList JavaDoc;
21 import java.util.List JavaDoc;
22 import java.util.Map JavaDoc;
23 import java.util.Set JavaDoc;
24
25 import org.eclipse.swt.SWT;
26 import org.eclipse.swt.SWTException;
27 import org.eclipse.swt.custom.StyleRange;
28 import org.eclipse.swt.custom.StyledText;
29 import org.eclipse.swt.events.PaintEvent;
30 import org.eclipse.swt.events.PaintListener;
31 import org.eclipse.swt.graphics.Color;
32 import org.eclipse.swt.graphics.GC;
33 import org.eclipse.swt.graphics.Point;
34 import org.eclipse.swt.widgets.Display;
35
36 import org.eclipse.core.runtime.Platform;
37
38 import org.eclipse.jface.text.BadLocationException;
39 import org.eclipse.jface.text.IDocument;
40 import org.eclipse.jface.text.IPaintPositionManager;
41 import org.eclipse.jface.text.IPainter;
42 import org.eclipse.jface.text.IRegion;
43 import org.eclipse.jface.text.ITextInputListener;
44 import org.eclipse.jface.text.ITextPresentationListener;
45 import org.eclipse.jface.text.ITextViewerExtension2;
46 import org.eclipse.jface.text.ITextViewerExtension5;
47 import org.eclipse.jface.text.JFaceTextUtil;
48 import org.eclipse.jface.text.Position;
49 import org.eclipse.jface.text.Region;
50 import org.eclipse.jface.text.TextPresentation;
51
52
53 /**
54  * Paints decorations for annotations provided by an annotation model and/or
55  * highlights them in the associated source viewer.
56  * <p>
57  * The annotation painter can be configured with drawing strategies. A drawing
58  * strategy defines the visual presentation of a particular type of annotation
59  * decoration.</p>
60  * <p>
61  * Clients usually instantiate and configure objects of this class.</p>
62  *
63  * @since 2.1
64  */

65 public class AnnotationPainter implements IPainter, PaintListener, IAnnotationModelListener, IAnnotationModelListenerExtension, ITextPresentationListener {
66
67
68     /**
69      * A drawing strategy draws the decoration for an annotation onto the text widget.
70      *
71      * @since 3.0
72      */

73     public interface IDrawingStrategy {
74         /**
75          * Draws a decoration for an annotation onto the specified GC at the given text range. There
76          * are two different invocation modes of the <code>draw</code> method:
77          * <ul>
78          * <li><strong>drawing mode:</strong> the passed GC is the graphics context of a paint
79          * event occurring on the text widget. The strategy should draw the decoration onto the
80          * graphics context, such that the decoration appears at the given range in the text
81          * widget.</li>
82          * <li><strong>clearing mode:</strong> the passed GC is <code>null</code>. In this case
83          * the strategy must invalidate enough of the text widget's client area to cover any
84          * decoration drawn in drawing mode. This can usually be accomplished by calling
85          * {@linkplain StyledText#redrawRange(int, int, boolean) textWidget.redrawRange(offset, length, true)}.</li>
86          * </ul>
87          *
88          * @param annotation the annotation to be drawn
89          * @param gc the graphics context, <code>null</code> when in clearing mode
90          * @param textWidget the text widget to draw on
91          * @param offset the offset of the line
92          * @param length the length of the line
93          * @param color the color of the line
94          */

95         void draw(Annotation annotation, GC gc, StyledText textWidget, int offset, int length, Color color);
96     }
97
98     /**
99      * Squiggles drawing strategy.
100      *
101      * @since 3.0
102      */

103     public static class SquigglesStrategy implements IDrawingStrategy {
104
105         /*
106          * @see org.eclipse.jface.text.source.AnnotationPainter.IDrawingStrategy#draw(org.eclipse.jface.text.source.Annotation, org.eclipse.swt.graphics.GC, org.eclipse.swt.custom.StyledText, int, int, org.eclipse.swt.graphics.Color)
107          * @since 3.0
108          */

109         public void draw(Annotation annotation, GC gc, StyledText textWidget, int offset, int length, Color color) {
110             if (gc != null) {
111
112                 if (length < 1)
113                     return;
114
115                 Point left= textWidget.getLocationAtOffset(offset);
116                 Point right= textWidget.getLocationAtOffset(offset + length);
117
118                 int[] polyline= computePolyline(left, right, textWidget.getBaseline(offset), textWidget.getLineHeight(offset));
119                 
120                 gc.setLineWidth(0); // NOTE: 0 means width is 1 but with optimized performance
121
gc.setLineStyle(SWT.LINE_SOLID);
122                 gc.setForeground(color);
123                 gc.drawPolyline(polyline);
124
125             } else {
126                 textWidget.redrawRange(offset, length, true);
127             }
128         }
129
130         /**
131          * Computes an array of alternating x and y values which are the corners of the squiggly line of the
132          * given height between the given end points.
133          *
134          * @param left the left end point
135          * @param right the right end point
136          * @param baseline the font's baseline
137          * @param lineHeight the height of the line
138          * @return the array of alternating x and y values which are the corners of the squiggly line
139          */

140         private int[] computePolyline(Point left, Point right, int baseline, int lineHeight) {
141
142             final int WIDTH= 4; // must be even
143
final int HEIGHT= 2; // can be any number
144

145             int peaks= (right.x - left.x) / WIDTH;
146             if (peaks == 0 && right.x - left.x > 2)
147                 peaks= 1;
148
149             int leftX= left.x;
150
151             // compute (number of point) * 2
152
int length= ((2 * peaks) + 1) * 2;
153             if (length < 0)
154                 return new int[0];
155
156             int[] coordinates= new int[length];
157
158             // cache peeks' y-coordinates
159
int top= left.y + Math.min(baseline + 1, lineHeight - HEIGHT - 1);
160             int bottom= top + HEIGHT;
161
162             // populate array with peek coordinates
163
for (int i= 0; i < peaks; i++) {
164                 int index= 4 * i;
165                 coordinates[index]= leftX + (WIDTH * i);
166                 coordinates[index+1]= bottom;
167                 coordinates[index+2]= coordinates[index] + WIDTH/2;
168                 coordinates[index+3]= top;
169             }
170
171             // the last down flank is missing
172
coordinates[length-2]= Math.min(Math.max(0, right.x - 1), left.x + (WIDTH * peaks));
173             coordinates[length-1]= bottom;
174
175             return coordinates;
176         }
177     }
178
179     /**
180      * Drawing strategy that does nothing.
181      *
182      * @since 3.0
183      */

184     public static final class NullStrategy implements IDrawingStrategy {
185
186         /*
187          * @see org.eclipse.jface.text.source.AnnotationPainter.IDrawingStrategy#draw(org.eclipse.jface.text.source.Annotation, org.eclipse.swt.graphics.GC, org.eclipse.swt.custom.StyledText, int, int, org.eclipse.swt.graphics.Color)
188          * @since 3.0
189          */

190         public void draw(Annotation annotation, GC gc, StyledText textWidget, int offset, int length, Color color) {
191             // do nothing
192
}
193     }
194
195     /**
196      * Implementation of <code>IRegion</code> that can be reused
197      * by setting the offset and the length.
198      */

199     private static class ReusableRegion extends Position implements IRegion {}
200
201     /**
202      * Tells whether this class is in debug mode.
203      * @since 3.0
204      */

205     private static boolean DEBUG= "true".equalsIgnoreCase(Platform.getDebugOption("org.eclipse.jface.text/debug/AnnotationPainter")); //$NON-NLS-1$//$NON-NLS-2$
206
/**
207      * The squiggly painter strategy.
208      * @since 3.0
209      */

210     private static final IDrawingStrategy fgSquigglyDrawer= new SquigglesStrategy();
211     /**
212      * The squiggles painter id.
213      * @since 3.0
214      */

215     private static final Object JavaDoc SQUIGGLES= new Object JavaDoc();
216     /**
217      * The default strategy that does nothing.
218      * @since 3.0
219      */

220     private static final IDrawingStrategy fgNullDrawer= new NullStrategy();
221
222     /**
223      * The presentation information (decoration) for an annotation. Each such
224      * object represents one decoration drawn on the text area, such as squiggly lines
225      * and underlines.
226      */

227     private static class Decoration {
228         /** The position of this decoration */
229         private Position fPosition;
230         /** The color of this decoration */
231         private Color fColor;
232         /**
233          * The annotation's layer
234          * @since 3.0
235          */

236         private int fLayer;
237         /**
238          * The painter strategy for this decoration.
239          * @since 3.0
240          */

241         private IDrawingStrategy fPainter;
242     }
243
244     /** Indicates whether this painter is active */
245     private boolean fIsActive= false;
246     /** Indicates whether this painter is managing decorations */
247     private boolean fIsPainting= false;
248     /** Indicates whether this painter is setting its annotation model */
249     private volatile boolean fIsSettingModel= false;
250     /** The associated source viewer */
251     private ISourceViewer fSourceViewer;
252     /** The cached widget of the source viewer */
253     private StyledText fTextWidget;
254     /** The annotation model providing the annotations to be drawn */
255     private IAnnotationModel fModel;
256     /** The annotation access */
257     private IAnnotationAccess fAnnotationAccess;
258     /**
259      * The map with decorations
260      * @since 3.0
261      */

262     private Map JavaDoc fDecorationsMap= new HashMap JavaDoc(); // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=50767
263
/**
264      * The map with of highlighted decorations.
265      * @since 3.0
266      */

267     private Map JavaDoc fHighlightedDecorationsMap= new HashMap JavaDoc(); // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=50767
268
/**
269      * Mutex for highlighted decorations map.
270      * @since 3.0
271      */

272     private Object JavaDoc fDecorationMapLock= new Object JavaDoc();
273     /**
274      * Mutex for for decorations map.
275      * @since 3.0
276      */

277     private Object JavaDoc fHighlightedDecorationsMapLock= new Object JavaDoc();
278     /** The internal color table */
279     private Map JavaDoc fColorTable= new HashMap JavaDoc();
280     /**
281      * The list of configured annotation types for being painted by this painter.
282      * @since 3.0
283      */

284     private Set JavaDoc fConfiguredAnnotationTypes= new HashSet JavaDoc();
285     /**
286      * The list of allowed annotation types for being painted by this painter.
287      * @since 3.0
288      */

289     private Set JavaDoc fAllowedAnnotationTypes= new HashSet JavaDoc();
290     /**
291      * The list of configured annotation typed to be highlighted by this painter.
292      * @since 3.0
293      */

294     private Set JavaDoc fConfiguredHighlightAnnotationTypes= new HashSet JavaDoc();
295     /**
296      * The list of allowed annotation types to be highlighted by this painter.
297      * @since 3.0
298      */

299     private Set JavaDoc fAllowedHighlightAnnotationTypes= new HashSet JavaDoc();
300     /**
301      * The range in which the current highlight annotations can be found.
302      * @since 3.0
303      */

304     private Position fCurrentHighlightAnnotationRange= null;
305     /**
306      * The range in which all added, removed and changed highlight
307      * annotations can be found since the last world change.
308      * @since 3.0
309      */

310     private Position fTotalHighlightAnnotationRange= null;
311     /**
312      * The range in which the currently drawn annotations can be found.
313      * @since 3.3
314      */

315     private Position fCurrentDrawRange= null;
316     /**
317      * The range in which all added, removed and changed drawn
318      * annotations can be found since the last world change.
319      * @since 3.3
320      */

321     private Position fTotalDrawRange= null;
322     /**
323      * The text input listener.
324      * @since 3.0
325      */

326     private ITextInputListener fTextInputListener;
327     /**
328      * Flag which tells that a new document input is currently being set.
329      * @since 3.0
330      */

331     private boolean fInputDocumentAboutToBeChanged;
332     /**
333      * Maps annotation types to drawing strategy identifiers.
334      * @since 3.0
335      */

336     private Map JavaDoc fAnnotationType2DrawingStrategyId= new HashMap JavaDoc();
337     /**
338      * Maps drawing strategy identifiers to drawing strategies.
339      * @since 3.0
340      */

341     private Map JavaDoc fRegisteredDrawingStrategies= new HashMap JavaDoc();
342
343     /**
344      * Reuse this region for performance reasons.
345      * @since 3.3
346      */

347     private ReusableRegion fReusableRegion= new ReusableRegion();
348     
349     /**
350      * Creates a new annotation painter for the given source viewer and with the
351      * given annotation access. The painter is not initialized, i.e. no
352      * annotation types are configured to be painted.
353      *
354      * @param sourceViewer the source viewer for this painter
355      * @param access the annotation access for this painter
356      */

357     public AnnotationPainter(ISourceViewer sourceViewer, IAnnotationAccess access) {
358         fSourceViewer= sourceViewer;
359         fAnnotationAccess= access;
360         fTextWidget= sourceViewer.getTextWidget();
361
362         // default drawing strategies: squiggles were the only decoration style before version 3.0
363
fRegisteredDrawingStrategies.put(SQUIGGLES, fgSquigglyDrawer);
364     }
365
366     /**
367      * Returns whether this painter has to draw any squiggles.
368      *
369      * @return <code>true</code> if there are squiggles to be drawn, <code>false</code> otherwise
370      */

371     private boolean hasDecorations() {
372         synchronized (fDecorationMapLock) {
373             return !fDecorationsMap.isEmpty();
374         }
375     }
376
377     /**
378      * Enables painting. This painter registers a paint listener with the
379      * source viewer's widget.
380      */

381     private void enablePainting() {
382         if (!fIsPainting && hasDecorations()) {
383             fIsPainting= true;
384             fTextWidget.addPaintListener(this);
385             handleDrawRequest(null);
386         }
387     }
388
389     /**
390      * Disables painting, if is has previously been enabled. Removes
391      * any paint listeners registered with the source viewer's widget.
392      *
393      * @param redraw <code>true</code> if the widget should be redrawn after disabling
394      */

395     private void disablePainting(boolean redraw) {
396         if (fIsPainting) {
397             fIsPainting= false;
398             fTextWidget.removePaintListener(this);
399             if (redraw && hasDecorations())
400                 handleDrawRequest(null);
401         }
402     }
403
404     /**
405      * Sets the annotation model for this painter. Registers this painter
406      * as listener of the give model, if the model is not <code>null</code>.
407      *
408      * @param model the annotation model
409      */

410     private void setModel(IAnnotationModel model) {
411         if (fModel != model) {
412             if (fModel != null)
413                 fModel.removeAnnotationModelListener(this);
414             fModel= model;
415             if (fModel != null) {
416                 try {
417                     fIsSettingModel= true;
418                     fModel.addAnnotationModelListener(this);
419                 } finally {
420                     fIsSettingModel= false;
421                 }
422             }
423         }
424     }
425
426     /**
427      * Updates the set of decorations based on the current state of
428      * the painter's annotation model.
429      *
430      * @param event the annotation model event
431      */

432     private void catchupWithModel(AnnotationModelEvent event) {
433
434         synchronized (fDecorationMapLock) {
435             if (fDecorationsMap == null)
436                 return;
437         }
438
439         IRegion clippingRegion= computeClippingRegion(null, true);
440         IDocument document= fSourceViewer.getDocument();
441
442         int highlightAnnotationRangeStart= Integer.MAX_VALUE;
443         int highlightAnnotationRangeEnd= -1;
444         
445         int drawRangeStart= Integer.MAX_VALUE;
446         int drawRangeEnd= -1;
447
448         if (fModel != null) {
449
450             Map JavaDoc decorationsMap;
451             Map JavaDoc highlightedDecorationsMap;
452
453             // Clone decoration maps
454
synchronized (fDecorationMapLock) {
455                 decorationsMap= new HashMap JavaDoc(fDecorationsMap);
456             }
457             synchronized (fHighlightedDecorationsMapLock) {
458                 highlightedDecorationsMap= new HashMap JavaDoc(fHighlightedDecorationsMap);
459             }
460
461             boolean isWorldChange= false;
462
463             Iterator JavaDoc e;
464             if (event == null || event.isWorldChange()) {
465                 isWorldChange= true;
466
467                 if (DEBUG && event == null)
468                     System.out.println("AP: INTERNAL CHANGE"); //$NON-NLS-1$
469

470                 Iterator JavaDoc iter= decorationsMap.entrySet().iterator();
471                 while (iter.hasNext()) {
472                     Map.Entry JavaDoc entry= (Map.Entry JavaDoc)iter.next();
473                     Annotation annotation= (Annotation)entry.getKey();
474                     Decoration decoration= (Decoration)entry.getValue();
475                     drawDecoration(decoration, null, annotation, clippingRegion, document);
476                 }
477                 
478                 decorationsMap.clear();
479                 
480                 highlightedDecorationsMap.clear();
481
482                 e= fModel.getAnnotationIterator();
483
484
485             } else {
486
487                 // Remove annotations
488
Annotation[] removedAnnotations= event.getRemovedAnnotations();
489                 for (int i=0, length= removedAnnotations.length; i < length; i++) {
490                     Annotation annotation= removedAnnotations[i];
491                     Decoration decoration= (Decoration)highlightedDecorationsMap.remove(annotation);
492                     if (decoration != null) {
493                         Position position= decoration.fPosition;
494                         if (position != null) {
495                             highlightAnnotationRangeStart= Math.min(highlightAnnotationRangeStart, position.offset);
496                             highlightAnnotationRangeEnd= Math.max(highlightAnnotationRangeEnd, position.offset + position.length);
497                         }
498                     }
499                     decoration= (Decoration)decorationsMap.remove(annotation);
500                     if (decoration != null) {
501                         drawDecoration(decoration, null, annotation, clippingRegion, document);
502                         Position position= decoration.fPosition;
503                         if (position != null) {
504                             drawRangeStart= Math.min(drawRangeStart, position.offset);
505                             drawRangeEnd= Math.max(drawRangeEnd, position.offset + position.length);
506                         }
507                     }
508                     
509                 }
510
511                 // Update existing annotations
512
Annotation[] changedAnnotations= event.getChangedAnnotations();
513                 for (int i=0, length= changedAnnotations.length; i < length; i++) {
514                     Annotation annotation= changedAnnotations[i];
515
516                     Object JavaDoc annotationType= annotation.getType();
517                     boolean isHighlighting= shouldBeHighlighted(annotationType);
518                     boolean usesDrawingStrategy= shouldBeDrawn(annotationType);
519
520                     Decoration decoration= (Decoration)highlightedDecorationsMap.get(annotation);
521
522                     if (decoration != null) {
523                         // The call below updates the decoration - no need to create new decoration
524
decoration= getDecoration(annotation, decoration, usesDrawingStrategy, isHighlighting);
525                         if (decoration == null)
526                             highlightedDecorationsMap.remove(annotation);
527                     } else {
528                         decoration= getDecoration(annotation, decoration, usesDrawingStrategy, isHighlighting);
529                         if (decoration != null && isHighlighting)
530                             highlightedDecorationsMap.put(annotation, decoration);
531                     }
532
533                     Position position= null;
534                     if (decoration == null)
535                         position= fModel.getPosition(annotation);
536                     else
537                         position= decoration.fPosition;
538
539                     if (position != null && !position.isDeleted()) {
540                         if (isHighlighting) {
541                             highlightAnnotationRangeStart= Math.min(highlightAnnotationRangeStart, position.offset);
542                             highlightAnnotationRangeEnd= Math.max(highlightAnnotationRangeEnd, position.offset + position.length);
543                         }
544                         if (usesDrawingStrategy) {
545                             drawRangeStart= Math.min(drawRangeStart, position.offset);
546                             drawRangeEnd= Math.max(drawRangeEnd, position.offset + position.length);
547                         }
548                     } else {
549                         highlightedDecorationsMap.remove(annotation);
550                     }
551
552                     if (usesDrawingStrategy) {
553                         Decoration oldDecoration= (Decoration)decorationsMap.get(annotation);
554                         if (oldDecoration != null) {
555                             drawDecoration(oldDecoration, null, annotation, clippingRegion, document);
556                         
557                         if (decoration != null)
558                             decorationsMap.put(annotation, decoration);
559                         else if (oldDecoration != null)
560                             decorationsMap.remove(annotation);
561                         }
562                     }
563                 }
564
565                 e= Arrays.asList(event.getAddedAnnotations()).iterator();
566             }
567
568             // Add new annotations
569
while (e.hasNext()) {
570                 Annotation annotation= (Annotation) e.next();
571
572                 Object JavaDoc annotationType= annotation.getType();
573                 boolean isHighlighting= shouldBeHighlighted(annotationType);
574                 boolean usesDrawingStrategy= shouldBeDrawn(annotationType);
575
576                 Decoration pp= getDecoration(annotation, null, usesDrawingStrategy, isHighlighting);
577                 if (pp != null) {
578
579                     if (usesDrawingStrategy) {
580                         decorationsMap.put(annotation, pp);
581                         drawRangeStart= Math.min(drawRangeStart, pp.fPosition.offset);
582                         drawRangeEnd= Math.max(drawRangeEnd, pp.fPosition.offset + pp.fPosition.length);
583                     }
584
585                     if (isHighlighting) {
586                         highlightedDecorationsMap.put(annotation, pp);
587                         highlightAnnotationRangeStart= Math.min(highlightAnnotationRangeStart, pp.fPosition.offset);
588                         highlightAnnotationRangeEnd= Math.max(highlightAnnotationRangeEnd, pp.fPosition.offset + pp.fPosition.length);
589                     }
590                     
591                 }
592             }
593
594             synchronized (fDecorationMapLock) {
595                 fDecorationsMap= decorationsMap;
596                 updateDrawRanges(drawRangeStart, drawRangeEnd, isWorldChange);
597             }
598
599             synchronized (fHighlightedDecorationsMapLock) {
600                 fHighlightedDecorationsMap= highlightedDecorationsMap;
601                 updateHighlightRanges(highlightAnnotationRangeStart, highlightAnnotationRangeEnd, isWorldChange);
602             }
603         } else {
604             // annotation model is null -> clear all
605
synchronized (fDecorationMapLock) {
606                 fDecorationsMap.clear();
607             }
608             synchronized (fHighlightedDecorationsMapLock) {
609                 fHighlightedDecorationsMap.clear();
610             }
611         }
612     }
613
614     /**
615      * Updates the remembered highlight ranges.
616      *
617      * @param highlightAnnotationRangeStart the start of the range
618      * @param highlightAnnotationRangeEnd the end of the range
619      * @param isWorldChange tells whether the range belongs to a annotation model event reporting a world change
620      * @since 3.0
621      */

622     private void updateHighlightRanges(int highlightAnnotationRangeStart, int highlightAnnotationRangeEnd, boolean isWorldChange) {
623         if (highlightAnnotationRangeStart != Integer.MAX_VALUE) {
624
625             int maxRangeStart= highlightAnnotationRangeStart;
626             int maxRangeEnd= highlightAnnotationRangeEnd;
627
628             if (fTotalHighlightAnnotationRange != null) {
629                 maxRangeStart= Math.min(maxRangeStart, fTotalHighlightAnnotationRange.offset);
630                 maxRangeEnd= Math.max(maxRangeEnd, fTotalHighlightAnnotationRange.offset + fTotalHighlightAnnotationRange.length);
631             }
632
633             if (fTotalHighlightAnnotationRange == null)
634                 fTotalHighlightAnnotationRange= new Position(0);
635             if (fCurrentHighlightAnnotationRange == null)
636                 fCurrentHighlightAnnotationRange= new Position(0);
637
638             if (isWorldChange) {
639                 fTotalHighlightAnnotationRange.offset= highlightAnnotationRangeStart;
640                 fTotalHighlightAnnotationRange.length= highlightAnnotationRangeEnd - highlightAnnotationRangeStart;
641                 fCurrentHighlightAnnotationRange.offset= maxRangeStart;
642                 fCurrentHighlightAnnotationRange.length= maxRangeEnd - maxRangeStart;
643             } else {
644                 fTotalHighlightAnnotationRange.offset= maxRangeStart;
645                 fTotalHighlightAnnotationRange.length= maxRangeEnd - maxRangeStart;
646                 fCurrentHighlightAnnotationRange.offset=highlightAnnotationRangeStart;
647                 fCurrentHighlightAnnotationRange.length= highlightAnnotationRangeEnd - highlightAnnotationRangeStart;
648             }
649         } else {
650             if (isWorldChange) {
651                 fCurrentHighlightAnnotationRange= fTotalHighlightAnnotationRange;
652                 fTotalHighlightAnnotationRange= null;
653             } else {
654                 fCurrentHighlightAnnotationRange= null;
655             }
656         }
657
658         adaptToDocumentLength(fCurrentHighlightAnnotationRange);
659         adaptToDocumentLength(fTotalHighlightAnnotationRange);
660     }
661     
662     /**
663      * Updates the remembered decoration ranges.
664      *
665      * @param drawRangeStart the start of the range
666      * @param drawRangeEnd the end of the range
667      * @param isWorldChange tells whether the range belongs to a annotation model event reporting a world change
668      * @since 3.3
669      */

670     private void updateDrawRanges(int drawRangeStart, int drawRangeEnd, boolean isWorldChange) {
671         if (drawRangeStart != Integer.MAX_VALUE) {
672             
673             int maxRangeStart= drawRangeStart;
674             int maxRangeEnd= drawRangeEnd;
675             
676             if (fTotalDrawRange != null) {
677                 maxRangeStart= Math.min(maxRangeStart, fTotalDrawRange.offset);
678                 maxRangeEnd= Math.max(maxRangeEnd, fTotalDrawRange.offset + fTotalDrawRange.length);
679             }
680             
681             if (fTotalDrawRange == null)
682                 fTotalDrawRange= new Position(0);
683             if (fCurrentDrawRange == null)
684                 fCurrentDrawRange= new Position(0);
685             
686             if (isWorldChange) {
687                 fTotalDrawRange.offset= drawRangeStart;
688                 fTotalDrawRange.length= drawRangeEnd - drawRangeStart;
689                 fCurrentDrawRange.offset= maxRangeStart;
690                 fCurrentDrawRange.length= maxRangeEnd - maxRangeStart;
691             } else {
692                 fTotalDrawRange.offset= maxRangeStart;
693                 fTotalDrawRange.length= maxRangeEnd - maxRangeStart;
694                 fCurrentDrawRange.offset=drawRangeStart;
695                 fCurrentDrawRange.length= drawRangeEnd - drawRangeStart;
696             }
697         } else {
698             if (isWorldChange) {
699                 fCurrentDrawRange= fTotalDrawRange;
700                 fTotalDrawRange= null;
701             } else {
702                 fCurrentDrawRange= null;
703             }
704         }
705         
706         adaptToDocumentLength(fCurrentDrawRange);
707         adaptToDocumentLength(fTotalDrawRange);
708     }
709
710     /**
711      * Adapts the given position to the document length.
712      *
713      * @param position the position to adapt
714      * @since 3.0
715      */

716     private void adaptToDocumentLength(Position position) {
717         if (position == null)
718             return;
719
720         int length= fSourceViewer.getDocument().getLength();
721         position.offset= Math.min(position.offset, length);
722         position.length= Math.min(position.length, length - position.offset);
723     }
724
725     /**
726      * Returns a decoration for the given annotation if this
727      * annotation is valid and shown by this painter.
728      *
729      * @param annotation the annotation
730      * @param decoration the decoration to be adapted and returned or <code>null</code> if a new one must be created
731      * @param isDrawingSquiggles tells if squiggles should be drawn for this annotation
732      * @param isHighlighting tells if this annotation should be highlighted
733      * @return the decoration or <code>null</code> if there's no valid one
734      * @since 3.0
735      */

736     private Decoration getDecoration(Annotation annotation, Decoration decoration, boolean isDrawingSquiggles, boolean isHighlighting) {
737
738         if (annotation.isMarkedDeleted())
739             return null;
740
741         Color color= null;
742
743         if (isDrawingSquiggles || isHighlighting)
744             color= findColor(annotation.getType());
745
746         if (color == null)
747             return null;
748
749         Position position= fModel.getPosition(annotation);
750         if (position == null || position.isDeleted())
751             return null;
752
753         if (decoration == null)
754             decoration= new Decoration();
755
756         decoration.fPosition= position;
757         decoration.fColor= color;
758         if (fAnnotationAccess instanceof IAnnotationAccessExtension) {
759             IAnnotationAccessExtension extension= (IAnnotationAccessExtension) fAnnotationAccess;
760             decoration.fLayer= extension.getLayer(annotation);
761         } else {
762             decoration.fLayer= IAnnotationAccessExtension.DEFAULT_LAYER;
763         }
764         decoration.fPainter= getDrawingStrategy(annotation);
765
766         return decoration;
767     }
768
769     /**
770      * Returns the drawing type for the given annotation.
771      *
772      * @param annotation the annotation
773      * @return the annotation painter
774      * @since 3.0
775      */

776     private IDrawingStrategy getDrawingStrategy(Annotation annotation) {
777         String JavaDoc type= annotation.getType();
778         IDrawingStrategy strategy = (IDrawingStrategy) fRegisteredDrawingStrategies.get(fAnnotationType2DrawingStrategyId.get(type));
779         if (strategy != null)
780             return strategy;
781
782         if (fAnnotationAccess instanceof IAnnotationAccessExtension) {
783             IAnnotationAccessExtension ext = (IAnnotationAccessExtension) fAnnotationAccess;
784             Object JavaDoc[] sts = ext.getSupertypes(type);
785             for (int i= 0; i < sts.length; i++) {
786                 strategy= (IDrawingStrategy) fRegisteredDrawingStrategies.get(fAnnotationType2DrawingStrategyId.get(sts[i]));
787                 if (strategy != null)
788                     return strategy;
789             }
790         }
791
792         return fgNullDrawer;
793
794     }
795
796     /**
797      * Returns whether the given annotation type should be drawn.
798      *
799      * @param annotationType the annotation type
800      * @return <code>true</code> if annotation type should be drawn, <code>false</code>
801      * otherwise
802      * @since 3.0
803      */

804     private boolean shouldBeDrawn(Object JavaDoc annotationType) {
805         return contains(annotationType, fAllowedAnnotationTypes, fConfiguredAnnotationTypes);
806     }
807
808     /**
809      * Returns whether the given annotation type should be highlighted.
810      *
811      * @param annotationType the annotation type
812      * @return <code>true</code> if annotation type should be highlighted, <code>false</code>
813      * otherwise
814      * @since 3.0
815      */

816     private boolean shouldBeHighlighted(Object JavaDoc annotationType) {
817         return contains(annotationType, fAllowedHighlightAnnotationTypes, fConfiguredHighlightAnnotationTypes);
818     }
819
820     /**
821      * Returns whether the given annotation type is contained in the given <code>allowed</code>
822      * set. This is the case if the type is either in the set
823      * or covered by the <code>configured</code> set.
824      *
825      * @param annotationType the annotation type
826      * @param allowed set with allowed annotation types
827      * @param configured set with configured annotation types
828      * @return <code>true</code> if annotation is contained, <code>false</code>
829      * otherwise
830      * @since 3.0
831      */

832     private boolean contains(Object JavaDoc annotationType, Set JavaDoc allowed, Set JavaDoc configured) {
833         if (allowed.contains(annotationType))
834             return true;
835
836         boolean covered= isCovered(annotationType, configured);
837         if (covered)
838             allowed.add(annotationType);
839
840         return covered;
841     }
842
843     /**
844      * Computes whether the annotations of the given type are covered by the given <code>configured</code>
845      * set. This is the case if either the type of the annotation or any of its
846      * super types is contained in the <code>configured</code> set.
847      *
848      * @param annotationType the annotation type
849      * @param configured set with configured annotation types
850      * @return <code>true</code> if annotation is covered, <code>false</code>
851      * otherwise
852      * @since 3.0
853      */

854     private boolean isCovered(Object JavaDoc annotationType, Set JavaDoc configured) {
855         if (fAnnotationAccess instanceof IAnnotationAccessExtension) {
856             IAnnotationAccessExtension extension= (IAnnotationAccessExtension) fAnnotationAccess;
857             Iterator JavaDoc e= configured.iterator();
858             while (e.hasNext()) {
859                 if (extension.isSubtype(annotationType,e.next()))
860                     return true;
861             }
862             return false;
863         }
864         return configured.contains(annotationType);
865     }
866
867     /**
868      * Returns the color for the given annotation type
869      *
870      * @param annotationType the annotation type
871      * @return the color
872      * @since 3.0
873      */

874     private Color findColor(Object JavaDoc annotationType) {
875         Color color= (Color) fColorTable.get(annotationType);
876         if (color != null)
877             return color;
878
879         if (fAnnotationAccess instanceof IAnnotationAccessExtension) {
880             IAnnotationAccessExtension extension= (IAnnotationAccessExtension) fAnnotationAccess;
881             Object JavaDoc[] superTypes= extension.getSupertypes(annotationType);
882             if (superTypes != null) {
883                 for (int i= 0; i < superTypes.length; i++) {
884                     color= (Color) fColorTable.get(superTypes[i]);
885                     if (color != null)
886                         return color;
887                 }
888             }
889         }
890
891         return null;
892     }
893
894     /**
895      * Recomputes the squiggles to be drawn and redraws them.
896      *
897      * @param event the annotation model event
898      * @since 3.0
899      */

900     private void updatePainting(AnnotationModelEvent event) {
901         disablePainting(event == null);
902
903         catchupWithModel(event);
904
905         if (!fInputDocumentAboutToBeChanged)
906             invalidateTextPresentation();
907
908         enablePainting();
909     }
910
911     private void invalidateTextPresentation() {
912         IRegion r= null;
913         synchronized (fHighlightedDecorationsMapLock) {
914             if (fCurrentHighlightAnnotationRange != null)
915                 r= new Region(fCurrentHighlightAnnotationRange.getOffset(), fCurrentHighlightAnnotationRange.getLength());
916         }
917         if (r == null)
918             return;
919
920         if (fSourceViewer instanceof ITextViewerExtension2) {
921             if (DEBUG)
922                 System.out.println("AP: invalidating offset: " + r.getOffset() + ", length= " + r.getLength()); //$NON-NLS-1$ //$NON-NLS-2$
923

924             ((ITextViewerExtension2)fSourceViewer).invalidateTextPresentation(r.getOffset(), r.getLength());
925
926         } else {
927             fSourceViewer.invalidateTextPresentation();
928         }
929     }
930
931     /*
932      * @see org.eclipse.jface.text.ITextPresentationListener#applyTextPresentation(org.eclipse.jface.text.TextPresentation)
933      * @since 3.0
934      */

935     public void applyTextPresentation(TextPresentation tp) {
936         Set JavaDoc decorations;
937
938         synchronized (fHighlightedDecorationsMapLock) {
939             if (fHighlightedDecorationsMap == null || fHighlightedDecorationsMap.isEmpty())
940                 return;
941
942             decorations= new HashSet JavaDoc(fHighlightedDecorationsMap.entrySet());
943         }
944
945         IRegion region= tp.getExtent();
946
947         if (DEBUG)
948             System.out.println("AP: applying text presentation offset: " + region.getOffset() + ", length= " + region.getLength()); //$NON-NLS-1$ //$NON-NLS-2$
949

950         for (int layer= 0, maxLayer= 1; layer < maxLayer; layer++) {
951
952             for (Iterator JavaDoc iter= decorations.iterator(); iter.hasNext();) {
953                 Map.Entry JavaDoc entry= (Map.Entry JavaDoc)iter.next();
954
955                 Annotation a= (Annotation)entry.getKey();
956                 if (a.isMarkedDeleted())
957                     continue;
958
959                 Decoration pp = (Decoration)entry.getValue();
960
961                 maxLayer= Math.max(maxLayer, pp.fLayer + 1); // dynamically update layer maximum
962
if (pp.fLayer != layer) // wrong layer: skip annotation
963
continue;
964
965                 Position p= pp.fPosition;
966                 if (fSourceViewer instanceof ITextViewerExtension5) {
967                     ITextViewerExtension5 extension3= (ITextViewerExtension5) fSourceViewer;
968                     if (null == extension3.modelRange2WidgetRange(new Region(p.getOffset(), p.getLength())))
969                         continue;
970                 } else if (!fSourceViewer.overlapsWithVisibleRegion(p.offset, p.length)) {
971                     continue;
972                 }
973
974                 int regionEnd= region.getOffset() + region.getLength();
975                 int pEnd= p.getOffset() + p.getLength();
976                 if (pEnd >= region.getOffset() && regionEnd > p.getOffset()) {
977                     int start= Math.max(p.getOffset(), region.getOffset());
978                     int end= Math.min(regionEnd, pEnd);
979                     int length= Math.max(end - start, 0);
980                     tp.mergeStyleRange(new StyleRange(start, length, null, pp.fColor));
981                 }
982             }
983         }
984     }
985
986     /*
987      * @see org.eclipse.jface.text.source.IAnnotationModelListener#modelChanged(org.eclipse.jface.text.source.IAnnotationModel)
988      */

989     public synchronized void modelChanged(final IAnnotationModel model) {
990         if (DEBUG)
991             System.err.println("AP: OLD API of AnnotationModelListener called"); //$NON-NLS-1$
992

993         modelChanged(new AnnotationModelEvent(model));
994     }
995
996     /*
997      * @see org.eclipse.jface.text.source.IAnnotationModelListenerExtension#modelChanged(org.eclipse.jface.text.source.AnnotationModelEvent)
998      */

999     public void modelChanged(final AnnotationModelEvent event) {
1000        Display textWidgetDisplay;
1001        try {
1002            StyledText textWidget= fTextWidget;
1003            if (textWidget == null || textWidget.isDisposed())
1004                return;
1005            textWidgetDisplay= textWidget.getDisplay();
1006        } catch (SWTException ex) {
1007            if (ex.code == SWT.ERROR_WIDGET_DISPOSED)
1008                return;
1009            throw ex;
1010        }
1011
1012        if (fIsSettingModel) {
1013            // inside the UI thread -> no need for posting
1014
if (textWidgetDisplay == Display.getCurrent())
1015                updatePainting(event);
1016            else {
1017                /*
1018                 * we can throw away the changes since
1019                 * further update painting will happen
1020                 */

1021                return;
1022            }
1023        } else {
1024            if (DEBUG && event != null && event.isWorldChange()) {
1025                System.out.println("AP: WORLD CHANGED, stack trace follows:"); //$NON-NLS-1$
1026
new Throwable JavaDoc().printStackTrace(System.out);
1027            }
1028
1029            // XXX: posting here is a problem for annotations that are being
1030
// removed and the positions of which are not updated to document
1031
// changes any more. If the document gets modified between
1032
// now and running the posted runnable, the position information
1033
// is not accurate any longer.
1034
textWidgetDisplay.asyncExec(new Runnable JavaDoc() {
1035                public void run() {
1036                    if (fTextWidget != null && !fTextWidget.isDisposed())
1037                        updatePainting(event);
1038                }
1039            });
1040        }
1041    }
1042
1043    /**
1044     * Sets the color in which the squiggly for the given annotation type should be drawn.
1045     *
1046     * @param annotationType the annotation type
1047     * @param color the color
1048     */

1049    public void setAnnotationTypeColor(Object JavaDoc annotationType, Color color) {
1050        if (color != null)
1051            fColorTable.put(annotationType, color);
1052        else
1053            fColorTable.remove(annotationType);
1054    }
1055
1056    /**
1057     * Adds the given annotation type to the list of annotation types whose
1058     * annotations should be painted by this painter using squiggly drawing. If the annotation type
1059     * is already in this list, this method is without effect.
1060     *
1061     * @param annotationType the annotation type
1062     */

1063    public void addAnnotationType(Object JavaDoc annotationType) {
1064        addAnnotationType(annotationType, SQUIGGLES);
1065    }
1066
1067    /**
1068     * Adds the given annotation type to the list of annotation types whose
1069     * annotations should be painted by this painter using the given drawing strategy.
1070     * If the annotation type is already in this list, the old drawing strategy gets replaced.
1071     *
1072     * @param annotationType the annotation type
1073     * @param drawingStrategyID the id of the drawing strategy that should be used for this annotation type
1074     * @since 3.0
1075     */

1076    public void addAnnotationType(Object JavaDoc annotationType, Object JavaDoc drawingStrategyID) {
1077        fConfiguredAnnotationTypes.add(annotationType);
1078        fAnnotationType2DrawingStrategyId.put(annotationType, drawingStrategyID);
1079    }
1080
1081    /**
1082     * Registers a new drawing strategy under the given ID. If there is already a
1083     * strategy registered under <code>id</code>, the old strategy gets replaced.
1084     * <p>The given id can be referenced when adding annotation types, see
1085     * {@link #addAnnotationType(Object, Object)}.</p>
1086     *
1087     * @param id the identifier under which the strategy can be referenced, not <code>null</code>
1088     * @param strategy the new strategy
1089     * @since 3.0
1090     */

1091    public void addDrawingStrategy(Object JavaDoc id, IDrawingStrategy strategy) {
1092        // don't permit null as null is used to signal that an annotation type is not
1093
// registered with a specific strategy, and that its annotation hierarchy should be searched
1094
if (id == null)
1095            throw new IllegalArgumentException JavaDoc();
1096        fRegisteredDrawingStrategies.put(id, strategy);
1097    }
1098
1099    /**
1100     * Adds the given annotation type to the list of annotation types whose
1101     * annotations should be highlighted this painter. If the annotation type
1102     * is already in this list, this method is without effect.
1103     *
1104     * @param annotationType the annotation type
1105     * @since 3.0
1106     */

1107    public void addHighlightAnnotationType(Object JavaDoc annotationType) {
1108        fConfiguredHighlightAnnotationTypes.add(annotationType);
1109        if (fTextInputListener == null) {
1110            fTextInputListener= new ITextInputListener() {
1111                /*
1112                 * @see org.eclipse.jface.text.ITextInputListener#inputDocumentAboutToBeChanged(org.eclipse.jface.text.IDocument, org.eclipse.jface.text.IDocument)
1113                 */

1114                public void inputDocumentAboutToBeChanged(IDocument oldInput, IDocument newInput) {
1115                    fInputDocumentAboutToBeChanged= true;
1116                }
1117                /*
1118                 * @see org.eclipse.jface.text.ITextInputListener#inputDocumentChanged(org.eclipse.jface.text.IDocument, org.eclipse.jface.text.IDocument)
1119                 */

1120                public void inputDocumentChanged(IDocument oldInput, IDocument newInput) {
1121                    fInputDocumentAboutToBeChanged= false;
1122                }
1123            };
1124            fSourceViewer.addTextInputListener(fTextInputListener);
1125        }
1126    }
1127
1128    /**
1129     * Removes the given annotation type from the list of annotation types whose
1130     * annotations are painted by this painter. If the annotation type is not
1131     * in this list, this method is without effect.
1132     *
1133     * @param annotationType the annotation type
1134     */

1135    public void removeAnnotationType(Object JavaDoc annotationType) {
1136        fConfiguredAnnotationTypes.remove(annotationType);
1137        fAllowedAnnotationTypes.clear();
1138    }
1139
1140    /**
1141     * Removes the given annotation type from the list of annotation types whose
1142     * annotations are highlighted by this painter. If the annotation type is not
1143     * in this list, this method is without effect.
1144     *
1145     * @param annotationType the annotation type
1146     * @since 3.0
1147     */

1148    public void removeHighlightAnnotationType(Object JavaDoc annotationType) {
1149        fConfiguredHighlightAnnotationTypes.remove(annotationType);
1150        fAllowedHighlightAnnotationTypes.clear();
1151        if (fConfiguredHighlightAnnotationTypes.isEmpty() && fTextInputListener != null) {
1152            fSourceViewer.removeTextInputListener(fTextInputListener);
1153            fTextInputListener= null;
1154            fInputDocumentAboutToBeChanged= false;
1155        }
1156    }
1157
1158    /**
1159     * Clears the list of annotation types whose annotations are
1160     * painted by this painter.
1161     */

1162    public void removeAllAnnotationTypes() {
1163        fConfiguredAnnotationTypes.clear();
1164        fAllowedAnnotationTypes.clear();
1165        fConfiguredHighlightAnnotationTypes.clear();
1166        fAllowedHighlightAnnotationTypes.clear();
1167        if (fTextInputListener != null) {
1168            fSourceViewer.removeTextInputListener(fTextInputListener);
1169            fTextInputListener= null;
1170        }
1171    }
1172
1173    /**
1174     * Returns whether the list of annotation types whose annotations are painted
1175     * by this painter contains at least on element.
1176     *
1177     * @return <code>true</code> if there is an annotation type whose annotations are painted
1178     */

1179    public boolean isPaintingAnnotations() {
1180        return !fConfiguredAnnotationTypes.isEmpty() || !fConfiguredHighlightAnnotationTypes.isEmpty();
1181    }
1182
1183    /*
1184     * @see org.eclipse.jface.text.IPainter#dispose()
1185     */

1186    public void dispose() {
1187
1188        if (fColorTable != null)
1189            fColorTable.clear();
1190        fColorTable= null;
1191
1192        if (fConfiguredAnnotationTypes != null)
1193            fConfiguredAnnotationTypes.clear();
1194        fConfiguredAnnotationTypes= null;
1195
1196        if (fAllowedAnnotationTypes != null)
1197            fAllowedAnnotationTypes.clear();
1198        fAllowedAnnotationTypes= null;
1199
1200        if (fConfiguredHighlightAnnotationTypes != null)
1201            fConfiguredHighlightAnnotationTypes.clear();
1202        fConfiguredHighlightAnnotationTypes= null;
1203
1204        if (fAllowedHighlightAnnotationTypes != null)
1205            fAllowedHighlightAnnotationTypes.clear();
1206        fAllowedHighlightAnnotationTypes= null;
1207
1208        fTextWidget= null;
1209        fSourceViewer= null;
1210        fAnnotationAccess= null;
1211        fModel= null;
1212        synchronized (fDecorationMapLock) {
1213            fDecorationsMap= null;
1214        }
1215        synchronized (fHighlightedDecorationsMapLock) {
1216            fHighlightedDecorationsMap= null;
1217        }
1218    }
1219
1220    /**
1221     * Returns the document offset of the upper left corner of the source viewer's view port,
1222     * possibly including partially visible lines.
1223     *
1224     * @return the document offset if the upper left corner of the view port
1225     */

1226    private int getInclusiveTopIndexStartOffset() {
1227
1228        if (fTextWidget != null && !fTextWidget.isDisposed()) {
1229            int top= JFaceTextUtil.getPartialTopIndex(fSourceViewer);
1230            try {
1231                IDocument document= fSourceViewer.getDocument();
1232                return document.getLineOffset(top);
1233            } catch (BadLocationException x) {
1234            }
1235        }
1236
1237        return -1;
1238    }
1239
1240    /**
1241     * Returns the first invisible document offset of the lower right corner of the source viewer's view port,
1242     * possibly including partially visible lines.
1243     *
1244     * @return the first invisible document offset of the lower right corner of the view port
1245     */

1246    private int getExclusiveBottomIndexEndOffset() {
1247
1248        if (fTextWidget != null && !fTextWidget.isDisposed()) {
1249            int bottom= JFaceTextUtil.getPartialBottomIndex(fSourceViewer);
1250            try {
1251                IDocument document= fSourceViewer.getDocument();
1252
1253                if (bottom >= document.getNumberOfLines())
1254                    bottom= document.getNumberOfLines() - 1;
1255
1256                return document.getLineOffset(bottom) + document.getLineLength(bottom);
1257            } catch (BadLocationException x) {
1258            }
1259        }
1260
1261        return -1;
1262    }
1263
1264    /*
1265     * @see org.eclipse.swt.events.PaintListener#paintControl(org.eclipse.swt.events.PaintEvent)
1266     */

1267    public void paintControl(PaintEvent event) {
1268        if (fTextWidget != null)
1269            handleDrawRequest(event);
1270    }
1271
1272    /**
1273     * Handles the request to draw the annotations using the given graphical context.
1274     *
1275     * @param event the paint event or <code>null</code>
1276     */

1277    private void handleDrawRequest(PaintEvent event) {
1278
1279        if (fTextWidget == null) {
1280            // is already disposed
1281
return;
1282        }
1283
1284        IRegion clippingRegion= computeClippingRegion(event, false);
1285        if (clippingRegion == null)
1286            return;
1287        
1288        int vOffset= clippingRegion.getOffset();
1289        int vLength= clippingRegion.getLength();
1290        
1291        final GC gc= event != null ? event.gc : null;
1292
1293        // Clone decorations
1294
Collection JavaDoc decorations;
1295        synchronized (fDecorationMapLock) {
1296            decorations= new ArrayList JavaDoc(fDecorationsMap.size());
1297            decorations.addAll(fDecorationsMap.entrySet());
1298        }
1299
1300        /*
1301         * Create a new list of annotations to be drawn, since removing from decorations is more
1302         * expensive. One bucket per drawing layer. Use linked lists as addition is cheap here.
1303         */

1304        ArrayList JavaDoc toBeDrawn= new ArrayList JavaDoc(10);
1305        for (Iterator JavaDoc e = decorations.iterator(); e.hasNext();) {
1306            Map.Entry JavaDoc entry= (Map.Entry JavaDoc)e.next();
1307            
1308            Annotation a= (Annotation)entry.getKey();
1309            Decoration pp = (Decoration)entry.getValue();
1310            // prune any annotation that is not drawable or does not need drawing
1311
if (!(a.isMarkedDeleted() || pp.fPainter == fgNullDrawer || pp.fPainter instanceof NullStrategy || skip(a) || !pp.fPosition.overlapsWith(vOffset, vLength))) {
1312                // ensure sized appropriately
1313
for (int i= toBeDrawn.size(); i <= pp.fLayer; i++)
1314                    toBeDrawn.add(new LinkedList JavaDoc());
1315                ((List JavaDoc) toBeDrawn.get(pp.fLayer)).add(entry);
1316            }
1317        }
1318        IDocument document= fSourceViewer.getDocument();
1319        for (Iterator JavaDoc it= toBeDrawn.iterator(); it.hasNext();) {
1320            List JavaDoc layer= (List JavaDoc) it.next();
1321            for (Iterator JavaDoc e = layer.iterator(); e.hasNext();) {
1322                Map.Entry JavaDoc entry= (Map.Entry JavaDoc)e.next();
1323                Annotation a= (Annotation)entry.getKey();
1324                Decoration pp = (Decoration)entry.getValue();
1325                drawDecoration(pp, gc, a, clippingRegion, document);
1326            }
1327        }
1328    }
1329    
1330    private void drawDecoration(Decoration pp, GC gc, Annotation annotation, IRegion clippingRegion, IDocument document) {
1331        if (clippingRegion == null || pp.fPainter == fgNullDrawer)
1332            return;
1333
1334        int clippingOffset= clippingRegion.getOffset();
1335        int clippingLength= clippingRegion.getLength();
1336
1337        Position p= pp.fPosition;
1338        try {
1339
1340            int startLine= document.getLineOfOffset(p.getOffset());
1341            int lastInclusive= Math.max(p.getOffset(), p.getOffset() + p.getLength() - 1);
1342            int endLine= document.getLineOfOffset(lastInclusive);
1343
1344            for (int i= startLine; i <= endLine; i++) {
1345                int lineOffset= document.getLineOffset(i);
1346                int paintStart= Math.max(lineOffset, p.getOffset());
1347                String JavaDoc lineDelimiter= document.getLineDelimiter(i);
1348                int delimiterLength= lineDelimiter != null ? lineDelimiter.length() : 0;
1349                int paintLength= Math.min(lineOffset + document.getLineLength(i) - delimiterLength, p.getOffset() + p.getLength()) - paintStart;
1350                if (paintLength >= 0 && overlapsWith(paintStart, paintLength, clippingOffset, clippingLength)) {
1351                    // otherwise inside a line delimiter
1352
IRegion widgetRange= getWidgetRange(paintStart, paintLength);
1353                    if (widgetRange != null) {
1354                        pp.fPainter.draw(annotation, gc, fTextWidget, widgetRange.getOffset(), widgetRange.getLength(), pp.fColor);
1355                    }
1356                }
1357            }
1358
1359        } catch (BadLocationException x) {
1360        }
1361    }
1362    
1363    /**
1364     * Computes the model (document) region that is covered by the paint event's clipping region. If
1365     * <code>event</code> is <code>null</code>, the model range covered by the visible editor
1366     * area (viewport) is returned.
1367     *
1368     * @param event the paint event or <code>null</code> to use the entire viewport
1369     * @param isClearing tells whether the clipping is need for clearing an annotation
1370     * @return the model region comprised by either the paint event's clipping region or the
1371     * viewport
1372     * @since 3.2
1373     */

1374    private IRegion computeClippingRegion(PaintEvent event, boolean isClearing) {
1375        if (event == null) {
1376            
1377            if (!isClearing && fCurrentDrawRange != null)
1378                return new Region(fCurrentDrawRange.offset, fCurrentDrawRange.length);
1379            
1380            // trigger a repaint of the entire viewport
1381
int vOffset= getInclusiveTopIndexStartOffset();
1382            if (vOffset == -1)
1383                return null;
1384            
1385            // http://bugs.eclipse.org/bugs/show_bug.cgi?id=17147
1386
int vLength= getExclusiveBottomIndexEndOffset() - vOffset;
1387            
1388            return new Region(vOffset, vLength);
1389        }
1390        
1391        int widgetOffset;
1392        try {
1393            int widgetClippingStartOffset= fTextWidget.getOffsetAtLocation(new Point(0, event.y));
1394            int firstWidgetLine= fTextWidget.getLineAtOffset(widgetClippingStartOffset);
1395            widgetOffset= fTextWidget.getOffsetAtLine(firstWidgetLine);
1396        } catch (IllegalArgumentException JavaDoc ex1) {
1397            try {
1398                int firstVisibleLine= JFaceTextUtil.getPartialTopIndex(fTextWidget);
1399                widgetOffset= fTextWidget.getOffsetAtLine(firstVisibleLine);
1400            } catch (IllegalArgumentException JavaDoc ex2) { // above try code might fail too
1401
widgetOffset= 0;
1402            }
1403        }
1404        
1405        int widgetEndOffset;
1406        try {
1407            int widgetClippingEndOffset= fTextWidget.getOffsetAtLocation(new Point(0, event.y + event.height));
1408            int lastWidgetLine= fTextWidget.getLineAtOffset(widgetClippingEndOffset);
1409            widgetEndOffset= fTextWidget.getOffsetAtLine(lastWidgetLine + 1);
1410        } catch (IllegalArgumentException JavaDoc ex1) {
1411            // happens if the editor is not "full", e.g. the last line of the document is visible in the editor
1412
try {
1413                int lastVisibleLine= JFaceTextUtil.getPartialBottomIndex(fTextWidget);
1414                if (lastVisibleLine == fTextWidget.getLineCount() - 1)
1415                    // last line
1416
widgetEndOffset= fTextWidget.getCharCount();
1417                else
1418                    widgetEndOffset= fTextWidget.getOffsetAtLine(lastVisibleLine + 1) - 1;
1419            } catch (IllegalArgumentException JavaDoc ex2) { // above try code might fail too
1420
widgetEndOffset= fTextWidget.getCharCount();
1421            }
1422        }
1423        
1424        IRegion clippingRegion= getModelRange(widgetOffset, widgetEndOffset - widgetOffset);
1425        
1426        return clippingRegion;
1427    }
1428
1429    /**
1430     * Should the given annotation be skipped when handling draw requests?
1431     *
1432     * @param annotation the annotation
1433     * @return <code>true</code> iff the given annotation should be
1434     * skipped when handling draw requests
1435     * @since 3.0
1436     */

1437    protected boolean skip(Annotation annotation) {
1438        return false;
1439    }
1440
1441    /**
1442     * Returns the widget region that corresponds to the
1443     * given offset and length in the viewer's document.
1444     *
1445     * @param modelOffset the model offset
1446     * @param modelLength the model length
1447     * @return the corresponding widget region
1448     */

1449    private IRegion getWidgetRange(int modelOffset, int modelLength) {
1450        fReusableRegion.setOffset(modelOffset);
1451        fReusableRegion.setLength(modelLength);
1452
1453        if (fReusableRegion == null || fReusableRegion.getOffset() == Integer.MAX_VALUE)
1454            return null;
1455
1456        if (fSourceViewer instanceof ITextViewerExtension5) {
1457            ITextViewerExtension5 extension= (ITextViewerExtension5) fSourceViewer;
1458            return extension.modelRange2WidgetRange(fReusableRegion);
1459        }
1460
1461        IRegion region= fSourceViewer.getVisibleRegion();
1462        int offset= region.getOffset();
1463        int length= region.getLength();
1464
1465        if (overlapsWith(fReusableRegion, region)) {
1466            int p1= Math.max(offset, fReusableRegion.getOffset());
1467            int p2= Math.min(offset + length, fReusableRegion.getOffset() + fReusableRegion.getLength());
1468            return new Region(p1 - offset, p2 - p1);
1469        }
1470        return null;
1471    }
1472
1473    /**
1474     * Returns the model region that corresponds to the given region in the
1475     * viewer's text widget.
1476     *
1477     * @param offset the offset in the viewer's widget
1478     * @param length the length in the viewer's widget
1479     * @return the corresponding document region
1480     * @since 3.2
1481     */

1482    private IRegion getModelRange(int offset, int length) {
1483        if (offset == Integer.MAX_VALUE)
1484            return null;
1485
1486        if (fSourceViewer instanceof ITextViewerExtension5) {
1487            ITextViewerExtension5 extension= (ITextViewerExtension5) fSourceViewer;
1488            return extension.widgetRange2ModelRange(new Region(offset, length));
1489        }
1490        
1491        IRegion region= fSourceViewer.getVisibleRegion();
1492        return new Region(region.getOffset() + offset, length);
1493    }
1494    
1495    /**
1496     * Checks whether the intersection of the given text ranges
1497     * is empty or not.
1498     *
1499     * @param range1 the first range to check
1500     * @param range2 the second range to check
1501     * @return <code>true</code> if intersection is not empty
1502     */

1503    private boolean overlapsWith(IRegion range1, IRegion range2) {
1504        return overlapsWith(range1.getOffset(), range1.getLength(), range2.getOffset(), range2.getLength());
1505    }
1506
1507    /**
1508     * Checks whether the intersection of the given text ranges
1509     * is empty or not.
1510     *
1511     * @param offset1 offset of the first range
1512     * @param length1 length of the first range
1513     * @param offset2 offset of the second range
1514     * @param length2 length of the second range
1515     * @return <code>true</code> if intersection is not empty
1516     */

1517    private boolean overlapsWith(int offset1, int length1, int offset2, int length2) {
1518        int end= offset2 + length2;
1519        int thisEnd= offset1 + length1;
1520
1521        if (length2 > 0) {
1522            if (length1 > 0)
1523                return offset1 < end && offset2 < thisEnd;
1524            return offset2 <= offset1 && offset1 < end;
1525        }
1526
1527        if (length1 > 0)
1528            return offset1 <= offset2 && offset2 < thisEnd;
1529        return offset1 == offset2;
1530    }
1531
1532    /*
1533     * @see org.eclipse.jface.text.IPainter#deactivate(boolean)
1534     */

1535    public void deactivate(boolean redraw) {
1536        if (fIsActive) {
1537            fIsActive= false;
1538            disablePainting(redraw);
1539            setModel(null);
1540            catchupWithModel(null);
1541        }
1542    }
1543
1544    /**
1545     * Returns whether the given reason causes a repaint.
1546     *
1547     * @param reason the reason
1548     * @return <code>true</code> if repaint reason, <code>false</code> otherwise
1549     * @since 3.0
1550     */

1551    protected boolean isRepaintReason(int reason) {
1552        return CONFIGURATION == reason || INTERNAL == reason;
1553    }
1554
1555    /**
1556     * Retrieves the annotation model from the given source viewer.
1557     *
1558     * @param sourceViewer the source viewer
1559     * @return the source viewer's annotation model or <code>null</code> if none can be found
1560     * @since 3.0
1561     */

1562    protected IAnnotationModel findAnnotationModel(ISourceViewer sourceViewer) {
1563        if(sourceViewer != null)
1564            return sourceViewer.getAnnotationModel();
1565        return null;
1566    }
1567
1568    /*
1569     * @see org.eclipse.jface.text.IPainter#paint(int)
1570     */

1571    public void paint(int reason) {
1572        if (fSourceViewer.getDocument() == null) {
1573            deactivate(false);
1574            return;
1575        }
1576
1577        if (!fIsActive) {
1578            IAnnotationModel model= findAnnotationModel(fSourceViewer);
1579            if (model != null) {
1580                fIsActive= true;
1581                setModel(model);
1582            }
1583        } else if (isRepaintReason(reason))
1584            updatePainting(null);
1585    }
1586
1587    /*
1588     * @see org.eclipse.jface.text.IPainter#setPositionManager(org.eclipse.jface.text.IPaintPositionManager)
1589     */

1590    public void setPositionManager(IPaintPositionManager manager) {
1591    }
1592}
1593
Popular Tags