Mercurial > projects > dwt-addons
comparison dwtx/jface/text/source/AnnotationPainter.d @ 129:eb30df5ca28b
Added JFace Text sources
author | Frank Benoit <benoit@tionex.de> |
---|---|
date | Sat, 23 Aug 2008 19:10:48 +0200 |
parents | |
children | c4fb132a086c |
comparison
equal
deleted
inserted
replaced
128:8df1d4193877 | 129:eb30df5ca28b |
---|---|
1 /******************************************************************************* | |
2 * Copyright (c) 2000, 2008 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 * Port to the D programming language: | |
11 * Frank Benoit <benoit@tionex.de> | |
12 *******************************************************************************/ | |
13 module dwtx.jface.text.source.AnnotationPainter; | |
14 | |
15 import dwt.dwthelper.utils; | |
16 | |
17 | |
18 import java.util.ArrayList; | |
19 import java.util.Arrays; | |
20 import java.util.Collection; | |
21 import java.util.HashMap; | |
22 import java.util.HashSet; | |
23 import java.util.Iterator; | |
24 import java.util.LinkedList; | |
25 import java.util.List; | |
26 import java.util.Map; | |
27 import java.util.Set; | |
28 | |
29 import dwt.DWT; | |
30 import dwt.DWTException; | |
31 import dwt.custom.StyleRange; | |
32 import dwt.custom.StyledText; | |
33 import dwt.events.PaintEvent; | |
34 import dwt.events.PaintListener; | |
35 import dwt.graphics.Color; | |
36 import dwt.graphics.GC; | |
37 import dwt.graphics.Point; | |
38 import dwt.graphics.Rectangle; | |
39 import dwt.graphics.TextStyle; | |
40 import dwt.widgets.Display; | |
41 import dwtx.core.runtime.Assert; | |
42 import dwtx.core.runtime.Platform; | |
43 import dwtx.jface.text.BadLocationException; | |
44 import dwtx.jface.text.IDocument; | |
45 import dwtx.jface.text.IPaintPositionManager; | |
46 import dwtx.jface.text.IPainter; | |
47 import dwtx.jface.text.IRegion; | |
48 import dwtx.jface.text.ITextInputListener; | |
49 import dwtx.jface.text.ITextPresentationListener; | |
50 import dwtx.jface.text.ITextViewerExtension2; | |
51 import dwtx.jface.text.ITextViewerExtension5; | |
52 import dwtx.jface.text.JFaceTextUtil; | |
53 import dwtx.jface.text.Position; | |
54 import dwtx.jface.text.Region; | |
55 import dwtx.jface.text.TextPresentation; | |
56 | |
57 | |
58 /** | |
59 * Paints decorations for annotations provided by an annotation model and/or | |
60 * highlights them in the associated source viewer. | |
61 * <p> | |
62 * The annotation painter can be configured with drawing strategies. A drawing | |
63 * strategy defines the visual presentation of a particular type of annotation | |
64 * decoration.</p> | |
65 * <p> | |
66 * Clients usually instantiate and configure objects of this class.</p> | |
67 * | |
68 * @since 2.1 | |
69 */ | |
70 public class AnnotationPainter : IPainter, PaintListener, IAnnotationModelListener, IAnnotationModelListenerExtension, ITextPresentationListener { | |
71 | |
72 | |
73 /** | |
74 * A drawing strategy draws the decoration for an annotation onto the text widget. | |
75 * | |
76 * @since 3.0 | |
77 */ | |
78 public interface IDrawingStrategy { | |
79 /** | |
80 * Draws a decoration for an annotation onto the specified GC at the given text range. There | |
81 * are two different invocation modes of the <code>draw</code> method: | |
82 * <ul> | |
83 * <li><strong>drawing mode:</strong> the passed GC is the graphics context of a paint | |
84 * event occurring on the text widget. The strategy should draw the decoration onto the | |
85 * graphics context, such that the decoration appears at the given range in the text | |
86 * widget.</li> | |
87 * <li><strong>clearing mode:</strong> the passed GC is <code>null</code>. In this case | |
88 * the strategy must invalidate enough of the text widget's client area to cover any | |
89 * decoration drawn in drawing mode. This can usually be accomplished by calling | |
90 * {@linkplain StyledText#redrawRange(int, int, bool) textWidget.redrawRange(offset, length, true)}.</li> | |
91 * </ul> | |
92 * | |
93 * @param annotation the annotation to be drawn | |
94 * @param gc the graphics context, <code>null</code> when in clearing mode | |
95 * @param textWidget the text widget to draw on | |
96 * @param offset the offset of the line | |
97 * @param length the length of the line | |
98 * @param color the color of the line | |
99 */ | |
100 void draw(Annotation annotation, GC gc, StyledText textWidget, int offset, int length, Color color); | |
101 } | |
102 | |
103 /** | |
104 * Squiggles drawing strategy. | |
105 * | |
106 * @since 3.0 | |
107 * @deprecated As of 3.4, replaced by {@link AnnotationPainter.UnderlineStrategy} | |
108 */ | |
109 public static class SquigglesStrategy : IDrawingStrategy { | |
110 | |
111 /* | |
112 * @see dwtx.jface.text.source.AnnotationPainter.IDrawingStrategy#draw(dwtx.jface.text.source.Annotation, dwt.graphics.GC, dwt.custom.StyledText, int, int, dwt.graphics.Color) | |
113 * @since 3.0 | |
114 */ | |
115 public void draw(Annotation annotation, GC gc, StyledText textWidget, int offset, int length, Color color) { | |
116 if (gc !is null) { | |
117 | |
118 if (length < 1) | |
119 return; | |
120 | |
121 Point left= textWidget.getLocationAtOffset(offset); | |
122 Point right= textWidget.getLocationAtOffset(offset + length); | |
123 Rectangle rect= textWidget.getTextBounds(offset, offset + length - 1); | |
124 left.x= rect.x; | |
125 right.x= rect.x + rect.width; | |
126 | |
127 int[] polyline= computePolyline(left, right, textWidget.getBaseline(offset), textWidget.getLineHeight(offset)); | |
128 | |
129 gc.setLineWidth(0); // NOTE: 0 means width is 1 but with optimized performance | |
130 gc.setLineStyle(DWT.LINE_SOLID); | |
131 gc.setForeground(color); | |
132 gc.drawPolyline(polyline); | |
133 | |
134 } else { | |
135 textWidget.redrawRange(offset, length, true); | |
136 } | |
137 } | |
138 | |
139 /** | |
140 * Computes an array of alternating x and y values which are the corners of the squiggly line of the | |
141 * given height between the given end points. | |
142 * | |
143 * @param left the left end point | |
144 * @param right the right end point | |
145 * @param baseline the font's baseline | |
146 * @param lineHeight the height of the line | |
147 * @return the array of alternating x and y values which are the corners of the squiggly line | |
148 */ | |
149 private int[] computePolyline(Point left, Point right, int baseline, int lineHeight) { | |
150 | |
151 final int WIDTH= 4; // must be even | |
152 final int HEIGHT= 2; // can be any number | |
153 | |
154 int peaks= (right.x - left.x) / WIDTH; | |
155 if (peaks is 0 && right.x - left.x > 2) | |
156 peaks= 1; | |
157 | |
158 int leftX= left.x; | |
159 | |
160 // compute (number of point) * 2 | |
161 int length= ((2 * peaks) + 1) * 2; | |
162 if (length < 0) | |
163 return new int[0]; | |
164 | |
165 int[] coordinates= new int[length]; | |
166 | |
167 // cache peeks' y-coordinates | |
168 int top= left.y + Math.min(baseline + 1, lineHeight - HEIGHT - 1); | |
169 int bottom= top + HEIGHT; | |
170 | |
171 // populate array with peek coordinates | |
172 for (int i= 0; i < peaks; i++) { | |
173 int index= 4 * i; | |
174 coordinates[index]= leftX + (WIDTH * i); | |
175 coordinates[index+1]= bottom; | |
176 coordinates[index+2]= coordinates[index] + WIDTH/2; | |
177 coordinates[index+3]= top; | |
178 } | |
179 | |
180 // the last down flank is missing | |
181 coordinates[length-2]= Math.min(Math.max(0, right.x - 1), left.x + (WIDTH * peaks)); | |
182 coordinates[length-1]= bottom; | |
183 | |
184 return coordinates; | |
185 } | |
186 } | |
187 | |
188 /** | |
189 * Drawing strategy that does nothing. | |
190 * | |
191 * @since 3.0 | |
192 */ | |
193 public static final class NullStrategy : IDrawingStrategy { | |
194 | |
195 /* | |
196 * @see dwtx.jface.text.source.AnnotationPainter.IDrawingStrategy#draw(dwtx.jface.text.source.Annotation, dwt.graphics.GC, dwt.custom.StyledText, int, int, dwt.graphics.Color) | |
197 * @since 3.0 | |
198 */ | |
199 public void draw(Annotation annotation, GC gc, StyledText textWidget, int offset, int length, Color color) { | |
200 // do nothing | |
201 } | |
202 } | |
203 | |
204 | |
205 /** | |
206 * A text style painting strategy draws the decoration for an annotation | |
207 * onto the text widget by applying a {@link TextStyle} on a given | |
208 * {@link StyleRange}. | |
209 * | |
210 * @since 3.4 | |
211 */ | |
212 public interface ITextStyleStrategy { | |
213 | |
214 /** | |
215 * Applies a text style on the given <code>StyleRange</code>. | |
216 * | |
217 * @param styleRange the style range on which to apply the text style | |
218 * @param annotationColor the color of the annotation | |
219 */ | |
220 void applyTextStyle(StyleRange styleRange, Color annotationColor); | |
221 } | |
222 | |
223 | |
224 /** | |
225 * @since 3.4 | |
226 */ | |
227 public static final class HighlightingStrategy : ITextStyleStrategy { | |
228 public void applyTextStyle(StyleRange styleRange, Color annotationColor) { | |
229 styleRange.background= annotationColor; | |
230 } | |
231 } | |
232 | |
233 | |
234 /** | |
235 * Underline text style strategy. | |
236 * | |
237 * @since 3.4 | |
238 */ | |
239 public static final class UnderlineStrategy : ITextStyleStrategy { | |
240 | |
241 int fUnderlineStyle; | |
242 | |
243 public UnderlineStrategy(int style) { | |
244 Assert.isLegal(style is DWT.UNDERLINE_SINGLE || style is DWT.UNDERLINE_DOUBLE || style is DWT.UNDERLINE_ERROR || style is DWT.UNDERLINE_SQUIGGLE); | |
245 fUnderlineStyle= style; | |
246 } | |
247 | |
248 public void applyTextStyle(StyleRange styleRange, Color annotationColor) { | |
249 styleRange.underline= true; | |
250 styleRange.underlineStyle= fUnderlineStyle; | |
251 styleRange.underlineColor= annotationColor; | |
252 } | |
253 } | |
254 | |
255 | |
256 /** | |
257 * Box text style strategy. | |
258 * | |
259 * @since 3.4 | |
260 */ | |
261 public static final class BoxStrategy : ITextStyleStrategy { | |
262 | |
263 int fBorderStyle; | |
264 | |
265 public BoxStrategy(int style) { | |
266 Assert.isLegal(style is DWT.BORDER_DASH || style is DWT.BORDER_DASH || style is DWT.BORDER_SOLID); | |
267 fBorderStyle= style; | |
268 } | |
269 | |
270 public void applyTextStyle(StyleRange styleRange, Color annotationColor) { | |
271 styleRange.borderStyle= fBorderStyle; | |
272 styleRange.borderColor= annotationColor; | |
273 } | |
274 } | |
275 | |
276 | |
277 /** | |
278 * Implementation of <code>IRegion</code> that can be reused | |
279 * by setting the offset and the length. | |
280 */ | |
281 private static class ReusableRegion : Position , IRegion {} | |
282 | |
283 /** | |
284 * Tells whether this class is in debug mode. | |
285 * @since 3.0 | |
286 */ | |
287 private static bool DEBUG= "true".equalsIgnoreCase(Platform.getDebugOption("dwtx.jface.text/debug/AnnotationPainter")); //$NON-NLS-1$//$NON-NLS-2$ | |
288 /** | |
289 * The squiggly painter strategy. | |
290 * @since 3.0 | |
291 */ | |
292 private static final IDrawingStrategy SQUIGGLES_STRATEGY= new SquigglesStrategy(); | |
293 | |
294 /** | |
295 * This strategy is used to mark the <code>null</code> value in the chache | |
296 * maps. | |
297 * | |
298 * @since 3.4 | |
299 */ | |
300 private static final IDrawingStrategy NULL_STRATEGY= new NullStrategy(); | |
301 /** | |
302 * The squiggles painter id. | |
303 * @since 3.0 | |
304 */ | |
305 private static final Object SQUIGGLES= new Object(); | |
306 /** | |
307 * The squiggly painter strategy. | |
308 * | |
309 * @since 3.4 | |
310 */ | |
311 private static final ITextStyleStrategy HIGHLIGHTING_STRATEGY= new HighlightingStrategy(); | |
312 | |
313 /** | |
314 * The highlighting text style strategy id. | |
315 * | |
316 * @since 3.4 | |
317 */ | |
318 private static final Object HIGHLIGHTING= new Object(); | |
319 | |
320 /** | |
321 * The presentation information (decoration) for an annotation. Each such | |
322 * object represents one decoration drawn on the text area, such as squiggly lines | |
323 * and underlines. | |
324 */ | |
325 private static class Decoration { | |
326 /** The position of this decoration */ | |
327 private Position fPosition; | |
328 /** The color of this decoration */ | |
329 private Color fColor; | |
330 /** | |
331 * The annotation's layer | |
332 * @since 3.0 | |
333 */ | |
334 private int fLayer; | |
335 /** | |
336 * The painting strategy for this decoration. | |
337 * @since 3.0 | |
338 */ | |
339 private Object fPaintingStrategy; | |
340 } | |
341 | |
342 | |
343 /** Indicates whether this painter is active */ | |
344 private bool fIsActive= false; | |
345 /** Indicates whether this painter is managing decorations */ | |
346 private bool fIsPainting= false; | |
347 /** Indicates whether this painter is setting its annotation model */ | |
348 private volatile bool fIsSettingModel= false; | |
349 /** The associated source viewer */ | |
350 private ISourceViewer fSourceViewer; | |
351 /** The cached widget of the source viewer */ | |
352 private StyledText fTextWidget; | |
353 /** The annotation model providing the annotations to be drawn */ | |
354 private IAnnotationModel fModel; | |
355 /** The annotation access */ | |
356 private IAnnotationAccess fAnnotationAccess; | |
357 /** | |
358 * The map with decorations | |
359 * @since 3.0 | |
360 */ | |
361 private Map fDecorationsMap= new HashMap(); // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=50767 | |
362 /** | |
363 * The map with of highlighted decorations. | |
364 * @since 3.0 | |
365 */ | |
366 private Map fHighlightedDecorationsMap= new HashMap(); // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=50767 | |
367 /** | |
368 * Mutex for highlighted decorations map. | |
369 * @since 3.0 | |
370 */ | |
371 private Object fDecorationMapLock= new Object(); | |
372 /** | |
373 * Mutex for for decorations map. | |
374 * @since 3.0 | |
375 */ | |
376 private Object fHighlightedDecorationsMapLock= new Object(); | |
377 /** | |
378 * Maps an annotation type to its registered color. | |
379 * | |
380 * @see #setAnnotationTypeColor(Object, Color) | |
381 */ | |
382 private Map fAnnotationType2Color= new HashMap(); | |
383 | |
384 /** | |
385 * Cache that maps the annotation type to its color. | |
386 * @since 3.4 | |
387 */ | |
388 private Map fCachedAnnotationType2Color= new HashMap(); | |
389 /** | |
390 * The range in which the current highlight annotations can be found. | |
391 * @since 3.0 | |
392 */ | |
393 private Position fCurrentHighlightAnnotationRange= null; | |
394 /** | |
395 * The range in which all added, removed and changed highlight | |
396 * annotations can be found since the last world change. | |
397 * @since 3.0 | |
398 */ | |
399 private Position fTotalHighlightAnnotationRange= null; | |
400 /** | |
401 * The range in which the currently drawn annotations can be found. | |
402 * @since 3.3 | |
403 */ | |
404 private Position fCurrentDrawRange= null; | |
405 /** | |
406 * The range in which all added, removed and changed drawn | |
407 * annotations can be found since the last world change. | |
408 * @since 3.3 | |
409 */ | |
410 private Position fTotalDrawRange= null; | |
411 /** | |
412 * The text input listener. | |
413 * @since 3.0 | |
414 */ | |
415 private ITextInputListener fTextInputListener; | |
416 /** | |
417 * Flag which tells that a new document input is currently being set. | |
418 * @since 3.0 | |
419 */ | |
420 private bool fInputDocumentAboutToBeChanged; | |
421 /** | |
422 * Maps annotation types to painting strategy identifiers. | |
423 * | |
424 * @see #addAnnotationType(Object, Object) | |
425 * @since 3.0 | |
426 */ | |
427 private Map fAnnotationType2PaintingStrategyId= new HashMap(); | |
428 /** | |
429 * Maps annotation types to painting strategy identifiers. | |
430 * @since 3.4 | |
431 */ | |
432 private Map fCachedAnnotationType2PaintingStrategy= new HashMap(); | |
433 | |
434 /** | |
435 * Maps painting strategy identifiers to painting strategies. | |
436 * | |
437 * @since 3.0 | |
438 */ | |
439 private Map fPaintingStrategyId2PaintingStrategy= new HashMap(); | |
440 | |
441 /** | |
442 * Reuse this region for performance reasons. | |
443 * @since 3.3 | |
444 */ | |
445 private ReusableRegion fReusableRegion= new ReusableRegion(); | |
446 | |
447 /** | |
448 * Creates a new annotation painter for the given source viewer and with the | |
449 * given annotation access. The painter is not initialized, i.e. no | |
450 * annotation types are configured to be painted. | |
451 * | |
452 * @param sourceViewer the source viewer for this painter | |
453 * @param access the annotation access for this painter | |
454 */ | |
455 public AnnotationPainter(ISourceViewer sourceViewer, IAnnotationAccess access) { | |
456 fSourceViewer= sourceViewer; | |
457 fAnnotationAccess= access; | |
458 fTextWidget= sourceViewer.getTextWidget(); | |
459 | |
460 // default drawing strategies: squiggles were the only decoration style before version 3.0 | |
461 fPaintingStrategyId2PaintingStrategy.put(SQUIGGLES, SQUIGGLES_STRATEGY); | |
462 fPaintingStrategyId2PaintingStrategy.put(HIGHLIGHTING, HIGHLIGHTING_STRATEGY); | |
463 } | |
464 | |
465 /** | |
466 * Returns whether this painter has to draw any squiggles. | |
467 * | |
468 * @return <code>true</code> if there are squiggles to be drawn, <code>false</code> otherwise | |
469 */ | |
470 private bool hasDecorations() { | |
471 synchronized (fDecorationMapLock) { | |
472 return !fDecorationsMap.isEmpty(); | |
473 } | |
474 } | |
475 | |
476 /** | |
477 * Enables painting. This painter registers a paint listener with the | |
478 * source viewer's widget. | |
479 */ | |
480 private void enablePainting() { | |
481 if (!fIsPainting && hasDecorations()) { | |
482 fIsPainting= true; | |
483 fTextWidget.addPaintListener(this); | |
484 handleDrawRequest(null); | |
485 } | |
486 } | |
487 | |
488 /** | |
489 * Disables painting, if is has previously been enabled. Removes | |
490 * any paint listeners registered with the source viewer's widget. | |
491 * | |
492 * @param redraw <code>true</code> if the widget should be redrawn after disabling | |
493 */ | |
494 private void disablePainting(bool redraw) { | |
495 if (fIsPainting) { | |
496 fIsPainting= false; | |
497 fTextWidget.removePaintListener(this); | |
498 if (redraw && hasDecorations()) | |
499 handleDrawRequest(null); | |
500 } | |
501 } | |
502 | |
503 /** | |
504 * Sets the annotation model for this painter. Registers this painter | |
505 * as listener of the give model, if the model is not <code>null</code>. | |
506 * | |
507 * @param model the annotation model | |
508 */ | |
509 private void setModel(IAnnotationModel model) { | |
510 if (fModel !is model) { | |
511 if (fModel !is null) | |
512 fModel.removeAnnotationModelListener(this); | |
513 fModel= model; | |
514 if (fModel !is null) { | |
515 try { | |
516 fIsSettingModel= true; | |
517 fModel.addAnnotationModelListener(this); | |
518 } finally { | |
519 fIsSettingModel= false; | |
520 } | |
521 } | |
522 } | |
523 } | |
524 | |
525 /** | |
526 * Updates the set of decorations based on the current state of | |
527 * the painter's annotation model. | |
528 * | |
529 * @param event the annotation model event | |
530 */ | |
531 private void catchupWithModel(AnnotationModelEvent event) { | |
532 | |
533 synchronized (fDecorationMapLock) { | |
534 if (fDecorationsMap is null) | |
535 return; | |
536 } | |
537 | |
538 IRegion clippingRegion= computeClippingRegion(null, true); | |
539 IDocument document= fSourceViewer.getDocument(); | |
540 | |
541 int highlightAnnotationRangeStart= Integer.MAX_VALUE; | |
542 int highlightAnnotationRangeEnd= -1; | |
543 | |
544 int drawRangeStart= Integer.MAX_VALUE; | |
545 int drawRangeEnd= -1; | |
546 | |
547 if (fModel !is null) { | |
548 | |
549 Map decorationsMap; | |
550 Map highlightedDecorationsMap; | |
551 | |
552 // Clone decoration maps | |
553 synchronized (fDecorationMapLock) { | |
554 decorationsMap= new HashMap(fDecorationsMap); | |
555 } | |
556 synchronized (fHighlightedDecorationsMapLock) { | |
557 highlightedDecorationsMap= new HashMap(fHighlightedDecorationsMap); | |
558 } | |
559 | |
560 bool isWorldChange= false; | |
561 | |
562 Iterator e; | |
563 if (event is null || event.isWorldChange()) { | |
564 isWorldChange= true; | |
565 | |
566 if (DEBUG && event is null) | |
567 System.out.println("AP: INTERNAL CHANGE"); //$NON-NLS-1$ | |
568 | |
569 Iterator iter= decorationsMap.entrySet().iterator(); | |
570 while (iter.hasNext()) { | |
571 Map.Entry entry= (Map.Entry)iter.next(); | |
572 Annotation annotation= (Annotation)entry.getKey(); | |
573 Decoration decoration= (Decoration)entry.getValue(); | |
574 drawDecoration(decoration, null, annotation, clippingRegion, document); | |
575 } | |
576 | |
577 decorationsMap.clear(); | |
578 | |
579 highlightedDecorationsMap.clear(); | |
580 | |
581 e= fModel.getAnnotationIterator(); | |
582 | |
583 | |
584 } else { | |
585 | |
586 // Remove annotations | |
587 Annotation[] removedAnnotations= event.getRemovedAnnotations(); | |
588 for (int i=0, length= removedAnnotations.length; i < length; i++) { | |
589 Annotation annotation= removedAnnotations[i]; | |
590 Decoration decoration= (Decoration)highlightedDecorationsMap.remove(annotation); | |
591 if (decoration !is null) { | |
592 Position position= decoration.fPosition; | |
593 if (position !is null) { | |
594 highlightAnnotationRangeStart= Math.min(highlightAnnotationRangeStart, position.offset); | |
595 highlightAnnotationRangeEnd= Math.max(highlightAnnotationRangeEnd, position.offset + position.length); | |
596 } | |
597 } | |
598 decoration= (Decoration)decorationsMap.remove(annotation); | |
599 if (decoration !is null) { | |
600 drawDecoration(decoration, null, annotation, clippingRegion, document); | |
601 Position position= decoration.fPosition; | |
602 if (position !is null) { | |
603 drawRangeStart= Math.min(drawRangeStart, position.offset); | |
604 drawRangeEnd= Math.max(drawRangeEnd, position.offset + position.length); | |
605 } | |
606 } | |
607 | |
608 } | |
609 | |
610 // Update existing annotations | |
611 Annotation[] changedAnnotations= event.getChangedAnnotations(); | |
612 for (int i=0, length= changedAnnotations.length; i < length; i++) { | |
613 Annotation annotation= changedAnnotations[i]; | |
614 | |
615 bool isHighlighting= false; | |
616 | |
617 Decoration decoration= (Decoration)highlightedDecorationsMap.get(annotation); | |
618 | |
619 if (decoration !is null) { | |
620 isHighlighting= true; | |
621 // The call below updates the decoration - no need to create new decoration | |
622 decoration= getDecoration(annotation, decoration); | |
623 if (decoration is null) | |
624 highlightedDecorationsMap.remove(annotation); | |
625 } else { | |
626 decoration= getDecoration(annotation, decoration); | |
627 if (decoration !is null && decoration.fPaintingStrategy instanceof ITextStyleStrategy) { | |
628 highlightedDecorationsMap.put(annotation, decoration); | |
629 isHighlighting= true; | |
630 } | |
631 } | |
632 | |
633 bool usesDrawingStrategy= !isHighlighting && decoration !is null; | |
634 | |
635 Position position= null; | |
636 if (decoration is null) | |
637 position= fModel.getPosition(annotation); | |
638 else | |
639 position= decoration.fPosition; | |
640 | |
641 if (position !is null && !position.isDeleted()) { | |
642 if (isHighlighting) { | |
643 highlightAnnotationRangeStart= Math.min(highlightAnnotationRangeStart, position.offset); | |
644 highlightAnnotationRangeEnd= Math.max(highlightAnnotationRangeEnd, position.offset + position.length); | |
645 } | |
646 if (usesDrawingStrategy) { | |
647 drawRangeStart= Math.min(drawRangeStart, position.offset); | |
648 drawRangeEnd= Math.max(drawRangeEnd, position.offset + position.length); | |
649 } | |
650 } else { | |
651 highlightedDecorationsMap.remove(annotation); | |
652 } | |
653 | |
654 if (usesDrawingStrategy) { | |
655 Decoration oldDecoration= (Decoration)decorationsMap.get(annotation); | |
656 if (oldDecoration !is null) { | |
657 drawDecoration(oldDecoration, null, annotation, clippingRegion, document); | |
658 | |
659 if (decoration !is null) | |
660 decorationsMap.put(annotation, decoration); | |
661 else if (oldDecoration !is null) | |
662 decorationsMap.remove(annotation); | |
663 } | |
664 } | |
665 } | |
666 | |
667 e= Arrays.asList(event.getAddedAnnotations()).iterator(); | |
668 } | |
669 | |
670 // Add new annotations | |
671 while (e.hasNext()) { | |
672 Annotation annotation= (Annotation) e.next(); | |
673 Decoration pp= getDecoration(annotation, null); | |
674 if (pp !is null) { | |
675 if (pp.fPaintingStrategy instanceof IDrawingStrategy) { | |
676 decorationsMap.put(annotation, pp); | |
677 drawRangeStart= Math.min(drawRangeStart, pp.fPosition.offset); | |
678 drawRangeEnd= Math.max(drawRangeEnd, pp.fPosition.offset + pp.fPosition.length); | |
679 } else if (pp.fPaintingStrategy instanceof ITextStyleStrategy) { | |
680 highlightedDecorationsMap.put(annotation, pp); | |
681 highlightAnnotationRangeStart= Math.min(highlightAnnotationRangeStart, pp.fPosition.offset); | |
682 highlightAnnotationRangeEnd= Math.max(highlightAnnotationRangeEnd, pp.fPosition.offset + pp.fPosition.length); | |
683 } | |
684 | |
685 } | |
686 } | |
687 | |
688 synchronized (fDecorationMapLock) { | |
689 fDecorationsMap= decorationsMap; | |
690 updateDrawRanges(drawRangeStart, drawRangeEnd, isWorldChange); | |
691 } | |
692 | |
693 synchronized (fHighlightedDecorationsMapLock) { | |
694 fHighlightedDecorationsMap= highlightedDecorationsMap; | |
695 updateHighlightRanges(highlightAnnotationRangeStart, highlightAnnotationRangeEnd, isWorldChange); | |
696 } | |
697 } else { | |
698 // annotation model is null -> clear all | |
699 synchronized (fDecorationMapLock) { | |
700 fDecorationsMap.clear(); | |
701 } | |
702 synchronized (fHighlightedDecorationsMapLock) { | |
703 fHighlightedDecorationsMap.clear(); | |
704 } | |
705 } | |
706 } | |
707 | |
708 /** | |
709 * Updates the remembered highlight ranges. | |
710 * | |
711 * @param highlightAnnotationRangeStart the start of the range | |
712 * @param highlightAnnotationRangeEnd the end of the range | |
713 * @param isWorldChange tells whether the range belongs to a annotation model event reporting a world change | |
714 * @since 3.0 | |
715 */ | |
716 private void updateHighlightRanges(int highlightAnnotationRangeStart, int highlightAnnotationRangeEnd, bool isWorldChange) { | |
717 if (highlightAnnotationRangeStart !is Integer.MAX_VALUE) { | |
718 | |
719 int maxRangeStart= highlightAnnotationRangeStart; | |
720 int maxRangeEnd= highlightAnnotationRangeEnd; | |
721 | |
722 if (fTotalHighlightAnnotationRange !is null) { | |
723 maxRangeStart= Math.min(maxRangeStart, fTotalHighlightAnnotationRange.offset); | |
724 maxRangeEnd= Math.max(maxRangeEnd, fTotalHighlightAnnotationRange.offset + fTotalHighlightAnnotationRange.length); | |
725 } | |
726 | |
727 if (fTotalHighlightAnnotationRange is null) | |
728 fTotalHighlightAnnotationRange= new Position(0); | |
729 if (fCurrentHighlightAnnotationRange is null) | |
730 fCurrentHighlightAnnotationRange= new Position(0); | |
731 | |
732 if (isWorldChange) { | |
733 fTotalHighlightAnnotationRange.offset= highlightAnnotationRangeStart; | |
734 fTotalHighlightAnnotationRange.length= highlightAnnotationRangeEnd - highlightAnnotationRangeStart; | |
735 fCurrentHighlightAnnotationRange.offset= maxRangeStart; | |
736 fCurrentHighlightAnnotationRange.length= maxRangeEnd - maxRangeStart; | |
737 } else { | |
738 fTotalHighlightAnnotationRange.offset= maxRangeStart; | |
739 fTotalHighlightAnnotationRange.length= maxRangeEnd - maxRangeStart; | |
740 fCurrentHighlightAnnotationRange.offset=highlightAnnotationRangeStart; | |
741 fCurrentHighlightAnnotationRange.length= highlightAnnotationRangeEnd - highlightAnnotationRangeStart; | |
742 } | |
743 } else { | |
744 if (isWorldChange) { | |
745 fCurrentHighlightAnnotationRange= fTotalHighlightAnnotationRange; | |
746 fTotalHighlightAnnotationRange= null; | |
747 } else { | |
748 fCurrentHighlightAnnotationRange= null; | |
749 } | |
750 } | |
751 | |
752 adaptToDocumentLength(fCurrentHighlightAnnotationRange); | |
753 adaptToDocumentLength(fTotalHighlightAnnotationRange); | |
754 } | |
755 | |
756 /** | |
757 * Updates the remembered decoration ranges. | |
758 * | |
759 * @param drawRangeStart the start of the range | |
760 * @param drawRangeEnd the end of the range | |
761 * @param isWorldChange tells whether the range belongs to a annotation model event reporting a world change | |
762 * @since 3.3 | |
763 */ | |
764 private void updateDrawRanges(int drawRangeStart, int drawRangeEnd, bool isWorldChange) { | |
765 if (drawRangeStart !is Integer.MAX_VALUE) { | |
766 | |
767 int maxRangeStart= drawRangeStart; | |
768 int maxRangeEnd= drawRangeEnd; | |
769 | |
770 if (fTotalDrawRange !is null) { | |
771 maxRangeStart= Math.min(maxRangeStart, fTotalDrawRange.offset); | |
772 maxRangeEnd= Math.max(maxRangeEnd, fTotalDrawRange.offset + fTotalDrawRange.length); | |
773 } | |
774 | |
775 if (fTotalDrawRange is null) | |
776 fTotalDrawRange= new Position(0); | |
777 if (fCurrentDrawRange is null) | |
778 fCurrentDrawRange= new Position(0); | |
779 | |
780 if (isWorldChange) { | |
781 fTotalDrawRange.offset= drawRangeStart; | |
782 fTotalDrawRange.length= drawRangeEnd - drawRangeStart; | |
783 fCurrentDrawRange.offset= maxRangeStart; | |
784 fCurrentDrawRange.length= maxRangeEnd - maxRangeStart; | |
785 } else { | |
786 fTotalDrawRange.offset= maxRangeStart; | |
787 fTotalDrawRange.length= maxRangeEnd - maxRangeStart; | |
788 fCurrentDrawRange.offset=drawRangeStart; | |
789 fCurrentDrawRange.length= drawRangeEnd - drawRangeStart; | |
790 } | |
791 } else { | |
792 if (isWorldChange) { | |
793 fCurrentDrawRange= fTotalDrawRange; | |
794 fTotalDrawRange= null; | |
795 } else { | |
796 fCurrentDrawRange= null; | |
797 } | |
798 } | |
799 | |
800 adaptToDocumentLength(fCurrentDrawRange); | |
801 adaptToDocumentLength(fTotalDrawRange); | |
802 } | |
803 | |
804 /** | |
805 * Adapts the given position to the document length. | |
806 * | |
807 * @param position the position to adapt | |
808 * @since 3.0 | |
809 */ | |
810 private void adaptToDocumentLength(Position position) { | |
811 if (position is null) | |
812 return; | |
813 | |
814 int length= fSourceViewer.getDocument().getLength(); | |
815 position.offset= Math.min(position.offset, length); | |
816 position.length= Math.min(position.length, length - position.offset); | |
817 } | |
818 | |
819 /** | |
820 * Returns a decoration for the given annotation if this | |
821 * annotation is valid and shown by this painter. | |
822 * | |
823 * @param annotation the annotation | |
824 * @param decoration the decoration to be adapted and returned or <code>null</code> if a new one must be created | |
825 * @return the decoration or <code>null</code> if there's no valid one | |
826 * @since 3.0 | |
827 */ | |
828 private Decoration getDecoration(Annotation annotation, Decoration decoration) { | |
829 | |
830 if (annotation.isMarkedDeleted()) | |
831 return null; | |
832 | |
833 String type= annotation.getType(); | |
834 | |
835 Object paintingStrategy= getPaintingStrategy(type); | |
836 if (paintingStrategy is null || paintingStrategy instanceof NullStrategy) | |
837 return null; | |
838 | |
839 Color color= getColor(type); | |
840 if (color is null) | |
841 return null; | |
842 | |
843 Position position= fModel.getPosition(annotation); | |
844 if (position is null || position.isDeleted()) | |
845 return null; | |
846 | |
847 if (decoration is null) | |
848 decoration= new Decoration(); | |
849 | |
850 decoration.fPosition= position; | |
851 decoration.fColor= color; | |
852 if (fAnnotationAccess instanceof IAnnotationAccessExtension) { | |
853 IAnnotationAccessExtension extension= (IAnnotationAccessExtension) fAnnotationAccess; | |
854 decoration.fLayer= extension.getLayer(annotation); | |
855 } else { | |
856 decoration.fLayer= IAnnotationAccessExtension.DEFAULT_LAYER; | |
857 } | |
858 | |
859 decoration.fPaintingStrategy= paintingStrategy; | |
860 | |
861 return decoration; | |
862 } | |
863 | |
864 /** | |
865 * Returns the painting strategy for the given annotation. | |
866 * | |
867 * @param type the annotation type | |
868 * @return the annotation painter | |
869 * @since 3.0 | |
870 */ | |
871 private Object getPaintingStrategy(final String type) { | |
872 Object strategy= fCachedAnnotationType2PaintingStrategy.get(type); | |
873 if (strategy !is null) | |
874 return strategy; | |
875 | |
876 strategy= fPaintingStrategyId2PaintingStrategy.get(fAnnotationType2PaintingStrategyId.get(type)); | |
877 if (strategy !is null) { | |
878 fCachedAnnotationType2PaintingStrategy.put(type, strategy); | |
879 return strategy; | |
880 } | |
881 | |
882 if (fAnnotationAccess instanceof IAnnotationAccessExtension) { | |
883 IAnnotationAccessExtension ext = (IAnnotationAccessExtension) fAnnotationAccess; | |
884 Object[] sts = ext.getSupertypes(type); | |
885 for (int i= 0; i < sts.length; i++) { | |
886 strategy= fPaintingStrategyId2PaintingStrategy.get(fAnnotationType2PaintingStrategyId.get(sts[i])); | |
887 if (strategy !is null) { | |
888 fCachedAnnotationType2PaintingStrategy.put(type, strategy); | |
889 return strategy; | |
890 } | |
891 } | |
892 } | |
893 | |
894 fCachedAnnotationType2PaintingStrategy.put(type, NULL_STRATEGY); | |
895 return null; | |
896 | |
897 } | |
898 | |
899 /** | |
900 * Returns the color for the given annotation type | |
901 * | |
902 * @param annotationType the annotation type | |
903 * @return the color | |
904 * @since 3.0 | |
905 */ | |
906 private Color getColor(final Object annotationType) { | |
907 Color color= (Color)fCachedAnnotationType2Color.get(annotationType); | |
908 if (color !is null) | |
909 return color; | |
910 | |
911 color= (Color)fAnnotationType2Color.get(annotationType); | |
912 if (color !is null) { | |
913 fCachedAnnotationType2Color.put(annotationType, color); | |
914 return color; | |
915 } | |
916 | |
917 if (fAnnotationAccess instanceof IAnnotationAccessExtension) { | |
918 IAnnotationAccessExtension extension= (IAnnotationAccessExtension) fAnnotationAccess; | |
919 Object[] superTypes= extension.getSupertypes(annotationType); | |
920 if (superTypes !is null) { | |
921 for (int i= 0; i < superTypes.length; i++) { | |
922 color= (Color)fAnnotationType2Color.get(superTypes[i]); | |
923 if (color !is null) { | |
924 fCachedAnnotationType2Color.put(annotationType, color); | |
925 return color; | |
926 } | |
927 } | |
928 } | |
929 } | |
930 | |
931 return null; | |
932 } | |
933 | |
934 /** | |
935 * Recomputes the squiggles to be drawn and redraws them. | |
936 * | |
937 * @param event the annotation model event | |
938 * @since 3.0 | |
939 */ | |
940 private void updatePainting(AnnotationModelEvent event) { | |
941 disablePainting(event is null); | |
942 | |
943 catchupWithModel(event); | |
944 | |
945 if (!fInputDocumentAboutToBeChanged) | |
946 invalidateTextPresentation(); | |
947 | |
948 enablePainting(); | |
949 } | |
950 | |
951 private void invalidateTextPresentation() { | |
952 IRegion r= null; | |
953 synchronized (fHighlightedDecorationsMapLock) { | |
954 if (fCurrentHighlightAnnotationRange !is null) | |
955 r= new Region(fCurrentHighlightAnnotationRange.getOffset(), fCurrentHighlightAnnotationRange.getLength()); | |
956 } | |
957 if (r is null) | |
958 return; | |
959 | |
960 if (fSourceViewer instanceof ITextViewerExtension2) { | |
961 if (DEBUG) | |
962 System.out.println("AP: invalidating offset: " + r.getOffset() + ", length= " + r.getLength()); //$NON-NLS-1$ //$NON-NLS-2$ | |
963 | |
964 ((ITextViewerExtension2)fSourceViewer).invalidateTextPresentation(r.getOffset(), r.getLength()); | |
965 | |
966 } else { | |
967 fSourceViewer.invalidateTextPresentation(); | |
968 } | |
969 } | |
970 | |
971 /* | |
972 * @see dwtx.jface.text.ITextPresentationListener#applyTextPresentation(dwtx.jface.text.TextPresentation) | |
973 * @since 3.0 | |
974 */ | |
975 public void applyTextPresentation(TextPresentation tp) { | |
976 Set decorations; | |
977 | |
978 synchronized (fHighlightedDecorationsMapLock) { | |
979 if (fHighlightedDecorationsMap is null || fHighlightedDecorationsMap.isEmpty()) | |
980 return; | |
981 | |
982 decorations= new HashSet(fHighlightedDecorationsMap.entrySet()); | |
983 } | |
984 | |
985 IRegion region= tp.getExtent(); | |
986 | |
987 if (DEBUG) | |
988 System.out.println("AP: applying text presentation offset: " + region.getOffset() + ", length= " + region.getLength()); //$NON-NLS-1$ //$NON-NLS-2$ | |
989 | |
990 for (int layer= 0, maxLayer= 1; layer < maxLayer; layer++) { | |
991 | |
992 for (Iterator iter= decorations.iterator(); iter.hasNext();) { | |
993 Map.Entry entry= (Map.Entry)iter.next(); | |
994 | |
995 Annotation a= (Annotation)entry.getKey(); | |
996 if (a.isMarkedDeleted()) | |
997 continue; | |
998 | |
999 Decoration pp = (Decoration)entry.getValue(); | |
1000 | |
1001 maxLayer= Math.max(maxLayer, pp.fLayer + 1); // dynamically update layer maximum | |
1002 if (pp.fLayer !is layer) // wrong layer: skip annotation | |
1003 continue; | |
1004 | |
1005 Position p= pp.fPosition; | |
1006 if (fSourceViewer instanceof ITextViewerExtension5) { | |
1007 ITextViewerExtension5 extension3= (ITextViewerExtension5) fSourceViewer; | |
1008 if (null is extension3.modelRange2WidgetRange(new Region(p.getOffset(), p.getLength()))) | |
1009 continue; | |
1010 } else if (!fSourceViewer.overlapsWithVisibleRegion(p.offset, p.length)) { | |
1011 continue; | |
1012 } | |
1013 | |
1014 int regionEnd= region.getOffset() + region.getLength(); | |
1015 int pEnd= p.getOffset() + p.getLength(); | |
1016 if (pEnd >= region.getOffset() && regionEnd > p.getOffset()) { | |
1017 int start= Math.max(p.getOffset(), region.getOffset()); | |
1018 int end= Math.min(regionEnd, pEnd); | |
1019 int length= Math.max(end - start, 0); | |
1020 StyleRange styleRange= new StyleRange(start, length, null, null); | |
1021 ((ITextStyleStrategy)pp.fPaintingStrategy).applyTextStyle(styleRange, pp.fColor); | |
1022 tp.mergeStyleRange(styleRange); | |
1023 } | |
1024 } | |
1025 } | |
1026 } | |
1027 | |
1028 /* | |
1029 * @see dwtx.jface.text.source.IAnnotationModelListener#modelChanged(dwtx.jface.text.source.IAnnotationModel) | |
1030 */ | |
1031 public synchronized void modelChanged(final IAnnotationModel model) { | |
1032 if (DEBUG) | |
1033 System.err.println("AP: OLD API of AnnotationModelListener called"); //$NON-NLS-1$ | |
1034 | |
1035 modelChanged(new AnnotationModelEvent(model)); | |
1036 } | |
1037 | |
1038 /* | |
1039 * @see dwtx.jface.text.source.IAnnotationModelListenerExtension#modelChanged(dwtx.jface.text.source.AnnotationModelEvent) | |
1040 */ | |
1041 public void modelChanged(final AnnotationModelEvent event) { | |
1042 Display textWidgetDisplay; | |
1043 try { | |
1044 StyledText textWidget= fTextWidget; | |
1045 if (textWidget is null || textWidget.isDisposed()) | |
1046 return; | |
1047 textWidgetDisplay= textWidget.getDisplay(); | |
1048 } catch (DWTException ex) { | |
1049 if (ex.code is DWT.ERROR_WIDGET_DISPOSED) | |
1050 return; | |
1051 throw ex; | |
1052 } | |
1053 | |
1054 if (fIsSettingModel) { | |
1055 // inside the UI thread -> no need for posting | |
1056 if (textWidgetDisplay is Display.getCurrent()) | |
1057 updatePainting(event); | |
1058 else { | |
1059 /* | |
1060 * we can throw away the changes since | |
1061 * further update painting will happen | |
1062 */ | |
1063 return; | |
1064 } | |
1065 } else { | |
1066 if (DEBUG && event !is null && event.isWorldChange()) { | |
1067 System.out.println("AP: WORLD CHANGED, stack trace follows:"); //$NON-NLS-1$ | |
1068 new Throwable().printStackTrace(System.out); | |
1069 } | |
1070 | |
1071 // XXX: posting here is a problem for annotations that are being | |
1072 // removed and the positions of which are not updated to document | |
1073 // changes any more. If the document gets modified between | |
1074 // now and running the posted runnable, the position information | |
1075 // is not accurate any longer. | |
1076 textWidgetDisplay.asyncExec(new Runnable() { | |
1077 public void run() { | |
1078 if (fTextWidget !is null && !fTextWidget.isDisposed()) | |
1079 updatePainting(event); | |
1080 } | |
1081 }); | |
1082 } | |
1083 } | |
1084 | |
1085 /** | |
1086 * Sets the color in which the squiggly for the given annotation type should be drawn. | |
1087 * | |
1088 * @param annotationType the annotation type | |
1089 * @param color the color | |
1090 */ | |
1091 public void setAnnotationTypeColor(Object annotationType, Color color) { | |
1092 if (color !is null) | |
1093 fAnnotationType2Color.put(annotationType, color); | |
1094 else | |
1095 fAnnotationType2Color.remove(annotationType); | |
1096 fCachedAnnotationType2Color.clear(); | |
1097 } | |
1098 | |
1099 /** | |
1100 * Adds the given annotation type to the list of annotation types whose | |
1101 * annotations should be painted by this painter using squiggly drawing. If the annotation type | |
1102 * is already in this list, this method is without effect. | |
1103 * | |
1104 * @param annotationType the annotation type | |
1105 */ | |
1106 public void addAnnotationType(Object annotationType) { | |
1107 addAnnotationType(annotationType, SQUIGGLES); | |
1108 } | |
1109 | |
1110 /** | |
1111 * Adds the given annotation type to the list of annotation types whose | |
1112 * annotations should be painted by this painter using the given drawing strategy. | |
1113 * If the annotation type is already in this list, the old drawing strategy gets replaced. | |
1114 * | |
1115 * @param annotationType the annotation type | |
1116 * @param drawingStrategyID the id of the drawing strategy that should be used for this annotation type | |
1117 * @since 3.0 | |
1118 */ | |
1119 public void addAnnotationType(Object annotationType, Object drawingStrategyID) { | |
1120 fAnnotationType2PaintingStrategyId.put(annotationType, drawingStrategyID); | |
1121 fCachedAnnotationType2PaintingStrategy.clear(); | |
1122 | |
1123 if (fTextInputListener is null) { | |
1124 fTextInputListener= new ITextInputListener() { | |
1125 | |
1126 /* | |
1127 * @see dwtx.jface.text.ITextInputListener#inputDocumentAboutToBeChanged(dwtx.jface.text.IDocument, dwtx.jface.text.IDocument) | |
1128 */ | |
1129 public void inputDocumentAboutToBeChanged(IDocument oldInput, IDocument newInput) { | |
1130 fInputDocumentAboutToBeChanged= true; | |
1131 } | |
1132 | |
1133 /* | |
1134 * @see dwtx.jface.text.ITextInputListener#inputDocumentChanged(dwtx.jface.text.IDocument, dwtx.jface.text.IDocument) | |
1135 */ | |
1136 public void inputDocumentChanged(IDocument oldInput, IDocument newInput) { | |
1137 fInputDocumentAboutToBeChanged= false; | |
1138 } | |
1139 }; | |
1140 fSourceViewer.addTextInputListener(fTextInputListener); | |
1141 } | |
1142 | |
1143 } | |
1144 | |
1145 /** | |
1146 * Registers a new drawing strategy under the given ID. If there is already a | |
1147 * strategy registered under <code>id</code>, the old strategy gets replaced. | |
1148 * <p>The given id can be referenced when adding annotation types, see | |
1149 * {@link #addAnnotationType(Object, Object)}.</p> | |
1150 * | |
1151 * @param id the identifier under which the strategy can be referenced, not <code>null</code> | |
1152 * @param strategy the new strategy | |
1153 * @since 3.0 | |
1154 */ | |
1155 public void addDrawingStrategy(Object id, IDrawingStrategy strategy) { | |
1156 // don't permit null as null is used to signal that an annotation type is not | |
1157 // registered with a specific strategy, and that its annotation hierarchy should be searched | |
1158 if (id is null) | |
1159 throw new IllegalArgumentException(); | |
1160 fPaintingStrategyId2PaintingStrategy.put(id, strategy); | |
1161 fCachedAnnotationType2PaintingStrategy.clear(); | |
1162 } | |
1163 | |
1164 /** | |
1165 * Registers a new drawing strategy under the given ID. If there is already | |
1166 * a strategy registered under <code>id</code>, the old strategy gets | |
1167 * replaced. | |
1168 * <p> | |
1169 * The given id can be referenced when adding annotation types, see | |
1170 * {@link #addAnnotationType(Object, Object)}. | |
1171 * </p> | |
1172 * | |
1173 * @param id the identifier under which the strategy can be referenced, not <code>null</code> | |
1174 * @param strategy the new strategy | |
1175 * @since 3.4 | |
1176 */ | |
1177 public void addTextStyleStrategy(Object id, ITextStyleStrategy strategy) { | |
1178 // don't permit null as null is used to signal that an annotation type is not | |
1179 // registered with a specific strategy, and that its annotation hierarchy should be searched | |
1180 if (id is null) | |
1181 throw new IllegalArgumentException(); | |
1182 fPaintingStrategyId2PaintingStrategy.put(id, strategy); | |
1183 fCachedAnnotationType2PaintingStrategy.clear(); | |
1184 } | |
1185 | |
1186 /** | |
1187 * Adds the given annotation type to the list of annotation types whose | |
1188 * annotations should be highlighted this painter. If the annotation type | |
1189 * is already in this list, this method is without effect. | |
1190 * | |
1191 * @param annotationType the annotation type | |
1192 * @since 3.0 | |
1193 */ | |
1194 public void addHighlightAnnotationType(Object annotationType) { | |
1195 addAnnotationType(annotationType, HIGHLIGHTING); | |
1196 } | |
1197 | |
1198 /** | |
1199 * Removes the given annotation type from the list of annotation types whose | |
1200 * annotations are painted by this painter. If the annotation type is not | |
1201 * in this list, this method is without effect. | |
1202 * | |
1203 * @param annotationType the annotation type | |
1204 */ | |
1205 public void removeAnnotationType(Object annotationType) { | |
1206 fCachedAnnotationType2PaintingStrategy.clear(); | |
1207 fAnnotationType2PaintingStrategyId.remove(annotationType); | |
1208 if (fAnnotationType2PaintingStrategyId.isEmpty() && fTextInputListener !is null) { | |
1209 fSourceViewer.removeTextInputListener(fTextInputListener); | |
1210 fTextInputListener= null; | |
1211 fInputDocumentAboutToBeChanged= false; | |
1212 } | |
1213 } | |
1214 | |
1215 /** | |
1216 * Removes the given annotation type from the list of annotation types whose | |
1217 * annotations are highlighted by this painter. If the annotation type is not | |
1218 * in this list, this method is without effect. | |
1219 * | |
1220 * @param annotationType the annotation type | |
1221 * @since 3.0 | |
1222 */ | |
1223 public void removeHighlightAnnotationType(Object annotationType) { | |
1224 removeAnnotationType(annotationType); | |
1225 } | |
1226 | |
1227 /** | |
1228 * Clears the list of annotation types whose annotations are | |
1229 * painted by this painter. | |
1230 */ | |
1231 public void removeAllAnnotationTypes() { | |
1232 fCachedAnnotationType2PaintingStrategy.clear(); | |
1233 fAnnotationType2PaintingStrategyId.clear(); | |
1234 if (fTextInputListener !is null) { | |
1235 fSourceViewer.removeTextInputListener(fTextInputListener); | |
1236 fTextInputListener= null; | |
1237 } | |
1238 } | |
1239 | |
1240 /** | |
1241 * Returns whether the list of annotation types whose annotations are painted | |
1242 * by this painter contains at least on element. | |
1243 * | |
1244 * @return <code>true</code> if there is an annotation type whose annotations are painted | |
1245 */ | |
1246 public bool isPaintingAnnotations() { | |
1247 return !fAnnotationType2PaintingStrategyId.isEmpty(); | |
1248 } | |
1249 | |
1250 /* | |
1251 * @see dwtx.jface.text.IPainter#dispose() | |
1252 */ | |
1253 public void dispose() { | |
1254 | |
1255 if (fAnnotationType2Color !is null) { | |
1256 fAnnotationType2Color.clear(); | |
1257 fAnnotationType2Color= null; | |
1258 } | |
1259 | |
1260 if (fCachedAnnotationType2Color !is null) { | |
1261 fCachedAnnotationType2Color.clear(); | |
1262 fCachedAnnotationType2Color= null; | |
1263 } | |
1264 | |
1265 if (fCachedAnnotationType2PaintingStrategy !is null) { | |
1266 fCachedAnnotationType2PaintingStrategy.clear(); | |
1267 fCachedAnnotationType2PaintingStrategy= null; | |
1268 } | |
1269 | |
1270 if (fAnnotationType2PaintingStrategyId !is null) { | |
1271 fAnnotationType2PaintingStrategyId.clear(); | |
1272 fAnnotationType2PaintingStrategyId= null; | |
1273 } | |
1274 | |
1275 fTextWidget= null; | |
1276 fSourceViewer= null; | |
1277 fAnnotationAccess= null; | |
1278 fModel= null; | |
1279 synchronized (fDecorationMapLock) { | |
1280 fDecorationsMap= null; | |
1281 } | |
1282 synchronized (fHighlightedDecorationsMapLock) { | |
1283 fHighlightedDecorationsMap= null; | |
1284 } | |
1285 } | |
1286 | |
1287 /** | |
1288 * Returns the document offset of the upper left corner of the source viewer's view port, | |
1289 * possibly including partially visible lines. | |
1290 * | |
1291 * @return the document offset if the upper left corner of the view port | |
1292 */ | |
1293 private int getInclusiveTopIndexStartOffset() { | |
1294 | |
1295 if (fTextWidget !is null && !fTextWidget.isDisposed()) { | |
1296 int top= JFaceTextUtil.getPartialTopIndex(fSourceViewer); | |
1297 try { | |
1298 IDocument document= fSourceViewer.getDocument(); | |
1299 return document.getLineOffset(top); | |
1300 } catch (BadLocationException x) { | |
1301 } | |
1302 } | |
1303 | |
1304 return -1; | |
1305 } | |
1306 | |
1307 /** | |
1308 * Returns the first invisible document offset of the lower right corner of the source viewer's view port, | |
1309 * possibly including partially visible lines. | |
1310 * | |
1311 * @return the first invisible document offset of the lower right corner of the view port | |
1312 */ | |
1313 private int getExclusiveBottomIndexEndOffset() { | |
1314 | |
1315 if (fTextWidget !is null && !fTextWidget.isDisposed()) { | |
1316 int bottom= JFaceTextUtil.getPartialBottomIndex(fSourceViewer); | |
1317 try { | |
1318 IDocument document= fSourceViewer.getDocument(); | |
1319 | |
1320 if (bottom >= document.getNumberOfLines()) | |
1321 bottom= document.getNumberOfLines() - 1; | |
1322 | |
1323 return document.getLineOffset(bottom) + document.getLineLength(bottom); | |
1324 } catch (BadLocationException x) { | |
1325 } | |
1326 } | |
1327 | |
1328 return -1; | |
1329 } | |
1330 | |
1331 /* | |
1332 * @see dwt.events.PaintListener#paintControl(dwt.events.PaintEvent) | |
1333 */ | |
1334 public void paintControl(PaintEvent event) { | |
1335 if (fTextWidget !is null) | |
1336 handleDrawRequest(event); | |
1337 } | |
1338 | |
1339 /** | |
1340 * Handles the request to draw the annotations using the given graphical context. | |
1341 * | |
1342 * @param event the paint event or <code>null</code> | |
1343 */ | |
1344 private void handleDrawRequest(PaintEvent event) { | |
1345 | |
1346 if (fTextWidget is null) { | |
1347 // is already disposed | |
1348 return; | |
1349 } | |
1350 | |
1351 IRegion clippingRegion= computeClippingRegion(event, false); | |
1352 if (clippingRegion is null) | |
1353 return; | |
1354 | |
1355 int vOffset= clippingRegion.getOffset(); | |
1356 int vLength= clippingRegion.getLength(); | |
1357 | |
1358 final GC gc= event !is null ? event.gc : null; | |
1359 | |
1360 // Clone decorations | |
1361 Collection decorations; | |
1362 synchronized (fDecorationMapLock) { | |
1363 decorations= new ArrayList(fDecorationsMap.size()); | |
1364 decorations.addAll(fDecorationsMap.entrySet()); | |
1365 } | |
1366 | |
1367 /* | |
1368 * Create a new list of annotations to be drawn, since removing from decorations is more | |
1369 * expensive. One bucket per drawing layer. Use linked lists as addition is cheap here. | |
1370 */ | |
1371 ArrayList toBeDrawn= new ArrayList(10); | |
1372 for (Iterator e = decorations.iterator(); e.hasNext();) { | |
1373 Map.Entry entry= (Map.Entry)e.next(); | |
1374 | |
1375 Annotation a= (Annotation)entry.getKey(); | |
1376 Decoration pp = (Decoration)entry.getValue(); | |
1377 // prune any annotation that is not drawable or does not need drawing | |
1378 if (!(a.isMarkedDeleted() || skip(a) || !pp.fPosition.overlapsWith(vOffset, vLength))) { | |
1379 // ensure sized appropriately | |
1380 for (int i= toBeDrawn.size(); i <= pp.fLayer; i++) | |
1381 toBeDrawn.add(new LinkedList()); | |
1382 ((List) toBeDrawn.get(pp.fLayer)).add(entry); | |
1383 } | |
1384 } | |
1385 IDocument document= fSourceViewer.getDocument(); | |
1386 for (Iterator it= toBeDrawn.iterator(); it.hasNext();) { | |
1387 List layer= (List) it.next(); | |
1388 for (Iterator e = layer.iterator(); e.hasNext();) { | |
1389 Map.Entry entry= (Map.Entry)e.next(); | |
1390 Annotation a= (Annotation)entry.getKey(); | |
1391 Decoration pp = (Decoration)entry.getValue(); | |
1392 drawDecoration(pp, gc, a, clippingRegion, document); | |
1393 } | |
1394 } | |
1395 } | |
1396 | |
1397 private void drawDecoration(Decoration pp, GC gc, Annotation annotation, IRegion clippingRegion, IDocument document) { | |
1398 if (clippingRegion is null) | |
1399 return; | |
1400 | |
1401 if (!(pp.fPaintingStrategy instanceof IDrawingStrategy)) | |
1402 return; | |
1403 | |
1404 IDrawingStrategy drawingStrategy= (IDrawingStrategy)pp.fPaintingStrategy; | |
1405 | |
1406 int clippingOffset= clippingRegion.getOffset(); | |
1407 int clippingLength= clippingRegion.getLength(); | |
1408 | |
1409 Position p= pp.fPosition; | |
1410 try { | |
1411 | |
1412 int startLine= document.getLineOfOffset(p.getOffset()); | |
1413 int lastInclusive= Math.max(p.getOffset(), p.getOffset() + p.getLength() - 1); | |
1414 int endLine= document.getLineOfOffset(lastInclusive); | |
1415 | |
1416 for (int i= startLine; i <= endLine; i++) { | |
1417 int lineOffset= document.getLineOffset(i); | |
1418 int paintStart= Math.max(lineOffset, p.getOffset()); | |
1419 String lineDelimiter= document.getLineDelimiter(i); | |
1420 int delimiterLength= lineDelimiter !is null ? lineDelimiter.length() : 0; | |
1421 int paintLength= Math.min(lineOffset + document.getLineLength(i) - delimiterLength, p.getOffset() + p.getLength()) - paintStart; | |
1422 if (paintLength >= 0 && overlapsWith(paintStart, paintLength, clippingOffset, clippingLength)) { | |
1423 // otherwise inside a line delimiter | |
1424 IRegion widgetRange= getWidgetRange(paintStart, paintLength); | |
1425 if (widgetRange !is null) { | |
1426 drawingStrategy.draw(annotation, gc, fTextWidget, widgetRange.getOffset(), widgetRange.getLength(), pp.fColor); | |
1427 } | |
1428 } | |
1429 } | |
1430 | |
1431 } catch (BadLocationException x) { | |
1432 } | |
1433 } | |
1434 | |
1435 /** | |
1436 * Computes the model (document) region that is covered by the paint event's clipping region. If | |
1437 * <code>event</code> is <code>null</code>, the model range covered by the visible editor | |
1438 * area (viewport) is returned. | |
1439 * | |
1440 * @param event the paint event or <code>null</code> to use the entire viewport | |
1441 * @param isClearing tells whether the clipping is need for clearing an annotation | |
1442 * @return the model region comprised by either the paint event's clipping region or the | |
1443 * viewport | |
1444 * @since 3.2 | |
1445 */ | |
1446 private IRegion computeClippingRegion(PaintEvent event, bool isClearing) { | |
1447 if (event is null) { | |
1448 | |
1449 if (!isClearing && fCurrentDrawRange !is null) | |
1450 return new Region(fCurrentDrawRange.offset, fCurrentDrawRange.length); | |
1451 | |
1452 // trigger a repaint of the entire viewport | |
1453 int vOffset= getInclusiveTopIndexStartOffset(); | |
1454 if (vOffset is -1) | |
1455 return null; | |
1456 | |
1457 // http://bugs.eclipse.org/bugs/show_bug.cgi?id=17147 | |
1458 int vLength= getExclusiveBottomIndexEndOffset() - vOffset; | |
1459 | |
1460 return new Region(vOffset, vLength); | |
1461 } | |
1462 | |
1463 int widgetOffset; | |
1464 try { | |
1465 int widgetClippingStartOffset= fTextWidget.getOffsetAtLocation(new Point(0, event.y)); | |
1466 int firstWidgetLine= fTextWidget.getLineAtOffset(widgetClippingStartOffset); | |
1467 widgetOffset= fTextWidget.getOffsetAtLine(firstWidgetLine); | |
1468 } catch (IllegalArgumentException ex1) { | |
1469 try { | |
1470 int firstVisibleLine= JFaceTextUtil.getPartialTopIndex(fTextWidget); | |
1471 widgetOffset= fTextWidget.getOffsetAtLine(firstVisibleLine); | |
1472 } catch (IllegalArgumentException ex2) { // above try code might fail too | |
1473 widgetOffset= 0; | |
1474 } | |
1475 } | |
1476 | |
1477 int widgetEndOffset; | |
1478 try { | |
1479 int widgetClippingEndOffset= fTextWidget.getOffsetAtLocation(new Point(0, event.y + event.height)); | |
1480 int lastWidgetLine= fTextWidget.getLineAtOffset(widgetClippingEndOffset); | |
1481 widgetEndOffset= fTextWidget.getOffsetAtLine(lastWidgetLine + 1); | |
1482 } catch (IllegalArgumentException ex1) { | |
1483 // happens if the editor is not "full", e.g. the last line of the document is visible in the editor | |
1484 try { | |
1485 int lastVisibleLine= JFaceTextUtil.getPartialBottomIndex(fTextWidget); | |
1486 if (lastVisibleLine is fTextWidget.getLineCount() - 1) | |
1487 // last line | |
1488 widgetEndOffset= fTextWidget.getCharCount(); | |
1489 else | |
1490 widgetEndOffset= fTextWidget.getOffsetAtLine(lastVisibleLine + 1) - 1; | |
1491 } catch (IllegalArgumentException ex2) { // above try code might fail too | |
1492 widgetEndOffset= fTextWidget.getCharCount(); | |
1493 } | |
1494 } | |
1495 | |
1496 IRegion clippingRegion= getModelRange(widgetOffset, widgetEndOffset - widgetOffset); | |
1497 | |
1498 return clippingRegion; | |
1499 } | |
1500 | |
1501 /** | |
1502 * Should the given annotation be skipped when handling draw requests? | |
1503 * | |
1504 * @param annotation the annotation | |
1505 * @return <code>true</code> iff the given annotation should be | |
1506 * skipped when handling draw requests | |
1507 * @since 3.0 | |
1508 */ | |
1509 protected bool skip(Annotation annotation) { | |
1510 return false; | |
1511 } | |
1512 | |
1513 /** | |
1514 * Returns the widget region that corresponds to the | |
1515 * given offset and length in the viewer's document. | |
1516 * | |
1517 * @param modelOffset the model offset | |
1518 * @param modelLength the model length | |
1519 * @return the corresponding widget region | |
1520 */ | |
1521 private IRegion getWidgetRange(int modelOffset, int modelLength) { | |
1522 fReusableRegion.setOffset(modelOffset); | |
1523 fReusableRegion.setLength(modelLength); | |
1524 | |
1525 if (fReusableRegion is null || fReusableRegion.getOffset() is Integer.MAX_VALUE) | |
1526 return null; | |
1527 | |
1528 if (fSourceViewer instanceof ITextViewerExtension5) { | |
1529 ITextViewerExtension5 extension= (ITextViewerExtension5) fSourceViewer; | |
1530 return extension.modelRange2WidgetRange(fReusableRegion); | |
1531 } | |
1532 | |
1533 IRegion region= fSourceViewer.getVisibleRegion(); | |
1534 int offset= region.getOffset(); | |
1535 int length= region.getLength(); | |
1536 | |
1537 if (overlapsWith(fReusableRegion, region)) { | |
1538 int p1= Math.max(offset, fReusableRegion.getOffset()); | |
1539 int p2= Math.min(offset + length, fReusableRegion.getOffset() + fReusableRegion.getLength()); | |
1540 return new Region(p1 - offset, p2 - p1); | |
1541 } | |
1542 return null; | |
1543 } | |
1544 | |
1545 /** | |
1546 * Returns the model region that corresponds to the given region in the | |
1547 * viewer's text widget. | |
1548 * | |
1549 * @param offset the offset in the viewer's widget | |
1550 * @param length the length in the viewer's widget | |
1551 * @return the corresponding document region | |
1552 * @since 3.2 | |
1553 */ | |
1554 private IRegion getModelRange(int offset, int length) { | |
1555 if (offset is Integer.MAX_VALUE) | |
1556 return null; | |
1557 | |
1558 if (fSourceViewer instanceof ITextViewerExtension5) { | |
1559 ITextViewerExtension5 extension= (ITextViewerExtension5) fSourceViewer; | |
1560 return extension.widgetRange2ModelRange(new Region(offset, length)); | |
1561 } | |
1562 | |
1563 IRegion region= fSourceViewer.getVisibleRegion(); | |
1564 return new Region(region.getOffset() + offset, length); | |
1565 } | |
1566 | |
1567 /** | |
1568 * Checks whether the intersection of the given text ranges | |
1569 * is empty or not. | |
1570 * | |
1571 * @param range1 the first range to check | |
1572 * @param range2 the second range to check | |
1573 * @return <code>true</code> if intersection is not empty | |
1574 */ | |
1575 private bool overlapsWith(IRegion range1, IRegion range2) { | |
1576 return overlapsWith(range1.getOffset(), range1.getLength(), range2.getOffset(), range2.getLength()); | |
1577 } | |
1578 | |
1579 /** | |
1580 * Checks whether the intersection of the given text ranges | |
1581 * is empty or not. | |
1582 * | |
1583 * @param offset1 offset of the first range | |
1584 * @param length1 length of the first range | |
1585 * @param offset2 offset of the second range | |
1586 * @param length2 length of the second range | |
1587 * @return <code>true</code> if intersection is not empty | |
1588 */ | |
1589 private bool overlapsWith(int offset1, int length1, int offset2, int length2) { | |
1590 int end= offset2 + length2; | |
1591 int thisEnd= offset1 + length1; | |
1592 | |
1593 if (length2 > 0) { | |
1594 if (length1 > 0) | |
1595 return offset1 < end && offset2 < thisEnd; | |
1596 return offset2 <= offset1 && offset1 < end; | |
1597 } | |
1598 | |
1599 if (length1 > 0) | |
1600 return offset1 <= offset2 && offset2 < thisEnd; | |
1601 return offset1 is offset2; | |
1602 } | |
1603 | |
1604 /* | |
1605 * @see dwtx.jface.text.IPainter#deactivate(bool) | |
1606 */ | |
1607 public void deactivate(bool redraw) { | |
1608 if (fIsActive) { | |
1609 fIsActive= false; | |
1610 disablePainting(redraw); | |
1611 setModel(null); | |
1612 catchupWithModel(null); | |
1613 } | |
1614 } | |
1615 | |
1616 /** | |
1617 * Returns whether the given reason causes a repaint. | |
1618 * | |
1619 * @param reason the reason | |
1620 * @return <code>true</code> if repaint reason, <code>false</code> otherwise | |
1621 * @since 3.0 | |
1622 */ | |
1623 protected bool isRepaintReason(int reason) { | |
1624 return CONFIGURATION is reason || INTERNAL is reason; | |
1625 } | |
1626 | |
1627 /** | |
1628 * Retrieves the annotation model from the given source viewer. | |
1629 * | |
1630 * @param sourceViewer the source viewer | |
1631 * @return the source viewer's annotation model or <code>null</code> if none can be found | |
1632 * @since 3.0 | |
1633 */ | |
1634 protected IAnnotationModel findAnnotationModel(ISourceViewer sourceViewer) { | |
1635 if(sourceViewer !is null) | |
1636 return sourceViewer.getAnnotationModel(); | |
1637 return null; | |
1638 } | |
1639 | |
1640 /* | |
1641 * @see dwtx.jface.text.IPainter#paint(int) | |
1642 */ | |
1643 public void paint(int reason) { | |
1644 if (fSourceViewer.getDocument() is null) { | |
1645 deactivate(false); | |
1646 return; | |
1647 } | |
1648 | |
1649 if (!fIsActive) { | |
1650 IAnnotationModel model= findAnnotationModel(fSourceViewer); | |
1651 if (model !is null) { | |
1652 fIsActive= true; | |
1653 setModel(model); | |
1654 } | |
1655 } else if (isRepaintReason(reason)) | |
1656 updatePainting(null); | |
1657 } | |
1658 | |
1659 /* | |
1660 * @see dwtx.jface.text.IPainter#setPositionManager(dwtx.jface.text.IPaintPositionManager) | |
1661 */ | |
1662 public void setPositionManager(IPaintPositionManager manager) { | |
1663 } | |
1664 } |