KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > jface > internal > text > revisions > RevisionPainter


1 /*******************************************************************************
2  * Copyright (c) 2006, 2007 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  *******************************************************************************/

11 package org.eclipse.jface.internal.text.revisions;
12
13 import java.util.ArrayList JavaDoc;
14 import java.util.Arrays JavaDoc;
15 import java.util.Collections JavaDoc;
16 import java.util.HashMap JavaDoc;
17 import java.util.Iterator JavaDoc;
18 import java.util.List JavaDoc;
19 import java.util.ListIterator JavaDoc;
20 import java.util.Map JavaDoc;
21 import java.util.Map.Entry;
22
23 import org.eclipse.swt.SWT;
24 import org.eclipse.swt.custom.StyledText;
25 import org.eclipse.swt.events.DisposeEvent;
26 import org.eclipse.swt.events.DisposeListener;
27 import org.eclipse.swt.events.MouseEvent;
28 import org.eclipse.swt.events.MouseListener;
29 import org.eclipse.swt.events.MouseMoveListener;
30 import org.eclipse.swt.events.MouseTrackListener;
31 import org.eclipse.swt.graphics.Color;
32 import org.eclipse.swt.graphics.FontMetrics;
33 import org.eclipse.swt.graphics.GC;
34 import org.eclipse.swt.graphics.Point;
35 import org.eclipse.swt.graphics.RGB;
36 import org.eclipse.swt.graphics.Rectangle;
37 import org.eclipse.swt.widgets.Canvas;
38 import org.eclipse.swt.widgets.Control;
39 import org.eclipse.swt.widgets.Display;
40 import org.eclipse.swt.widgets.Event;
41 import org.eclipse.swt.widgets.Listener;
42 import org.eclipse.swt.widgets.Shell;
43
44 import org.eclipse.core.runtime.Assert;
45 import org.eclipse.core.runtime.ListenerList;
46 import org.eclipse.core.runtime.Platform;
47
48 import org.eclipse.jface.internal.text.html.BrowserInformationControl;
49 import org.eclipse.jface.internal.text.html.HTMLPrinter;
50 import org.eclipse.jface.internal.text.html.HTMLTextPresenter;
51
52 import org.eclipse.jface.text.AbstractReusableInformationControlCreator;
53 import org.eclipse.jface.text.BadLocationException;
54 import org.eclipse.jface.text.DefaultInformationControl;
55 import org.eclipse.jface.text.IDocument;
56 import org.eclipse.jface.text.IInformationControl;
57 import org.eclipse.jface.text.IInformationControlCreator;
58 import org.eclipse.jface.text.IRegion;
59 import org.eclipse.jface.text.ITextViewer;
60 import org.eclipse.jface.text.ITextViewerExtension5;
61 import org.eclipse.jface.text.JFaceTextUtil;
62 import org.eclipse.jface.text.Position;
63 import org.eclipse.jface.text.Region;
64 import org.eclipse.jface.text.information.IInformationProviderExtension2;
65 import org.eclipse.jface.text.revisions.IRevisionListener;
66 import org.eclipse.jface.text.revisions.IRevisionRulerColumnExtension;
67 import org.eclipse.jface.text.revisions.Revision;
68 import org.eclipse.jface.text.revisions.RevisionEvent;
69 import org.eclipse.jface.text.revisions.RevisionInformation;
70 import org.eclipse.jface.text.revisions.RevisionRange;
71 import org.eclipse.jface.text.revisions.IRevisionRulerColumnExtension.RenderingMode;
72 import org.eclipse.jface.text.source.Annotation;
73 import org.eclipse.jface.text.source.CompositeRuler;
74 import org.eclipse.jface.text.source.IAnnotationHover;
75 import org.eclipse.jface.text.source.IAnnotationHoverExtension;
76 import org.eclipse.jface.text.source.IAnnotationHoverExtension2;
77 import org.eclipse.jface.text.source.IAnnotationModel;
78 import org.eclipse.jface.text.source.IAnnotationModelExtension;
79 import org.eclipse.jface.text.source.IAnnotationModelListener;
80 import org.eclipse.jface.text.source.IChangeRulerColumn;
81 import org.eclipse.jface.text.source.ILineDiffer;
82 import org.eclipse.jface.text.source.ILineRange;
83 import org.eclipse.jface.text.source.ISharedTextColors;
84 import org.eclipse.jface.text.source.ISourceViewer;
85 import org.eclipse.jface.text.source.IVerticalRulerColumn;
86 import org.eclipse.jface.text.source.LineRange;
87
88
89 /**
90  * A strategy for painting the live annotate colors onto the vertical ruler column. It also manages
91  * the revision hover.
92  *
93  * @since 3.2
94  */

