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