little bugfixes
[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.ITextViewerExtension3;
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 ITextViewerExtension3) {
280                         
281                         ITextViewerExtension3 extension= (ITextViewerExtension3) fSourceViewer;
282                         return extension.modelRange2WidgetRange(new Region(p.getOffset(), p.getLength()));
283                 
284                 } else {
285                         
286                         IRegion region= fSourceViewer.getVisibleRegion();
287                         int offset= region.getOffset();
288                         int length= region.getLength();
289                         
290                         if (p.overlapsWith(offset , length)) {
291                                 int p1= Math.max(offset, p.getOffset());
292                                 int p2= Math.min(offset + length, p.getOffset() + p.getLength());
293                                 return new Region(p1 - offset, p2 - p1);
294                         }
295                 }
296                 
297                 return null;
298         }
299         
300         private int[] computePolyline(Point left, Point right, int height) {
301                 
302                 final int WIDTH= 4; // must be even
303                 final int HEIGHT= 2; // can be any number
304 //              final int MINPEEKS= 2; // minimal number of peeks
305                 
306                 int peeks= (right.x - left.x) / WIDTH;
307 //              if (peeks < MINPEEKS) {
308 //                      int missing= (MINPEEKS - peeks) * WIDTH;
309 //                      left.x= Math.max(0, left.x - missing/2);
310 //                      peeks= MINPEEKS;
311 //              }
312                 
313                 int leftX= left.x;
314                                 
315                 // compute (number of point) * 2
316                 int length= ((2 * peeks) + 1) * 2;
317                 if (length < 0)
318                         return new int[0];
319                         
320                 int[] coordinates= new int[length];
321                 
322                 // cache peeks' y-coordinates
323                 int bottom= left.y + height - 1;
324                 int top= bottom - HEIGHT;
325                 
326                 // populate array with peek coordinates
327                 for (int i= 0; i < peeks; i++) {
328                         int index= 4 * i;
329                         coordinates[index]= leftX + (WIDTH * i);
330                         coordinates[index+1]= bottom;
331                         coordinates[index+2]= coordinates[index] + WIDTH/2;
332                         coordinates[index+3]= top;
333                 }
334                 
335                 // the last down flank is missing
336                 coordinates[length-2]= left.x + (WIDTH * peeks);
337                 coordinates[length-1]= bottom;
338                 
339                 return coordinates;
340         }
341         
342         private void draw(GC gc, int offset, int length, Color color) {
343                 if (gc != null) {
344                         
345                         Point left= fTextWidget.getLocationAtOffset(offset);
346                         Point right= fTextWidget.getLocationAtOffset(offset + length);
347                         
348                         gc.setForeground(color);
349                         int[] polyline= computePolyline(left, right, gc.getFontMetrics().getHeight());
350                         gc.drawPolyline(polyline);
351                                                                 
352                 } else {
353                         fTextWidget.redrawRange(offset, length, true);
354                 }
355         }
356         
357         /*
358          * @see IPainter#deactivate(boolean)
359          */
360         public void deactivate(boolean redraw) {
361                 if (fIsActive) {
362                         fIsActive= false;
363                         disablePainting(redraw);
364                         setModel(null);
365                         catchupWithModel();
366                 }
367         }
368         
369         /*
370          * @see IPainter#paint(int)
371          */
372         public void paint(int reason) {
373                 if (!fIsActive) {
374                         fIsActive= true;
375                         IDocumentProvider provider= PHPeclipsePlugin.getDefault().getCompilationUnitDocumentProvider();
376                         setModel(provider.getAnnotationModel(fTextEditor.getEditorInput()));
377                 } else if (CONFIGURATION == reason || INTERNAL == reason)
378                         updatePainting();
379         }
380
381         /*
382          * @see IPainter#setPositionManager(IPositionManager)
383          */
384         public void setPositionManager(IPositionManager manager) {
385         }
386 }