95 public final class RevisionPainter {
96     /** Tells whether this class is in debug mode. */
97     private static boolean DEBUG= "true".equalsIgnoreCase(Platform.getDebugOption("org.eclipse.jface.text.source/debug/RevisionRulerColumn")); //$NON-NLS-1$//$NON-NLS-2$
98

99     // RGBs provided by UI Designer
100
private static final RGB BY_DATE_START_COLOR= new RGB(199, 134, 57);
101     private static final RGB BY_DATE_END_COLOR= new RGB(241, 225, 206);
102
103     
104     /**
105      * The annotations created to show a revision in the overview ruler.
106      */

107     private static final class RevisionAnnotation extends Annotation {
108         public RevisionAnnotation(String JavaDoc text) {
109             super("org.eclipse.ui.workbench.texteditor.revisionAnnotation", false, text); //$NON-NLS-1$
110
}
111     }
112
113     /**
114      * The color tool manages revision colors and computes shaded colors based on the relative age
115      * and author of a revision.
116      */

117     private final class ColorTool {
118         /**
119          * The average perceived intensity of a base color. 0 means black, 1 means white. A base
120          * revision color perceived as light such as yellow will be darkened, while colors perceived
121          * as dark such as blue will be lightened up.
122          */

123         private static final float AVERAGE_INTENSITY= 0.5f;
124         /**
125          * The maximum shading in [0, 1] - this is the shade that the most recent revision will
126          * receive.
127          */

128         private static final float MAX_SHADING= 0.7f;
129         /**
130          * The minimum shading in [0, 1] - this is the shade that the oldest revision will receive.
131          */

132         private static final float MIN_SHADING= 0.2f;
133         /**
134          * The shade for the focus boxes.
135          */

136         private static final float FOCUS_COLOR_SHADING= 1f;
137
138
139         /**
140          * A list of {@link Long}, storing the age of each revision in a sorted list.
141          */

142         private List JavaDoc fRevisions;
143         /**
144          * The stored shaded colors.
145          */

146         private final Map JavaDoc fColors= new HashMap JavaDoc();
147         /**
148          * The stored focus colors.
149          */

150         private final Map JavaDoc fFocusColors= new HashMap JavaDoc();
151
152         /**
153          * Sets the revision information, which is needed to compute the relative age of a revision.
154          *
155          * @param info the new revision info, <code>null</code> for none.
156          */

157         public void setInfo(RevisionInformation info) {
158             fRevisions= null;
159             fColors.clear();
160             fFocusColors.clear();
161             
162             if (info == null)
163                 return;
164             List JavaDoc revisions= new ArrayList JavaDoc();
165             for (Iterator JavaDoc it= info.getRevisions().iterator(); it.hasNext();) {
166                 Revision revision= (Revision) it.next();
167                 revisions.add(new Long JavaDoc(computeAge(revision)));
168             }
169             Collections.sort(revisions);
170             fRevisions= revisions;
171         }
172
173         private RGB adaptColor(Revision revision, boolean focus) {
174             RGB rgb;
175             float scale;
176             if (fRenderingMode == IRevisionRulerColumnExtension.AGE) {
177                 int index= computeAgeIndex(revision);
178                 if (index == -1 || fRevisions.size() == 0) {
179                     rgb= getBackground().getRGB();
180                 } else {
181                     // gradient from intense red for most recent to faint yellow for oldest
182
RGB[] gradient= Colors.palette(BY_DATE_START_COLOR, BY_DATE_END_COLOR, fRevisions.size());
183                     rgb= gradient[gradient.length - index - 1];
184                 }
185                 scale= 0.99f;
186             } else if (fRenderingMode == IRevisionRulerColumnExtension.AUTHOR) {
187                 rgb= revision.getColor();
188                 rgb= Colors.adjustBrightness(rgb, AVERAGE_INTENSITY);
189                 scale= 0.6f;
190             } else if (fRenderingMode == IRevisionRulerColumnExtension.AUTHOR_SHADED_BY_AGE) {
191                 rgb= revision.getColor();
192                 rgb= Colors.adjustBrightness(rgb, AVERAGE_INTENSITY);
193                 int index= computeAgeIndex(revision);
194                 int size= fRevisions.size();
195                 // relative age: newest is 0, oldest is 1
196
// if there is only one revision, use an intermediate value to avoid extreme coloring
197
if (index == -1 || size < 2)
198                     scale= 0.5f;
199                 else
200                     scale= (float) index / (size - 1);
201             } else {
202                 Assert.isTrue(false);
203                 return null; // dummy
204
}
205             rgb= getShadedColor(rgb, scale, focus);
206             return rgb;
207         }
208
209         private int computeAgeIndex(Revision revision) {
210             long age= computeAge(revision);
211             int index= fRevisions.indexOf(new Long JavaDoc(age));
212             return index;
213         }
214         
215         private RGB getShadedColor(RGB color, float scale, boolean focus) {
216             Assert.isLegal(scale >= 0.0);
217             Assert.isLegal(scale <= 1.0);
218             RGB background= getBackground().getRGB();
219
220             // normalize to lie within [MIN_SHADING, MAX_SHADING]
221
// use more intense colors if the ruler is narrow (i.e. not showing line numbers)
222
boolean makeIntense= getWidth() <= 15;
223             float intensityShift= makeIntense ? 0.3f : 0f;
224             float max= MAX_SHADING + intensityShift;
225             float min= MIN_SHADING + intensityShift;
226             scale= (max - min) * scale + min;
227
228             // focus coloring
229
if (focus) {
230                 scale += FOCUS_COLOR_SHADING;
231                 if (scale > 1) {
232                     background= new RGB(255 - background.red, 255 - background.green, 255 - background.blue);
233                     scale= 2 - scale;
234                 }
235             }
236
237             return Colors.blend(background, color, scale);
238         }
239
240         private long computeAge(Revision revision) {
241             return revision.getDate().getTime();
242         }
243
244         /**
245          * Returns the color for a revision based on relative age and author.
246          *
247          * @param revision the revision
248          * @param focus <code>true</code> to return the focus color
249          * @return the color for a revision
250          */

251         public RGB getColor(Revision revision, boolean focus) {
252             Map JavaDoc map= focus ? fFocusColors : fColors;
253             RGB color= (RGB) map.get(revision);
254             if (color != null)
255                 return color;
256             
257             color= adaptColor(revision, focus);
258             map.put(revision, color);
259             return color;
260         }
261     }
262
263     /**
264      * Handles all the mouse interaction in this line number ruler column.
265      */

266     private class MouseHandler implements MouseListener, MouseMoveListener, MouseTrackListener, Listener {
267
268         private RevisionRange fMouseDownRegion;
269         
270         /*
271          * @see org.eclipse.swt.events.MouseListener#mouseUp(org.eclipse.swt.events.MouseEvent)
272          */

273         public void mouseUp(MouseEvent e) {
274             if (e.button == 1) {
275                 RevisionRange upRegion= fFocusRange;
276                 RevisionRange downRegion= fMouseDownRegion;
277                 fMouseDownRegion= null;
278
279                 if (upRegion == downRegion) {
280                     Revision revision= upRegion == null ? null : upRegion.getRevision();
281                     if (revision == fSelectedRevision)
282                         revision= null; // deselect already selected revision
283
handleRevisionSelected(revision);
284                 }
285             }
286         }
287
288         /*
289          * @see org.eclipse.swt.events.MouseListener#mouseDown(org.eclipse.swt.events.MouseEvent)
290          */

291         public void mouseDown(MouseEvent e) {
292             if (e.button == 3)
293                 updateFocusRevision(null); // kill any focus as the ctx menu is going to show
294
if (e.button == 1) {
295                 fMouseDownRegion= fFocusRange;
296                 postRedraw();
297             }
298         }
299
300         /*
301          * @see org.eclipse.swt.events.MouseListener#mouseDoubleClick(org.eclipse.swt.events.MouseEvent)
302          */

303         public void mouseDoubleClick(MouseEvent e) {
304         }
305
306         /*
307          * @see org.eclipse.swt.widgets.Listener#handleEvent(org.eclipse.swt.widgets.Event)
308          */

309         public void handleEvent(Event event) {
310             Assert.isTrue(event.type == SWT.MouseWheel);
311             handleMouseWheel(event);
312         }
313
314         /*
315          * @see org.eclipse.swt.events.MouseTrackListener#mouseEnter(org.eclipse.swt.events.MouseEvent)
316          */

317         public void mouseEnter(MouseEvent e) {
318             updateFocusLine(toDocumentLineNumber(e.y));
319         }
320
321         /*
322          * @see org.eclipse.swt.events.MouseTrackListener#mouseExit(org.eclipse.swt.events.MouseEvent)
323          */

324         public void mouseExit(MouseEvent e) {
325             updateFocusLine(-1);
326         }
327
328         /*
329          * @see org.eclipse.swt.events.MouseTrackListener#mouseHover(org.eclipse.swt.events.MouseEvent)
330          */

331         public void mouseHover(MouseEvent e) {
332         }
333
334         /*
335          * @see org.eclipse.swt.events.MouseMoveListener#mouseMove(org.eclipse.swt.events.MouseEvent)
336          */

337         public void mouseMove(MouseEvent e) {
338             updateFocusLine(toDocumentLineNumber(e.y));
339         }
340     }
341
342     /**
343      * Internal listener class that will update the ruler when the underlying model changes.
344      */

345     private class AnnotationListener implements IAnnotationModelListener {
346         /*
347          * @see org.eclipse.jface.text.source.IAnnotationModelListener#modelChanged(org.eclipse.jface.text.source.IAnnotationModel)
348          */

349         public void modelChanged(IAnnotationModel model) {
350             clearRangeCache();
351             postRedraw();
352         }
353
354     }
355
356     /**
357      * The information control creator.
358      */

359     private static final class HoverInformationControlCreator extends AbstractReusableInformationControlCreator {
360         private boolean fIsFocusable;
361         
362         public HoverInformationControlCreator(boolean isFocusable) {
363             fIsFocusable= isFocusable;
364         }
365
366         /*
367          * @see org.eclipse.jface.internal.text.revisions.AbstractReusableInformationControlCreator#doCreateInformationControl(org.eclipse.swt.widgets.Shell)
368          */

369         protected IInformationControl doCreateInformationControl(Shell parent) {
370             int style= fIsFocusable ? SWT.V_SCROLL | SWT.H_SCROLL : SWT.NONE;
371             
372             if (BrowserInformationControl.isAvailable(parent)) {
373                 final int shellStyle= SWT.TOOL | (fIsFocusable ? SWT.RESIZE : SWT.NO_TRIM);
374                 return new BrowserInformationControl(parent, shellStyle, style) {
375                     /*
376                      * @see org.eclipse.jface.internal.text.html.BrowserInformationControl#setInformation(java.lang.String)
377                      * @since 3.3
378                      */

379                     public void setInformation(String JavaDoc content) {
380                         content= addCSSToHTMLFragment(content);
381                         super.setInformation(content);
382                     }
383                     
384                     /**
385                      * Adds a HTML header and CSS info if <code>html</code> is only an HTML fragment (has no
386                      * &lt;html&gt; section).
387                      *
388                      * @param html the html / text produced by a revision
389                      * @return modified html
390                      */

391                     private String JavaDoc addCSSToHTMLFragment(String JavaDoc html) {
392                         int max= Math.min(100, html.length());
393                         if (html.substring(0, max).indexOf("<html>") != -1) //$NON-NLS-1$
394
// there is already a header
395
return html;
396                         
397                         StringBuffer JavaDoc info= new StringBuffer JavaDoc(512 + html.length());
398                         HTMLPrinter.insertPageProlog(info, 0, fgStyleSheet);
399                         info.append(html);
400                         HTMLPrinter.addPageEpilog(info);
401                         return info.toString();
402                     }
403                     
404                 };
405             }
406             return new DefaultInformationControl(parent, style, new HTMLTextPresenter());
407         }
408     }
409
410     private static final String JavaDoc fgStyleSheet= "/* Font definitions */\n" + //$NON-NLS-1$
411
"body, h1, h2, h3, h4, h5, h6, p, table, td, caption, th, ul, ol, dl, li, dd, dt {font-family: sans-serif; font-size: 9pt }\n" + //$NON-NLS-1$
412
"pre { font-family: monospace; font-size: 9pt }\n" + //$NON-NLS-1$
413
"\n" + //$NON-NLS-1$
414
"/* Margins */\n" + //$NON-NLS-1$
415
"body { overflow: auto; margin-top: 0; margin-bottom: 4; margin-left: 3; margin-right: 0 }\n" + //$NON-NLS-1$
416
"h1 { margin-top: 5; margin-bottom: 1 } \n" + //$NON-NLS-1$
417
"h2 { margin-top: 25; margin-bottom: 3 }\n" + //$NON-NLS-1$
418
"h3 { margin-top: 20; margin-bottom: 3 }\n" + //$NON-NLS-1$
419
"h4 { margin-top: 20; margin-bottom: 3 }\n" + //$NON-NLS-1$
420
"h5 { margin-top: 0; margin-bottom: 0 }\n" + //$NON-NLS-1$
421
"p { margin-top: 10px; margin-bottom: 10px }\n" + //$NON-NLS-1$
422
"pre { margin-left: 6 }\n" + //$NON-NLS-1$
423
"ul { margin-top: 0; margin-bottom: 10 }\n" + //$NON-NLS-1$
424
"li { margin-top: 0; margin-bottom: 0 } \n" + //$NON-NLS-1$
425
"li p { margin-top: 0; margin-bottom: 0 } \n" + //$NON-NLS-1$
426
"ol { margin-top: 0; margin-bottom: 10 }\n" + //$NON-NLS-1$
427
"dl { margin-top: 0; margin-bottom: 10 }\n" + //$NON-NLS-1$
428
"dt { margin-top: 0; margin-bottom: 0; font-weight: bold }\n" + //$NON-NLS-1$
429
"dd { margin-top: 0; margin-bottom: 0 }\n" + //$NON-NLS-1$
430
"\n" + //$NON-NLS-1$
431
"/* Styles and colors */\n" + //$NON-NLS-1$
432
"a:link { color: #0000FF }\n" + //$NON-NLS-1$
433
"a:hover { color: #000080 }\n" + //$NON-NLS-1$
434
"a:visited { text-decoration: underline }\n" + //$NON-NLS-1$
435
"h4 { font-style: italic }\n" + //$NON-NLS-1$
436
"strong { font-weight: bold }\n" + //$NON-NLS-1$
437
"em { font-style: italic }\n" + //$NON-NLS-1$
438
"var { font-style: italic }\n" + //$NON-NLS-1$
439
"th { font-weight: bold }\n" + //$NON-NLS-1$
440
""; //$NON-NLS-1$
441

442     /**
443      * The revision hover displays information about the currently selected revision.
444      */

445     private final class RevisionHover implements IAnnotationHover, IAnnotationHoverExtension, IAnnotationHoverExtension2, IInformationProviderExtension2 {
446         
447         /*
448          * @see org.eclipse.jface.text.source.IAnnotationHover#getHoverInfo(org.eclipse.jface.text.source.ISourceViewer,
449          * int)
450          */

451         public String JavaDoc getHoverInfo(ISourceViewer sourceViewer, int lineNumber) {
452             Object JavaDoc info= getHoverInfo(sourceViewer, getHoverLineRange(sourceViewer, lineNumber), 0);
453             return info == null ? null : info.toString();
454         }
455
456         /*
457          * @see org.eclipse.jface.text.source.IAnnotationHoverExtension#getHoverControlCreator()
458          */

459         public IInformationControlCreator getHoverControlCreator() {
460             RevisionInformation revisionInfo= fRevisionInfo;
461             if (revisionInfo != null) {
462                 IInformationControlCreator creator= revisionInfo.getHoverControlCreator();
463                 if (creator != null)
464                     return creator;
465             }
466             return new HoverInformationControlCreator(false);
467         }
468
469         /*
470          * @see org.eclipse.jface.text.source.IAnnotationHoverExtension#canHandleMouseCursor()
471          */

472         public boolean canHandleMouseCursor() {
473             return false;
474         }
475
476         /*
477          * @see org.eclipse.jface.text.source.IAnnotationHoverExtension2#canHandleMouseWheel()
478          */

479         public boolean canHandleMouseWheel() {
480             return true;
481         }
482
483         /*
484          * @see org.eclipse.jface.text.source.IAnnotationHoverExtension#getHoverInfo(org.eclipse.jface.text.source.ISourceViewer,
485          * org.eclipse.jface.text.source.ILineRange, int)
486          */

487         public Object JavaDoc getHoverInfo(ISourceViewer sourceViewer, ILineRange lineRange, int visibleNumberOfLines) {
488             RevisionRange range= getRange(lineRange.getStartLine());
489             Object JavaDoc info= range == null ? null : range.getRevision().getHoverInfo();
490             return info;
491         }
492
493         /*
494          * @see org.eclipse.jface.text.source.IAnnotationHoverExtension#getHoverLineRange(org.eclipse.jface.text.source.ISourceViewer,
495          * int)
496          */

497         public ILineRange getHoverLineRange(ISourceViewer viewer, int lineNumber) {
498             RevisionRange range= getRange(lineNumber);
499             return range == null ? null : new LineRange(lineNumber, 1);
500         }
501
502         /*
503          * @see org.eclipse.jface.text.information.IInformationProviderExtension2#getInformationPresenterControlCreator()
504          */

505         public IInformationControlCreator getInformationPresenterControlCreator() {
506             RevisionInformation revisionInfo= fRevisionInfo;
507             if (revisionInfo != null) {
508                 IInformationControlCreator creator= revisionInfo.getInformationPresenterControlCreator();
509                 if (creator != null)
510                     return creator;
511             }
512             return new HoverInformationControlCreator(true);
513         }
514     }
515
516     /* Listeners and helpers. */
517
518     /** The shared color provider. */
519     private final ISharedTextColors fSharedColors;
520     /** The color tool. */
521     private final ColorTool fColorTool= new ColorTool();
522     /** The mouse handler. */
523     private final MouseHandler fMouseHandler= new MouseHandler();
524     /** The hover. */
525     private final RevisionHover fHover= new RevisionHover();
526     /** The annotation listener. */
527     private final AnnotationListener fAnnotationListener= new AnnotationListener();
528     /** The selection provider. */
529     private final RevisionSelectionProvider fRevisionSelectionProvider= new RevisionSelectionProvider(this);
530     /**
531      * The list of revision listeners.
532      * @since 3.3.
533      */

534     private final ListenerList fRevisionListeners= new ListenerList();
535
536     /* The context - column and viewer we are connected to. */
537
538     /** The vertical ruler column that delegates painting to this painter. */
539     private final IVerticalRulerColumn fColumn;
540     /** The parent ruler. */
541     private CompositeRuler fParentRuler;
542     /** The column's control, typically a {@link Canvas}, possibly <code>null</code>. */
543     private Control fControl;
544     /** The text viewer that the column is attached to. */
545     private ITextViewer fViewer;
546     /** The viewer's text widget. */
547     private StyledText fWidget;
548
549     /* The models we operate on. */
550
551     /** The revision model object. */
552     private RevisionInformation fRevisionInfo;
553     /** The line differ. */
554     private ILineDiffer fLineDiffer= null;
555     /** The annotation model. */
556     private IAnnotationModel fAnnotationModel= null;
557     /** The background color, possibly <code>null</code>. */
558     private Color fBackground;
559
560     /* Cache. */
561
562     /** The cached list of ranges adapted to quick diff. */
563     private List JavaDoc fRevisionRanges= null;
564     /** The annotations created for the overview ruler temporary display. */
565     private List JavaDoc fAnnotations= new ArrayList JavaDoc();
566
567     /* State */
568
569     /** The current focus line, -1 for none. */
570     private int fFocusLine= -1;
571     /** The current focus region, <code>null</code> if none. */
572     private RevisionRange fFocusRange= null;
573     /** The current focus revision, <code>null</code> if none. */
574     private Revision fFocusRevision= null;
575     /**
576      * The currently selected revision, <code>null</code> if none. The difference between
577      * {@link #fFocusRevision} and {@link #fSelectedRevision} may not be obvious: the focus revision
578      * is the one focused by the mouse (by hovering over a block of the revision), while the
579      * selected revision is sticky, i.e. is not removed when the mouse leaves the ruler.
580      *
581      * @since 3.3
582      */

583     private Revision fSelectedRevision= null;
584     /** <code>true</code> if the mouse wheel handler is installed, <code>false</code> otherwise. */
585     private boolean fWheelHandlerInstalled= false;
586     /**
587      * The revision rendering mode.
588      */

589     private RenderingMode fRenderingMode= IRevisionRulerColumnExtension.AUTHOR_SHADED_BY_AGE;
590     /**
591      * The required with in characters.
592      * @since 3.3
593      */

594     private int fRequiredWidth= -1;
595     /**
596      * The width of the revision field in chars to compute {@link #fAuthorInset} from.
597      * @since 3.3
598      */

599     private int fRevisionIdChars= 0;
600     /**
601      * <code>true</code> to show revision ids, <code>false</code> otherwise.
602      * @since 3.3
603      */

604     private boolean fShowRevision= false;
605     /**
606      * <code>true</code> to show the author, <code>false</code> otherwise.
607      * @since 3.3
608      */

609     private boolean fShowAuthor= false;
610     /**
611      * The author inset in pixels for when author *and* revision id are shown.
612      * @since 3.3
613      */

614     private int fAuthorInset;
615     /**
616      * The remembered ruler width (as changing the ruler width triggers recomputation of the colors.
617      * @since 3.3
618      */

619     private int fLastWidth= -1;
620
621     /**
622      * Creates a new revision painter for a vertical ruler column.
623      *
624      * @param column the column that will delegate{@link #paint(GC, ILineRange) painting} to the
625      * newly created painter.
626      * @param sharedColors a shared colors object to store shaded colors in
627      */

628     public RevisionPainter(IVerticalRulerColumn column, ISharedTextColors sharedColors) {
629         Assert.isLegal(column != null);
630         Assert.isLegal(sharedColors != null);
631         fColumn= column;
632         fSharedColors= sharedColors;
633     }
634
635     /**
636      * Sets the revision information to be drawn and triggers a redraw.
637      *
638      * @param info the revision information to show, <code>null</code> to draw none
639      */

640     public void setRevisionInformation(RevisionInformation info) {
641         if (fRevisionInfo != info) {
642             fRequiredWidth= -1;
643             fRevisionIdChars= 0;
644             fRevisionInfo= info;
645             clearRangeCache();
646             updateFocusRange(null);
647             handleRevisionSelected((Revision) null);
648             fColorTool.setInfo(info);
649             postRedraw();
650             informListeners();
651         }
652     }
653
654     /**
655      * Changes the rendering mode and triggers redrawing if needed.
656      *
657      * @param renderingMode the rendering mode
658      * @since 3.3
659      */

660     public void setRenderingMode(RenderingMode renderingMode) {
661         Assert.isLegal(renderingMode != null);
662         if (fRenderingMode != renderingMode) {
663             fRenderingMode= renderingMode;
664             fColorTool.setInfo(fRevisionInfo);
665             postRedraw();
666         }
667     }
668
669     /**
670      * Sets the background color.
671      *
672      * @param background the background color, <code>null</code> for the platform's list
673      * background
674      */

675     public void setBackground(Color background) {
676         fBackground= background;
677     }
678
679     /**
680      * Sets the parent ruler - the delegating column must call this method as soon as it creates its
681      * control.
682      *
683      * @param parentRuler the parent ruler
684      */

685     public void setParentRuler(CompositeRuler parentRuler) {
686         fParentRuler= parentRuler;
687     }
688
689     /**
690      * Delegates the painting of the quick diff colors to this painter. The painter will draw the
691      * color boxes onto the passed {@link GC} for all model (document) lines in
692      * <code>visibleModelLines</code>.
693      *
694      * @param gc the {@link GC} to draw onto
695      * @param visibleLines the lines (in document offsets) that are currently (perhaps only
696      * partially) visible
697      */

698     public void paint(GC gc, ILineRange visibleLines) {
699         connectIfNeeded();
700         if (!isConnected())
701             return;
702
703         // compute the horizontal indent of the author for the case that we show revision
704
// and author
705
if (fShowAuthor && fShowRevision) {
706             char[] string= new char[fRevisionIdChars + 1];
707             Arrays.fill(string, '9');
708             if (string.length > 1) {
709                 string[0]= '.';
710                 string[1]= ' ';
711             }
712             fAuthorInset= gc.stringExtent(new String JavaDoc(string)).x;
713         }
714         
715         // recompute colors (show intense colors if ruler is narrow)
716
int width= getWidth();
717         if (width != fLastWidth) {
718             fColorTool.setInfo(fRevisionInfo);
719             fLastWidth= width;
720         }
721         
722         // draw change regions
723
List JavaDoc/* <RevisionRange> */ranges= getRanges(visibleLines);
724         for (Iterator JavaDoc it= ranges.iterator(); it.hasNext();) {
725             RevisionRange region= (RevisionRange) it.next();
726             paintRange(region, gc);
727         }
728     }
729
730     /**
731      * Ensures that the column is fully instantiated, i.e. has a control, and that the viewer is
732      * visible.
733      */

734     private void connectIfNeeded() {
735         if (isConnected() || fParentRuler == null)
736             return;
737
738         fViewer= fParentRuler.getTextViewer();
739         if (fViewer == null)
740             return;
741
742         fWidget= fViewer.getTextWidget();
743         if (fWidget == null)
744             return;
745
746         fControl= fColumn.getControl();
747         if (fControl == null)
748             return;
749
750         fControl.addMouseTrackListener(fMouseHandler);
751         fControl.addMouseMoveListener(fMouseHandler);
752         fControl.addMouseListener(fMouseHandler);
753         fControl.addDisposeListener(new DisposeListener() {
754             /*
755              * @see org.eclipse.swt.events.DisposeListener#widgetDisposed(org.eclipse.swt.events.DisposeEvent)
756              */

757             public void widgetDisposed(DisposeEvent e) {
758                 handleDispose();
759             }
760         });
761         
762         fRevisionSelectionProvider.install(fViewer);
763     }
764
765     /**
766      * Returns <code>true</code> if the column is fully connected.
767      *
768      * @return <code>true</code> if the column is fully connected, false otherwise
769      */

770     private boolean isConnected() {
771         return fControl != null;
772     }
773
774     /**
775      * Sets the annotation model.
776      *
777      * @param model the annotation model, possibly <code>null</code>
778      * @see IVerticalRulerColumn#setModel(IAnnotationModel)
779      */

780     public void setModel(IAnnotationModel model) {
781         IAnnotationModel diffModel;
782         if (model instanceof IAnnotationModelExtension)
783             diffModel= ((IAnnotationModelExtension) model).getAnnotationModel(IChangeRulerColumn.QUICK_DIFF_MODEL_ID);
784         else
785             diffModel= model;
786
787         setDiffer(diffModel);
788         setAnnotationModel(model);
789     }
790
791     /**
792      * Sets the annotation model.
793      *
794      * @param model the annotation model.
795      */

796     private void setAnnotationModel(IAnnotationModel model) {
797         if (fAnnotationModel != model)
798             fAnnotationModel= model;
799     }
800
801     /**
802      * Sets the line differ.
803      *
804      * @param differ the line differ or <code>null</code> if none
805      */

806     private void setDiffer(IAnnotationModel differ) {
807         if (differ instanceof ILineDiffer || differ == null) {
808             if (fLineDiffer != differ) {
809                 if (fLineDiffer != null)
810                     ((IAnnotationModel) fLineDiffer).removeAnnotationModelListener(fAnnotationListener);
811                 fLineDiffer= (ILineDiffer) differ;
812                 if (fLineDiffer != null)
813                     ((IAnnotationModel) fLineDiffer).addAnnotationModelListener(fAnnotationListener);
814             }
815         }
816     }
817
818     /**
819      * Disposes of the painter's resources.
820      */

821     private void handleDispose() {
822         updateFocusLine(-1);
823
824         if (fLineDiffer != null) {
825             ((IAnnotationModel) fLineDiffer).removeAnnotationModelListener(fAnnotationListener);
826             fLineDiffer= null;
827         }
828         
829         fRevisionSelectionProvider.uninstall();
830     }
831
832     /**
833      * Paints a single change region onto <code>gc</code>.
834      *
835      * @param range the range to paint
836      * @param gc the {@link GC} to paint on
837      */

838     private void paintRange(RevisionRange range, GC gc) {
839         ILineRange widgetRange= modelLinesToWidgetLines(range);
840         if (widgetRange == null)
841             return;
842
843         Revision revision= range.getRevision();
844         boolean drawArmedFocus= range == fMouseHandler.fMouseDownRegion;
845         boolean drawSelection= !drawArmedFocus && revision == fSelectedRevision;
846         boolean drawFocus= !drawSelection && !drawArmedFocus && revision == fFocusRevision;
847         Rectangle box= computeBoxBounds(widgetRange);
848
849         gc.setBackground(lookupColor(revision, false));
850         if (drawArmedFocus) {
851             Color foreground= gc.getForeground();
852             Color focusColor= lookupColor(revision, true);
853             gc.setForeground(focusColor);
854             gc.fillRectangle(box);
855             gc.drawRectangle(box.x, box.y, box.width - 1, box.height - 1); // highlight box
856
gc.drawRectangle(box.x + 1, box.y + 1, box.width - 3, box.height - 3); // inner highlight box
857
gc.setForeground(foreground);
858         } else if (drawFocus || drawSelection) {
859             Color foreground= gc.getForeground();
860             Color focusColor= lookupColor(revision, true);
861             gc.setForeground(focusColor);
862             gc.fillRectangle(box);
863             gc.drawRectangle(box.x, box.y, box.width - 1, box.height - 1); // highlight box
864
gc.setForeground(foreground);
865         } else {
866             gc.fillRectangle(box);
867         }
868
869         if ((fShowAuthor || fShowRevision)) {
870             int indentation= 1;
871             int baselineBias= getBaselineBias(gc, widgetRange.getStartLine());
872             if (fShowAuthor && fShowRevision) {
873                 gc.drawString(revision.getId(), indentation, box.y + baselineBias, true);
874                 gc.drawString(revision.getAuthor(), fAuthorInset, box.y + baselineBias, true);
875             } else if (fShowAuthor) {
876                 gc.drawString(revision.getAuthor(), indentation, box.y + baselineBias, true);
877             } else if (fShowRevision) {
878                 gc.drawString(revision.getId(), indentation, box.y + baselineBias, true);
879             }
880         }
881     }
882
883     /**
884      * Returns the difference between the baseline of the widget and the
885      * baseline as specified by the font for <code>gc</code>. When drawing
886      * line numbers, the returned bias should be added to obtain text lined up
887      * on the correct base line of the text widget.
888      *
889      * @param gc the <code>GC</code> to get the font metrics from
890      * @param widgetLine the widget line
891      * @return the baseline bias to use when drawing text that is lined up with
892      * <code>fCachedTextWidget</code>
893      * @since 3.3
894      */

895     private int getBaselineBias(GC gc, int widgetLine) {
896         /*
897          * https://bugs.eclipse.org/bugs/show_bug.cgi?id=62951
898          * widget line height may be more than the font height used for the
899          * line numbers, since font styles (bold, italics...) can have larger
900          * font metrics than the simple font used for the numbers.
901          */

902         int offset= fWidget.getOffsetAtLine(widgetLine);
903         int widgetBaseline= fWidget.getBaseline(offset);
904         
905         FontMetrics fm = gc.getFontMetrics();
906         int fontBaseline = fm.getAscent() + fm.getLeading();
907         int baselineBias= widgetBaseline - fontBaseline;
908         return Math.max(0, baselineBias);
909     }
910     
911     /**
912      * Looks up the color for a certain revision.
913      *
914      * @param revision the revision to get the color for
915      * @param focus <code>true</code> if it is the focus revision
916      * @return the color for the revision
917      */

918     private Color lookupColor(Revision revision, boolean focus) {
919         return fSharedColors.getColor(fColorTool.getColor(revision, focus));
920     }
921
922     /**
923      * Returns the revision range that contains the given line, or
924      * <code>null</code> if there is none.
925      *
926      * @param line the line of interest
927      * @return the corresponding <code>RevisionRange</code> or <code>null</code>
928      */

929     private RevisionRange getRange(int line) {
930         List JavaDoc ranges= getRangeCache();
931
932         if (ranges.isEmpty() || line == -1)
933             return null;
934
935         for (Iterator JavaDoc it= ranges.iterator(); it.hasNext();) {
936             RevisionRange range= (RevisionRange) it.next();
937             if (contains(range, line))
938                 return range;
939         }
940
941         // line may be right after the last region
942
RevisionRange lastRegion= (RevisionRange) ranges.get(ranges.size() - 1);
943         if (line == end(lastRegion))
944             return lastRegion;
945         return null;
946     }
947
948     /**
949      * Returns the sublist of all <code>RevisionRange</code>s that intersect with the given lines.
950      *
951      * @param lines the model based lines of interest
952      * @return elementType: RevisionRange
953      */

954     private List JavaDoc getRanges(ILineRange lines) {
955         List JavaDoc ranges= getRangeCache();
956
957         // return the interesting subset
958
int end= end(lines);
959         int first= -1, last= -1;
960         for (int i= 0; i < ranges.size(); i++) {
961             RevisionRange range= (RevisionRange) ranges.get(i);
962             int rangeEnd= end(range);
963             if (first == -1 && rangeEnd > lines.getStartLine())
964                 first= i;
965             if (first != -1 && rangeEnd > end) {
966                 last= i;
967                 break;
968             }
969         }
970         if (first == -1)
971             return Collections.EMPTY_LIST;
972         if (last == -1)
973             last= ranges.size() - 1; // bottom index may be one too much
974

975         return ranges.subList(first, last + 1);
976     }
977
978     /**
979      * Gets all change ranges of the revisions in the revision model and adapts them to the current
980      * quick diff information. The list is cached.
981      *
982      * @return the list of all change regions, with diff information applied
983      */

984     private List JavaDoc getRangeCache() {
985         if (fRevisionRanges == null) {
986             if (fRevisionInfo == null) {
987                 fRevisionRanges= Collections.EMPTY_LIST;
988             } else {
989                 Hunk[] hunks= HunkComputer.computeHunks(fLineDiffer, fViewer.getDocument().getNumberOfLines());
990                 fRevisionInfo.applyDiff(hunks);
991                 fRevisionRanges= fRevisionInfo.getRanges();
992                 updateOverviewAnnotations();
993                 informListeners();
994             }
995         }
996
997         return fRevisionRanges;
998     }
999
1000    /**
1001     * Clears the range cache.
1002     *
1003     * @since 3.3
1004     */

1005    private void clearRangeCache() {
1006        fRevisionRanges= null;
1007    }
1008    
1009    /**
1010     * Returns <code>true</code> if <code>range</code> contains <code>line</code>. A line is
1011     * not contained in a range if it is the range's exclusive end line.
1012     *
1013     * @param range the range to check whether it contains <code>line</code>
1014     * @param line the line the line
1015     * @return <code>true</code> if <code>range</code> contains <code>line</code>,
1016     * <code>false</code> if not
1017     */

1018    private static boolean contains(ILineRange range, int line) {
1019        return range.getStartLine() <= line && end(range) > line;
1020    }
1021
1022    /**
1023     * Computes the end index of a line range.
1024     *
1025     * @param range a line range
1026     * @return the last line (exclusive) of <code>range</code>
1027     */

1028    private static int end(ILineRange range) {
1029        return range.getStartLine() + range.getNumberOfLines();
1030    }
1031
1032    /**
1033     * Returns the visible extent of a document line range in widget lines.
1034     *
1035     * @param range the document line range
1036     * @return the visible extent of <code>range</code> in widget lines
1037     */

1038    private ILineRange modelLinesToWidgetLines(ILineRange range) {
1039        int widgetStartLine= -1;
1040        int widgetEndLine= -1;
1041        if (fViewer instanceof ITextViewerExtension5) {
1042            ITextViewerExtension5 extension= (ITextViewerExtension5) fViewer;
1043            int modelEndLine= end(range);
1044            for (int modelLine= range.getStartLine(); modelLine < modelEndLine; modelLine++) {
1045                int widgetLine= extension.modelLine2WidgetLine(modelLine);
1046                if (widgetLine != -1) {
1047                    if (widgetStartLine == -1)
1048                        widgetStartLine= widgetLine;
1049                    widgetEndLine= widgetLine;
1050                }
1051            }
1052        } else {
1053            IRegion region= fViewer.getVisibleRegion();
1054            IDocument document= fViewer.getDocument();
1055            try {
1056                int visibleStartLine= document.getLineOfOffset(region.getOffset());
1057                int visibleEndLine= document.getLineOfOffset(region.getOffset() + region.getLength());
1058                widgetStartLine= Math.max(0, range.getStartLine() - visibleStartLine);
1059                widgetEndLine= Math.min(visibleEndLine, end(range) - 1);
1060            } catch (BadLocationException x) {
1061                x.printStackTrace();
1062                // ignore and return null
1063
}
1064        }
1065        if (widgetStartLine == -1 || widgetEndLine == -1)
1066            return null;
1067        return new LineRange(widgetStartLine, widgetEndLine - widgetStartLine + 1);
1068    }
1069
1070    /**
1071     * Returns the revision hover.
1072     *
1073     * @return the revision hover
1074     */

1075    public IAnnotationHover getHover() {
1076        return fHover;
1077    }
1078
1079    /**
1080     * Computes and returns the bounds of the rectangle corresponding to a widget line range. The
1081     * rectangle is in pixel coordinates relative to the text widget's
1082     * {@link StyledText#getClientArea() client area} and has the width of the ruler.
1083     *
1084     * @param range the widget line range
1085     * @return the box bounds corresponding to <code>range</code>
1086     */

1087    private Rectangle computeBoxBounds(ILineRange range) {
1088        int y1= fWidget.getLinePixel(range.getStartLine());
1089        int y2= fWidget.getLinePixel(range.getStartLine() + range.getNumberOfLines());
1090
1091        return new Rectangle(0, y1, getWidth(), y2 - y1 - 1);
1092    }
1093
1094    /**
1095     * Shows (or hides) the overview annotations.
1096     */

1097    private void updateOverviewAnnotations() {
1098        if (fAnnotationModel == null)
1099            return;
1100        
1101        Revision revision= fFocusRevision != null ? fFocusRevision : fSelectedRevision;
1102
1103        Map JavaDoc added= null;
1104        if (revision != null) {
1105            added= new HashMap JavaDoc();
1106            for (Iterator JavaDoc it= revision.getRegions().iterator(); it.hasNext();) {
1107                RevisionRange range= (RevisionRange) it.next();
1108                try {
1109                    IRegion charRegion= toCharRegion(range);
1110                    Position position= new Position(charRegion.getOffset(), charRegion.getLength());
1111                    Annotation annotation= new RevisionAnnotation(revision.getId());
1112                    added.put(annotation, position);
1113                } catch (BadLocationException x) {
1114                    // ignore - document was changed, show no annotations
1115
}
1116            }
1117        }
1118
1119        if (fAnnotationModel instanceof IAnnotationModelExtension) {
1120            IAnnotationModelExtension ext= (IAnnotationModelExtension) fAnnotationModel;
1121            ext.replaceAnnotations((Annotation[]) fAnnotations.toArray(new Annotation[fAnnotations.size()]), added);
1122        } else {
1123            for (Iterator JavaDoc it= fAnnotations.iterator(); it.hasNext();) {
1124                Annotation annotation= (Annotation) it.next();
1125                fAnnotationModel.removeAnnotation(annotation);
1126            }
1127            if (added != null) {
1128                for (Iterator JavaDoc it= added.entrySet().iterator(); it.hasNext();) {
1129                    Entry entry= (Entry) it.next();
1130                    fAnnotationModel.addAnnotation((Annotation) entry.getKey(), (Position) entry.getValue());
1131                }
1132            }
1133        }
1134        fAnnotations.clear();
1135        if (added != null)
1136            fAnnotations.addAll(added.keySet());
1137
1138    }
1139
1140    /**
1141     * Returns the character offset based region of a line range.
1142     *
1143     * @param lines the line range to convert
1144     * @return the character offset range corresponding to <code>range</code>
1145     * @throws BadLocationException if the line range is not within the document bounds
1146     */

1147    private IRegion toCharRegion(ILineRange lines) throws BadLocationException {
1148        IDocument document= fViewer.getDocument();
1149        int offset= document.getLineOffset(lines.getStartLine());
1150        int nextLine= end(lines);
1151        int endOffset;
1152        if (nextLine >= document.getNumberOfLines())
1153            endOffset= document.getLength();
1154        else
1155            endOffset= document.getLineOffset(nextLine);
1156        return new Region(offset, endOffset - offset);
1157    }
1158
1159    /**
1160     * Handles the selection of a revision and informs listeners.
1161     *
1162     * @param revision the selected revision, <code>null</code> for none
1163     */

1164    void handleRevisionSelected(Revision revision) {
1165        fSelectedRevision= revision;
1166        fRevisionSelectionProvider.revisionSelected(revision);
1167        updateOverviewAnnotations();
1168        postRedraw();
1169    }
1170
1171    /**
1172     * Handles the selection of a revision id and informs listeners
1173     *
1174     * @param id the selected revision id
1175     */

1176    void handleRevisionSelected(String JavaDoc id) {
1177        Assert.isLegal(id != null);
1178        if (fRevisionInfo == null)
1179            return;
1180
1181        for (Iterator JavaDoc it= fRevisionInfo.getRevisions().iterator(); it.hasNext();) {
1182            Revision revision= (Revision) it.next();
1183            if (id.equals(revision.getId())) {
1184                handleRevisionSelected(revision);
1185                return;
1186            }
1187        }
1188
1189        // clear selection if it does not exist
1190
handleRevisionSelected((Revision) null);
1191    }
1192
1193    /**
1194     * Returns the selection provider.
1195     *
1196     * @return the selection provider
1197     */

1198    public RevisionSelectionProvider getRevisionSelectionProvider() {
1199        return fRevisionSelectionProvider;
1200    }
1201
1202    /**
1203     * Updates the focus line with a new line.
1204     *
1205     * @param line the new focus line, -1 for no focus
1206     */

1207    private void updateFocusLine(int line) {
1208        if (fFocusLine != line)
1209            onFocusLineChanged(fFocusLine, line);
1210    }
1211
1212    /**
1213     * Handles a changing focus line.
1214     *
1215     * @param previousLine the old focus line (-1 for no focus)
1216     * @param nextLine the new focus line (-1 for no focus)
1217     */

1218    private void onFocusLineChanged(int previousLine, int nextLine) {
1219        if (DEBUG)
1220            System.out.println("line: " + previousLine + " > " + nextLine); //$NON-NLS-1$ //$NON-NLS-2$
1221
fFocusLine= nextLine;
1222        RevisionRange region= getRange(nextLine);
1223        updateFocusRange(region);
1224    }
1225
1226    /**
1227     * Updates the focus range.
1228     *
1229     * @param range the new focus range, <code>null</code> for no focus
1230     */

1231    private void updateFocusRange(RevisionRange range) {
1232        if (range != fFocusRange)
1233            onFocusRangeChanged(fFocusRange, range);
1234    }
1235
1236    /**
1237     * Handles a changing focus range.
1238     *
1239     * @param previousRange the old focus range (<code>null</code> for no focus)
1240     * @param nextRange the new focus range (<code>null</code> for no focus)
1241     */

1242    private void onFocusRangeChanged(RevisionRange previousRange, RevisionRange nextRange) {
1243        if (DEBUG)
1244            System.out.println("range: " + previousRange + " > " + nextRange); //$NON-NLS-1$ //$NON-NLS-2$
1245
fFocusRange= nextRange;
1246        Revision revision= nextRange == null ? null : nextRange.getRevision();
1247        updateFocusRevision(revision);
1248    }
1249
1250    private void updateFocusRevision(Revision revision) {
1251        if (fFocusRevision != revision)
1252            onFocusRevisionChanged(fFocusRevision, revision);
1253    }
1254
1255    /**
1256     * Handles a changing focus revision.
1257     *
1258     * @param previousRevision the old focus revision (<code>null</code> for no focus)
1259     * @param nextRevision the new focus revision (<code>null</code> for no focus)
1260     */

1261    private void onFocusRevisionChanged(Revision previousRevision, Revision nextRevision) {
1262        if (DEBUG)
1263            System.out.println("revision: " + previousRevision + " > " + nextRevision); //$NON-NLS-1$ //$NON-NLS-2$
1264
fFocusRevision= nextRevision;
1265        uninstallWheelHandler();
1266        installWheelHandler();
1267        updateOverviewAnnotations();
1268        redraw(); // pick up new highlights
1269
}
1270
1271    /**
1272     * Uninstalls the mouse wheel handler.
1273     */

1274    private void uninstallWheelHandler() {
1275        fControl.removeListener(SWT.MouseWheel, fMouseHandler);
1276        fWheelHandlerInstalled= false;
1277    }
1278
1279    /**
1280     * Installs the mouse wheel handler.
1281     */

1282    private void installWheelHandler() {
1283        if (fFocusRevision != null && !fWheelHandlerInstalled) {
1284            fControl.addListener(SWT.MouseWheel, fMouseHandler);
1285            fWheelHandlerInstalled= true;
1286        }
1287    }
1288
1289    /**
1290     * Handles a mouse wheel event.
1291     *
1292     * @param event the mouse wheel event
1293     */

1294    private void handleMouseWheel(Event event) {
1295        boolean up= event.count > 0;
1296        int documentHoverLine= fFocusLine;
1297
1298        ILineRange nextWidgetRange= null;
1299        ILineRange last= null;
1300        List JavaDoc ranges= fFocusRevision.getRegions();
1301        if (up) {
1302            for (Iterator JavaDoc it= ranges.iterator(); it.hasNext();) {
1303                RevisionRange range= (RevisionRange) it.next();
1304                ILineRange widgetRange= modelLinesToWidgetLines(range);
1305                if (contains(range, documentHoverLine)) {
1306                    nextWidgetRange= last;
1307                    break;
1308                }
1309                if (widgetRange != null)
1310                    last= widgetRange;
1311            }
1312        } else {
1313            for (ListIterator JavaDoc it= ranges.listIterator(ranges.size()); it.hasPrevious();) {
1314                RevisionRange range= (RevisionRange) it.previous();
1315                ILineRange widgetRange= modelLinesToWidgetLines(range);
1316                if (contains(range, documentHoverLine)) {
1317                    nextWidgetRange= last;
1318                    break;
1319                }
1320                if (widgetRange != null)
1321                    last= widgetRange;
1322            }
1323        }
1324
1325        if (nextWidgetRange == null)
1326            return;
1327
1328        int widgetCurrentFocusLine= modelLinesToWidgetLines(new LineRange(documentHoverLine, 1)).getStartLine();
1329        int widgetNextFocusLine= nextWidgetRange.getStartLine();
1330        int newTopPixel= fWidget.getTopPixel() + JFaceTextUtil.computeLineHeight(fWidget, widgetCurrentFocusLine, widgetNextFocusLine, widgetNextFocusLine - widgetCurrentFocusLine);
1331        fWidget.setTopPixel(newTopPixel);
1332        if (newTopPixel < 0) {
1333            Point cursorLocation= fWidget.getDisplay().getCursorLocation();
1334            cursorLocation.y+= newTopPixel;
1335            fWidget.getDisplay().setCursorLocation(cursorLocation);
1336        } else {
1337            int topPixel= fWidget.getTopPixel();
1338            if (topPixel < newTopPixel) {
1339                Point cursorLocation= fWidget.getDisplay().getCursorLocation();
1340                cursorLocation.y+= newTopPixel - topPixel;
1341                fWidget.getDisplay().setCursorLocation(cursorLocation);
1342            }
1343        }
1344        updateFocusLine(toDocumentLineNumber(fWidget.toControl(fWidget.getDisplay().getCursorLocation()).y));
1345        immediateUpdate();
1346    }
1347
1348    /**
1349     * Triggers a redraw in the display thread.
1350     */

1351    private final void postRedraw() {
1352        if (isConnected() && !fControl.isDisposed()) {
1353            Display d= fControl.getDisplay();
1354            if (d != null) {
1355                d.asyncExec(new Runnable JavaDoc() {
1356                    public void run() {
1357                        redraw();
1358                    }
1359                });
1360            }
1361        }
1362    }
1363
1364    /**
1365     * Translates a y coordinate in the pixel coordinates of the column's control to a document line
1366     * number.
1367     *
1368     * @param y the y coordinate
1369     * @return the corresponding document line, -1 for no line
1370     * @see CompositeRuler#toDocumentLineNumber(int)
1371     */

1372    private int toDocumentLineNumber(int y) {
1373        return fParentRuler.toDocumentLineNumber(y);
1374    }
1375
1376    /**
1377     * Triggers redrawing of the column.
1378     */

1379    private void redraw() {
1380        fColumn.redraw();
1381    }
1382
1383    /**
1384     * Triggers immediate redrawing of the entire column - use with care.
1385     */

1386    private void immediateUpdate() {
1387        fParentRuler.immediateUpdate();
1388    }
1389
1390    /**
1391     * Returns the width of the column.
1392     *
1393     * @return the width of the column
1394     */

1395    private int getWidth() {
1396        return fColumn.getWidth();
1397    }
1398
1399    /**
1400     * Returns the System background color for list widgets.
1401     *
1402     * @return the System background color for list widgets
1403     */

1404    private Color getBackground() {
1405        if (fBackground == null)
1406            return fWidget.getDisplay().getSystemColor(SWT.COLOR_LIST_BACKGROUND);
1407        return fBackground;
1408    }
1409
1410    /**
1411     * Sets the hover later returned by {@link #getHover()}.
1412     *
1413     * @param hover the hover
1414     */

1415    public void setHover(IAnnotationHover hover) {
1416        // TODO ignore for now - must make revision hover settable from outside
1417
}
1418
1419    /**
1420     * Returns <code>true</code> if the receiver can provide a hover for a certain document line.
1421     *
1422     * @param activeLine the document line of interest
1423     * @return <code>true</code> if the receiver can provide a hover
1424     */

1425    public boolean hasHover(int activeLine) {
1426        return fViewer instanceof ISourceViewer && fHover.getHoverLineRange((ISourceViewer) fViewer, activeLine) != null;
1427    }
1428
1429    /**
1430     * Returns the revision at a certain document offset, or <code>null</code> for none.
1431     *
1432     * @param offset the document offset
1433     * @return the revision at offset, or <code>null</code> for none
1434     */

1435    Revision getRevision(int offset) {
1436        IDocument document= fViewer.getDocument();
1437        int line;
1438        try {
1439            line= document.getLineOfOffset(offset);
1440        } catch (BadLocationException x) {
1441            return null;
1442        }
1443        if (line != -1) {
1444            RevisionRange range= getRange(line);
1445            if (range != null)
1446                return range.getRevision();
1447        }
1448        return null;
1449    }
1450
1451    /**
1452     * Returns <code>true</code> if a revision model has been set, <code>false</code> otherwise.
1453     *
1454     * @return <code>true</code> if a revision model has been set, <code>false</code> otherwise
1455     */

1456    public boolean hasInformation() {
1457        return fRevisionInfo != null;
1458    }
1459
1460    /**
1461     * Returns the width in chars required to display information.
1462     *
1463     * @return the width in chars required to display information
1464     * @since 3.3
1465     */

1466    public int getRequiredWidth() {
1467        if (fRequiredWidth == -1) {
1468            if (hasInformation() && (fShowRevision || fShowAuthor)) {
1469                int revisionWidth= 0;
1470                int authorWidth= 0;
1471                for (Iterator JavaDoc it= fRevisionInfo.getRevisions().iterator(); it.hasNext();) {
1472                    Revision revision= (Revision) it.next();
1473                    revisionWidth= Math.max(revisionWidth, revision.getId().length());
1474                    authorWidth= Math.max(authorWidth, revision.getAuthor().length());
1475                }
1476                fRevisionIdChars= revisionWidth + 1;
1477                if (fShowAuthor && fShowRevision)
1478                    fRequiredWidth= revisionWidth + authorWidth + 2;
1479                else if (fShowAuthor)
1480                    fRequiredWidth= authorWidth + 1;
1481                else
1482                    fRequiredWidth= revisionWidth + 1;
1483            } else {
1484                fRequiredWidth= 0;
1485            }
1486        }
1487        return fRequiredWidth;
1488    }
1489
1490    /**
1491     * Enables showing the revision id.
1492     *
1493     * @param show <code>true</code> to show the revision, <code>false</code> to hide it
1494     */

1495    public void showRevisionId(boolean show) {
1496        if (fShowRevision != show) {
1497            fRequiredWidth= -1;
1498            fRevisionIdChars= 0;
1499            fShowRevision= show;
1500            postRedraw();
1501        }
1502    }
1503    
1504    /**
1505     * Enables showing the revision author.
1506     *
1507     * @param show <code>true</code> to show the author, <code>false</code> to hide it
1508     */

1509    public void showRevisionAuthor(boolean show) {
1510        if (fShowAuthor != show) {
1511            fRequiredWidth= -1;
1512            fRevisionIdChars= 0;
1513            fShowAuthor= show;
1514            postRedraw();
1515        }
1516    }
1517
1518    /**
1519     * Adds a revision listener.
1520     *
1521     * @param listener the listener
1522     * @since 3.3
1523     */

1524    public void addRevisionListener(IRevisionListener listener) {
1525        fRevisionListeners.add(listener);
1526    }
1527
1528    /**
1529     * Removes a revision listener.
1530     *
1531     * @param listener the listener
1532     * @since 3.3
1533     */

1534    public void removeRevisionListener(IRevisionListener listener) {
1535        fRevisionListeners.remove(listener);
1536    }
1537
1538    /**
1539     * Informs the revision listeners about a change.
1540     *
1541     * @since 3.3
1542     */

1543    private void informListeners() {
1544        if (fRevisionInfo == null || fRevisionListeners.isEmpty())
1545            return;
1546
1547        RevisionEvent event= new RevisionEvent(fRevisionInfo);
1548        Object JavaDoc[] listeners= fRevisionListeners.getListeners();
1549        for (int i= 0; i < listeners.length; i++) {
1550            IRevisionListener listener= (IRevisionListener) listeners[i];
1551            listener.revisionInformationChanged(event);
1552        }
1553    }
1554}
Popular Tags