Got back Next/Previous Annotation functionality.
[phpeclipse.git] / net.sourceforge.phpeclipse / src / net / sourceforge / phpeclipse / phpeditor / ProblemPainter.java
1 /**********************************************************************
2  Copyright (c) 2000, 2002 IBM Corp. and others.
3  All rights reserved. This program and the accompanying materials
4  are made available under the terms of the Common Public License v1.0
5  which accompanies this distribution, and is available at
6  http://www.eclipse.org/legal/cpl-v10.html
7
8  Contributors:
9  IBM Corporation - Initial implementation
10  **********************************************************************/
11
12 package net.sourceforge.phpeclipse.phpeditor;
13
14 import java.util.ArrayList;
15 import java.util.HashMap;
16 import java.util.HashSet;
17 import java.util.Iterator;
18 import java.util.List;
19 import java.util.Map;
20 import java.util.Set;
21
22 import net.sourceforge.phpeclipse.PHPeclipsePlugin;
23
24 import org.eclipse.jface.text.BadLocationException;
25 import org.eclipse.jface.text.IDocument;
26 import org.eclipse.jface.text.IRegion;
27 import org.eclipse.jface.text.ITextViewerExtension5;
28 import org.eclipse.jface.text.Position;
29 import org.eclipse.jface.text.Region;
30 import org.eclipse.jface.text.source.Annotation;
31 import org.eclipse.jface.text.source.IAnnotationModel;
32 import org.eclipse.jface.text.source.IAnnotationModelListener;
33 import org.eclipse.jface.text.source.ISourceViewer;
34 import org.eclipse.swt.custom.StyledText;
35 import org.eclipse.swt.events.PaintEvent;
36 import org.eclipse.swt.events.PaintListener;
37 import org.eclipse.swt.graphics.Color;
38 import org.eclipse.swt.graphics.GC;
39 import org.eclipse.swt.graphics.Point;
40 import org.eclipse.swt.widgets.Display;
41 import org.eclipse.ui.texteditor.IDocumentProvider;
42 import org.eclipse.ui.texteditor.ITextEditor;
43
44 /**
45  * Highlights the temporary problems.
46  */
47 public class ProblemPainter implements IPainter, PaintListener,
48                 IAnnotationModelListener {
49
50         private static class ProblemPosition {
51                 Position fPosition;
52
53                 Color fColor;
54
55                 boolean fMultiLine;
56         };
57
58         private boolean fIsActive = false;
59
60         private boolean fIsPainting = false;
61
62         private boolean fIsSettingModel = false;
63
64         private ITextEditor fTextEditor;
65
66         private ISourceViewer fSourceViewer;
67
68         private StyledText fTextWidget;
69
70         private IAnnotationModel fModel;
71
72         private List fProblemPositions = new ArrayList();
73
74         private Map fColorTable = new HashMap();
75
76         private Set fAnnotationSet = new HashSet();
77
78         public ProblemPainter(ITextEditor textEditor, ISourceViewer sourceViewer) {
79                 fTextEditor = textEditor;
80                 fSourceViewer = sourceViewer;
81                 fTextWidget = sourceViewer.getTextWidget();
82         }
83
84         private boolean hasProblems() {
85                 return !fProblemPositions.isEmpty();
86         }
87
88         private void enablePainting() {
89                 if (!fIsPainting && hasProblems()) {
90                         fIsPainting = true;
91                         fTextWidget.addPaintListener(this);
92                         handleDrawRequest(null);
93                 }
94         }
95
96         private void disablePainting(boolean redraw) {
97                 if (fIsPainting) {
98                         fIsPainting = false;
99                         fTextWidget.removePaintListener(this);
100                         if (redraw && hasProblems())
101                                 handleDrawRequest(null);
102                 }
103         }
104
105         private void setModel(IAnnotationModel model) {
106                 if (fModel != model) {
107                         if (fModel != null)
108                                 fModel.removeAnnotationModelListener(this);
109                         fModel = model;
110                         if (fModel != null) {
111                                 try {
112                                         fIsSettingModel = true;
113                                         fModel.addAnnotationModelListener(this);
114                                 } finally {
115                                         fIsSettingModel = false;
116                                 }
117                         }
118                 }
119         }
120
121         private void catchupWithModel() {
122                 if (fProblemPositions != null) {
123                         fProblemPositions.clear();
124                         if (fModel != null) {
125
126                                 Iterator e = new ProblemAnnotationIterator(fModel, true);
127                                 while (e.hasNext()) {
128                                         IProblemAnnotation pa = (IProblemAnnotation) e.next();
129                                         Annotation a = (Annotation) pa;
130
131                                         Color color = null;
132                                         AnnotationType type = pa.getAnnotationType();
133                                         if (fAnnotationSet.contains(type))
134                                                 color = (Color) fColorTable.get(type);
135
136                                         if (color != null) {
137                                                 ProblemPosition pp = new ProblemPosition();
138                                                 pp.fPosition = fModel.getPosition(a);
139                                                 pp.fColor = color;
140                                                 pp.fMultiLine = true;
141                                                 fProblemPositions.add(pp);
142                                         }
143                                 }
144                         }
145                 }
146         }
147
148         private void updatePainting() {
149                 disablePainting(true);
150                 catchupWithModel();
151                 enablePainting();
152         }
153
154         /*
155          * @see IAnnotationModelListener#modelChanged(IAnnotationModel)
156          */
157         public void modelChanged(final IAnnotationModel model) {
158                 if (fTextWidget != null && !fTextWidget.isDisposed()) {
159                         if (fIsSettingModel) {
160                                 // inside the ui thread -> no need for posting
161                                 updatePainting();
162                         } else {
163                                 Display d = fTextWidget.getDisplay();
164                                 if (d != null) {
165                                         d.asyncExec(new Runnable() {
166                                                 public void run() {
167                                                         if (fTextWidget != null
168                                                                         && !fTextWidget.isDisposed())
169                                                                 updatePainting();
170                                                 }
171                                         });
172                                 }
173                         }
174                 }
175         }
176
177         public void setColor(AnnotationType annotationType, Color color) {
178                 if (color != null)
179                         fColorTable.put(annotationType, color);
180                 else
181                         fColorTable.remove(annotationType);
182         }
183
184         public void paintAnnotations(AnnotationType annotationType, boolean paint) {
185                 if (paint)
186                         fAnnotationSet.add(annotationType);
187                 else
188                         fAnnotationSet.remove(annotationType);
189         }
190
191         public boolean isPaintingAnnotations() {
192                 return !fAnnotationSet.isEmpty();
193         }
194
195         /*
196          * @see IPainter#dispose()
197          */
198         public void dispose() {
199
200                 if (fColorTable != null)
201                         fColorTable.clear();
202                 fColorTable = null;
203
204                 if (fAnnotationSet != null)
205                         fAnnotationSet.clear();
206                 fAnnotationSet = null;
207
208                 fTextWidget = null;
209                 fModel = null;
210                 fProblemPositions = null;
211         }
212
213         /*
214          * Returns the document offset of the upper left corner of the widgets
215          * viewport, possibly including partially visible lines.
216          */
217         private int getInclusiveTopIndexStartOffset() {
218
219                 if (fTextWidget != null && !fTextWidget.isDisposed()) {
220                         int top = fSourceViewer.getTopIndex();
221                         if ((fTextWidget.getTopPixel() % fTextWidget.getLineHeight()) != 0)
222                                 top--;
223                         try {
224                                 IDocument document = fSourceViewer.getDocument();
225                                 return document.getLineOffset(top);
226                         } catch (BadLocationException ex) {
227                         }
228                 }
229
230                 return -1;
231         }
232
233         /*
234          * @see PaintListener#paintControl(PaintEvent)
235          */
236         public void paintControl(PaintEvent event) {
237                 if (fTextWidget != null)
238                         handleDrawRequest(event.gc);
239         }
240
241         private void handleDrawRequest(GC gc) {
242
243                 int vOffset = getInclusiveTopIndexStartOffset();
244                 // http://bugs.eclipse.org/bugs/show_bug.cgi?id=17147
245                 int vLength = fSourceViewer.getBottomIndexEndOffset() + 1;
246
247                 for (Iterator e = fProblemPositions.iterator(); e.hasNext();) {
248                         ProblemPosition pp = (ProblemPosition) e.next();
249                         Position p = pp.fPosition;
250                         if (p.overlapsWith(vOffset, vLength)) {
251
252                                 if (!pp.fMultiLine) {
253
254                                         IRegion widgetRange = getWidgetRange(p);
255                                         if (widgetRange != null)
256                                                 draw(gc, widgetRange.getOffset(), widgetRange
257                                                                 .getLength(), pp.fColor);
258
259                                 } else {
260
261                                         IDocument document = fSourceViewer.getDocument();
262                                         try {
263
264                                                 int startLine = document.getLineOfOffset(p.getOffset());
265                                                 int lastInclusive = Math.max(p.getOffset(), p
266                                                                 .getOffset()
267                                                                 + p.getLength() - 1);
268                                                 int endLine = document.getLineOfOffset(lastInclusive);
269
270                                                 for (int i = startLine; i <= endLine; i++) {
271                                                         IRegion line = document.getLineInformation(i);
272                                                         int paintStart = Math.max(line.getOffset(), p
273                                                                         .getOffset());
274                                                         int paintEnd = Math.min(line.getOffset()
275                                                                         + line.getLength(), p.getOffset()
276                                                                         + p.getLength());
277                                                         if (paintEnd > paintStart) {
278                                                                 // otherwise inside a line delimiter
279                                                                 IRegion widgetRange = getWidgetRange(new Position(
280                                                                                 paintStart, paintEnd - paintStart));
281                                                                 if (widgetRange != null)
282                                                                         draw(gc, widgetRange.getOffset(),
283                                                                                         widgetRange.getLength(), pp.fColor);
284                                                         }
285                                                 }
286
287                                         } catch (BadLocationException x) {
288                                         }
289                                 }
290                         }
291                 }
292         }
293
294         private IRegion getWidgetRange(Position p) {
295                 if (fSourceViewer instanceof ITextViewerExtension5) {
296                         ITextViewerExtension5 extension = (ITextViewerExtension5) fSourceViewer;
297                         return extension.modelRange2WidgetRange(new Region(p.getOffset(), p
298                                         .getLength()));
299
300                 } else {
301
302                         IRegion region = fSourceViewer.getVisibleRegion();
303                         int offset = region.getOffset();
304                         int length = region.getLength();
305
306                         if (p.overlapsWith(offset, length)) {
307                                 int p1 = Math.max(offset, p.getOffset());
308                                 int p2 = Math.min(offset + length, p.getOffset()
309                                                 + p.getLength());
310                                 return new Region(p1 - offset, p2 - p1);
311                         }
312                 }
313
314                 return null;
315         }
316
317         private int[] computePolyline(Point left, Point right, int height) {
318
319                 final int WIDTH = 4; // must be even
320                 final int HEIGHT = 2; // can be any number
321                 // final int MINPEEKS= 2; // minimal number of peeks
322
323                 int peeks = (right.x - left.x) / WIDTH;
324                 // if (peeks < MINPEEKS) {
325                 // int missing= (MINPEEKS - peeks) * WIDTH;
326                 // left.x= Math.max(0, left.x - missing/2);
327                 // peeks= MINPEEKS;
328                 // }
329
330                 int leftX = left.x;
331
332                 // compute (number of point) * 2
333                 int length = ((2 * peeks) + 1) * 2;
334                 if (length < 0)
335                         return new int[0];
336
337                 int[] coordinates = new int[length];
338
339                 // cache peeks' y-coordinates
340                 int bottom = left.y + height - 1;
341                 int top = bottom - HEIGHT;
342
343                 // populate array with peek coordinates
344                 for (int i = 0; i < peeks; i++) {
345                         int index = 4 * i;
346                         coordinates[index] = leftX + (WIDTH * i);
347                         coordinates[index + 1] = bottom;
348                         coordinates[index + 2] = coordinates[index] + WIDTH / 2;
349                         coordinates[index + 3] = top;
350                 }
351
352                 // the last down flank is missing
353                 coordinates[length - 2] = left.x + (WIDTH * peeks);
354                 coordinates[length - 1] = bottom;
355
356                 return coordinates;
357         }
358
359         private void draw(GC gc, int offset, int length, Color color) {
360                 if (gc != null) {
361
362                         Point left = fTextWidget.getLocationAtOffset(offset);
363                         Point right = fTextWidget.getLocationAtOffset(offset + length);
364
365                         gc.setForeground(color);
366                         int[] polyline = computePolyline(left, right, gc.getFontMetrics()
367                                         .getHeight());
368                         gc.drawPolyline(polyline);
369
370                 } else {
371                         fTextWidget.redrawRange(offset, length, true);
372                 }
373         }
374
375         /*
376          * @see IPainter#deactivate(boolean)
377          */
378         public void deactivate(boolean redraw) {
379                 if (fIsActive) {
380                         fIsActive = false;
381                         disablePainting(redraw);
382                         setModel(null);
383                         catchupWithModel();
384                 }
385         }
386
387         /*
388          * @see IPainter#paint(int)
389          */
390         public void paint(int reason) {
391                 if (!fIsActive) {
392                         fIsActive = true;
393                         IDocumentProvider provider = PHPeclipsePlugin.getDefault()
394                                         .getCompilationUnitDocumentProvider();
395                         setModel(provider.getAnnotationModel(fTextEditor.getEditorInput()));
396                 } else if (CONFIGURATION == reason || INTERNAL == reason)
397                         updatePainting();
398         }
399
400         /*
401          * @see IPainter#setPositionManager(IPositionManager)
402          */
403         public void setPositionManager(IPositionManager manager) {
404         }
405 }