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