129
|
1 /*******************************************************************************
|
|
2 * Copyright (c) 2006, 2008 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 * Port to the D programming language:
|
|
11 * Frank Benoit <benoit@tionex.de>
|
|
12 *******************************************************************************/
|
|
13 module dwtx.jface.internal.text.revisions.RevisionPainter;
|
|
14
|
131
|
15 import dwtx.jface.internal.text.revisions.HunkComputer; // packageimport
|
|
16 import dwtx.jface.internal.text.revisions.LineIndexOutOfBoundsException; // packageimport
|
|
17 import dwtx.jface.internal.text.revisions.Hunk; // packageimport
|
|
18 import dwtx.jface.internal.text.revisions.Colors; // packageimport
|
|
19 import dwtx.jface.internal.text.revisions.ChangeRegion; // packageimport
|
|
20 import dwtx.jface.internal.text.revisions.Range; // packageimport
|
|
21 import dwtx.jface.internal.text.revisions.RevisionSelectionProvider; // packageimport
|
|
22
|
|
23
|
129
|
24 import dwt.dwthelper.utils;
|
|
25
|
|
26 import java.util.ArrayList;
|
|
27 import java.util.Arrays;
|
|
28 import java.util.Collections;
|
|
29 import java.util.HashMap;
|
|
30 import java.util.Iterator;
|
|
31 import java.util.List;
|
|
32 import java.util.ListIterator;
|
|
33 import java.util.Map;
|
|
34 import java.util.Map.Entry;
|
|
35
|
|
36 import dwt.DWT;
|
|
37 import dwt.custom.StyledText;
|
|
38 import dwt.events.DisposeEvent;
|
|
39 import dwt.events.DisposeListener;
|
|
40 import dwt.events.MouseEvent;
|
|
41 import dwt.events.MouseMoveListener;
|
|
42 import dwt.events.MouseTrackListener;
|
|
43 import dwt.graphics.Color;
|
|
44 import dwt.graphics.FontMetrics;
|
|
45 import dwt.graphics.GC;
|
|
46 import dwt.graphics.Point;
|
|
47 import dwt.graphics.RGB;
|
|
48 import dwt.graphics.Rectangle;
|
|
49 import dwt.widgets.Canvas;
|
|
50 import dwt.widgets.Control;
|
|
51 import dwt.widgets.Display;
|
|
52 import dwt.widgets.Event;
|
|
53 import dwt.widgets.Listener;
|
|
54 import dwt.widgets.Shell;
|
|
55 import dwtx.core.runtime.Assert;
|
|
56 import dwtx.core.runtime.ListenerList;
|
|
57 import dwtx.core.runtime.Platform;
|
|
58 import dwtx.jface.internal.text.html.BrowserInformationControl;
|
|
59 import dwtx.jface.internal.text.html.HTMLPrinter;
|
|
60 import dwtx.jface.resource.JFaceResources;
|
|
61 import dwtx.jface.text.AbstractReusableInformationControlCreator;
|
|
62 import dwtx.jface.text.BadLocationException;
|
|
63 import dwtx.jface.text.DefaultInformationControl;
|
|
64 import dwtx.jface.text.IDocument;
|
|
65 import dwtx.jface.text.IInformationControl;
|
|
66 import dwtx.jface.text.IInformationControlCreator;
|
|
67 import dwtx.jface.text.IRegion;
|
|
68 import dwtx.jface.text.ITextViewer;
|
|
69 import dwtx.jface.text.ITextViewerExtension5;
|
|
70 import dwtx.jface.text.JFaceTextUtil;
|
|
71 import dwtx.jface.text.Position;
|
|
72 import dwtx.jface.text.Region;
|
|
73 import dwtx.jface.text.information.IInformationProviderExtension2;
|
|
74 import dwtx.jface.text.revisions.IRevisionListener;
|
|
75 import dwtx.jface.text.revisions.IRevisionRulerColumnExtension;
|
|
76 import dwtx.jface.text.revisions.Revision;
|
|
77 import dwtx.jface.text.revisions.RevisionEvent;
|
|
78 import dwtx.jface.text.revisions.RevisionInformation;
|
|
79 import dwtx.jface.text.revisions.RevisionRange;
|
|
80 import dwtx.jface.text.revisions.IRevisionRulerColumnExtension.RenderingMode;
|
|
81 import dwtx.jface.text.source.Annotation;
|
|
82 import dwtx.jface.text.source.CompositeRuler;
|
|
83 import dwtx.jface.text.source.IAnnotationHover;
|
|
84 import dwtx.jface.text.source.IAnnotationHoverExtension;
|
|
85 import dwtx.jface.text.source.IAnnotationHoverExtension2;
|
|
86 import dwtx.jface.text.source.IAnnotationModel;
|
|
87 import dwtx.jface.text.source.IAnnotationModelExtension;
|
|
88 import dwtx.jface.text.source.IAnnotationModelListener;
|
|
89 import dwtx.jface.text.source.IChangeRulerColumn;
|
|
90 import dwtx.jface.text.source.ILineDiffer;
|
|
91 import dwtx.jface.text.source.ILineRange;
|
|
92 import dwtx.jface.text.source.ISharedTextColors;
|
|
93 import dwtx.jface.text.source.ISourceViewer;
|
|
94 import dwtx.jface.text.source.IVerticalRulerColumn;
|
|
95 import dwtx.jface.text.source.LineRange;
|
|
96
|
|
97
|
|
98 /**
|
|
99 * A strategy for painting the live annotate colors onto the vertical ruler column. It also manages
|
|
100 * the revision hover.
|
|
101 *
|
|
102 * @since 3.2
|
|
103 */
|
|
104 public final class RevisionPainter {
|
|
105 /** Tells whether this class is in debug mode. */
|
|
106 private static bool DEBUG= "true".equalsIgnoreCase(Platform.getDebugOption("dwtx.jface.text.source/debug/RevisionRulerColumn")); //$NON-NLS-1$//$NON-NLS-2$
|
|
107
|
|
108 // RGBs provided by UI Designer
|
|
109 private static final RGB BY_DATE_START_COLOR= new RGB(199, 134, 57);
|
|
110 private static final RGB BY_DATE_END_COLOR= new RGB(241, 225, 206);
|
|
111
|
|
112
|
|
113 /**
|
|
114 * The annotations created to show a revision in the overview ruler.
|
|
115 */
|
|
116 private static final class RevisionAnnotation : Annotation {
|
|
117 public RevisionAnnotation(String text) {
|
|
118 super("dwtx.ui.workbench.texteditor.revisionAnnotation", false, text); //$NON-NLS-1$
|
|
119 }
|
|
120 }
|
|
121
|
|
122 /**
|
|
123 * The color tool manages revision colors and computes shaded colors based on the relative age
|
|
124 * and author of a revision.
|
|
125 */
|
|
126 private final class ColorTool {
|
|
127 /**
|
|
128 * The average perceived intensity of a base color. 0 means black, 1 means white. A base
|
|
129 * revision color perceived as light such as yellow will be darkened, while colors perceived
|
|
130 * as dark such as blue will be lightened up.
|
|
131 */
|
|
132 private static final float AVERAGE_INTENSITY= 0.5f;
|
|
133 /**
|
|
134 * The maximum shading in [0, 1] - this is the shade that the most recent revision will
|
|
135 * receive.
|
|
136 */
|
|
137 private static final float MAX_SHADING= 0.7f;
|
|
138 /**
|
|
139 * The minimum shading in [0, 1] - this is the shade that the oldest revision will receive.
|
|
140 */
|
|
141 private static final float MIN_SHADING= 0.2f;
|
|
142 /**
|
|
143 * The shade for the focus boxes.
|
|
144 */
|
|
145 private static final float FOCUS_COLOR_SHADING= 1f;
|
|
146
|
|
147
|
|
148 /**
|
|
149 * A list of {@link Long}, storing the age of each revision in a sorted list.
|
|
150 */
|
|
151 private List fRevisions;
|
|
152 /**
|
|
153 * The stored shaded colors.
|
|
154 */
|
|
155 private final Map fColors= new HashMap();
|
|
156 /**
|
|
157 * The stored focus colors.
|
|
158 */
|
|
159 private final Map fFocusColors= new HashMap();
|
|
160
|
|
161 /**
|
|
162 * Sets the revision information, which is needed to compute the relative age of a revision.
|
|
163 *
|
|
164 * @param info the new revision info, <code>null</code> for none.
|
|
165 */
|
|
166 public void setInfo(RevisionInformation info) {
|
|
167 fRevisions= null;
|
|
168 fColors.clear();
|
|
169 fFocusColors.clear();
|
|
170
|
|
171 if (info is null)
|
|
172 return;
|
|
173 List revisions= new ArrayList();
|
|
174 for (Iterator it= info.getRevisions().iterator(); it.hasNext();) {
|
|
175 Revision revision= (Revision) it.next();
|
|
176 revisions.add(new Long(computeAge(revision)));
|
|
177 }
|
|
178 Collections.sort(revisions);
|
|
179 fRevisions= revisions;
|
|
180 }
|
|
181
|
|
182 private RGB adaptColor(Revision revision, bool focus) {
|
|
183 RGB rgb;
|
|
184 float scale;
|
|
185 if (fRenderingMode is IRevisionRulerColumnExtension.AGE) {
|
|
186 int index= computeAgeIndex(revision);
|
|
187 if (index is -1 || fRevisions.size() is 0) {
|
|
188 rgb= getBackground().getRGB();
|
|
189 } else {
|
|
190 // gradient from intense red for most recent to faint yellow for oldest
|
|
191 RGB[] gradient= Colors.palette(BY_DATE_START_COLOR, BY_DATE_END_COLOR, fRevisions.size());
|
|
192 rgb= gradient[gradient.length - index - 1];
|
|
193 }
|
|
194 scale= 0.99f;
|
|
195 } else if (fRenderingMode is IRevisionRulerColumnExtension.AUTHOR) {
|
|
196 rgb= revision.getColor();
|
|
197 rgb= Colors.adjustBrightness(rgb, AVERAGE_INTENSITY);
|
|
198 scale= 0.6f;
|
|
199 } else if (fRenderingMode is IRevisionRulerColumnExtension.AUTHOR_SHADED_BY_AGE) {
|
|
200 rgb= revision.getColor();
|
|
201 rgb= Colors.adjustBrightness(rgb, AVERAGE_INTENSITY);
|
|
202 int index= computeAgeIndex(revision);
|
|
203 int size= fRevisions.size();
|
|
204 // relative age: newest is 0, oldest is 1
|
|
205 // if there is only one revision, use an intermediate value to avoid extreme coloring
|
|
206 if (index is -1 || size < 2)
|
|
207 scale= 0.5f;
|
|
208 else
|
|
209 scale= (float) index / (size - 1);
|
|
210 } else {
|
|
211 Assert.isTrue(false);
|
|
212 return null; // dummy
|
|
213 }
|
|
214 rgb= getShadedColor(rgb, scale, focus);
|
|
215 return rgb;
|
|
216 }
|
|
217
|
|
218 private int computeAgeIndex(Revision revision) {
|
|
219 long age= computeAge(revision);
|
|
220 int index= fRevisions.indexOf(new Long(age));
|
|
221 return index;
|
|
222 }
|
|
223
|
|
224 private RGB getShadedColor(RGB color, float scale, bool focus) {
|
|
225 Assert.isLegal(scale >= 0.0);
|
|
226 Assert.isLegal(scale <= 1.0);
|
|
227 RGB background= getBackground().getRGB();
|
|
228
|
|
229 // normalize to lie within [MIN_SHADING, MAX_SHADING]
|
|
230 // use more intense colors if the ruler is narrow (i.e. not showing line numbers)
|
|
231 bool makeIntense= getWidth() <= 15;
|
|
232 float intensityShift= makeIntense ? 0.3f : 0f;
|
|
233 float max= MAX_SHADING + intensityShift;
|
|
234 float min= MIN_SHADING + intensityShift;
|
|
235 scale= (max - min) * scale + min;
|
|
236
|
|
237 // focus coloring
|
|
238 if (focus) {
|
|
239 scale += FOCUS_COLOR_SHADING;
|
|
240 if (scale > 1) {
|
|
241 background= new RGB(255 - background.red, 255 - background.green, 255 - background.blue);
|
|
242 scale= 2 - scale;
|
|
243 }
|
|
244 }
|
|
245
|
|
246 return Colors.blend(background, color, scale);
|
|
247 }
|
|
248
|
|
249 private long computeAge(Revision revision) {
|
|
250 return revision.getDate().getTime();
|
|
251 }
|
|
252
|
|
253 /**
|
|
254 * Returns the color for a revision based on relative age and author.
|
|
255 *
|
|
256 * @param revision the revision
|
|
257 * @param focus <code>true</code> to return the focus color
|
|
258 * @return the color for a revision
|
|
259 */
|
|
260 public RGB getColor(Revision revision, bool focus) {
|
|
261 Map map= focus ? fFocusColors : fColors;
|
|
262 RGB color= (RGB) map.get(revision);
|
|
263 if (color !is null)
|
|
264 return color;
|
|
265
|
|
266 color= adaptColor(revision, focus);
|
|
267 map.put(revision, color);
|
|
268 return color;
|
|
269 }
|
|
270 }
|
|
271
|
|
272 /**
|
|
273 * Handles all the mouse interaction in this line number ruler column.
|
|
274 */
|
|
275 private class MouseHandler : MouseMoveListener, MouseTrackListener, Listener {
|
|
276
|
|
277 private RevisionRange fMouseDownRegion;
|
|
278
|
|
279 private void handleMouseUp(Event e) {
|
|
280 if (e.button is 1) {
|
|
281 RevisionRange upRegion= fFocusRange;
|
|
282 RevisionRange downRegion= fMouseDownRegion;
|
|
283 fMouseDownRegion= null;
|
|
284
|
|
285 if (upRegion is downRegion) {
|
|
286 Revision revision= upRegion is null ? null : upRegion.getRevision();
|
|
287 if (revision is fSelectedRevision)
|
|
288 revision= null; // deselect already selected revision
|
|
289 handleRevisionSelected(revision);
|
|
290 }
|
|
291 }
|
|
292 }
|
|
293
|
|
294 private void handleMouseDown(Event e) {
|
|
295 if (e.button is 3)
|
|
296 updateFocusRevision(null); // kill any focus as the ctx menu is going to show
|
|
297 if (e.button is 1) {
|
|
298 fMouseDownRegion= fFocusRange;
|
|
299 postRedraw();
|
|
300 }
|
|
301 }
|
|
302
|
|
303 /*
|
|
304 * @see dwt.widgets.Listener#handleEvent(dwt.widgets.Event)
|
|
305 */
|
|
306 public void handleEvent(Event event) {
|
|
307 switch (event.type) {
|
|
308 case DWT.MouseWheel:
|
|
309 handleMouseWheel(event);
|
|
310 break;
|
|
311 case DWT.MouseDown:
|
|
312 handleMouseDown(event);
|
|
313 break;
|
|
314 case DWT.MouseUp:
|
|
315 handleMouseUp(event);
|
|
316 break;
|
|
317 default:
|
|
318 Assert.isLegal(false);
|
|
319 }
|
|
320 }
|
|
321
|
|
322 /*
|
|
323 * @see dwt.events.MouseTrackListener#mouseEnter(dwt.events.MouseEvent)
|
|
324 */
|
|
325 public void mouseEnter(MouseEvent e) {
|
|
326 updateFocusLine(toDocumentLineNumber(e.y));
|
|
327 }
|
|
328
|
|
329 /*
|
|
330 * @see dwt.events.MouseTrackListener#mouseExit(dwt.events.MouseEvent)
|
|
331 */
|
|
332 public void mouseExit(MouseEvent e) {
|
|
333 updateFocusLine(-1);
|
|
334 }
|
|
335
|
|
336 /*
|
|
337 * @see dwt.events.MouseTrackListener#mouseHover(dwt.events.MouseEvent)
|
|
338 */
|
|
339 public void mouseHover(MouseEvent e) {
|
|
340 }
|
|
341
|
|
342 /*
|
|
343 * @see dwt.events.MouseMoveListener#mouseMove(dwt.events.MouseEvent)
|
|
344 */
|
|
345 public void mouseMove(MouseEvent e) {
|
|
346 updateFocusLine(toDocumentLineNumber(e.y));
|
|
347 }
|
|
348 }
|
|
349
|
|
350 /**
|
|
351 * Internal listener class that will update the ruler when the underlying model changes.
|
|
352 */
|
|
353 private class AnnotationListener : IAnnotationModelListener {
|
|
354 /*
|
|
355 * @see dwtx.jface.text.source.IAnnotationModelListener#modelChanged(dwtx.jface.text.source.IAnnotationModel)
|
|
356 */
|
|
357 public void modelChanged(IAnnotationModel model) {
|
|
358 clearRangeCache();
|
|
359 postRedraw();
|
|
360 }
|
|
361
|
|
362 }
|
|
363
|
|
364 /**
|
|
365 * The information control creator.
|
|
366 */
|
|
367 private static final class HoverInformationControlCreator : AbstractReusableInformationControlCreator {
|
|
368 private bool fIsFocusable;
|
|
369
|
|
370 public HoverInformationControlCreator(bool isFocusable) {
|
|
371 fIsFocusable= isFocusable;
|
|
372 }
|
|
373
|
|
374 /*
|
|
375 * @see dwtx.jface.internal.text.revisions.AbstractReusableInformationControlCreator#doCreateInformationControl(dwt.widgets.Shell)
|
|
376 */
|
|
377 protected IInformationControl doCreateInformationControl(Shell parent) {
|
|
378 if (BrowserInformationControl.isAvailable(parent)) {
|
|
379 return new BrowserInformationControl(parent, JFaceResources.DIALOG_FONT, fIsFocusable) {
|
|
380 /*
|
|
381 * @see dwtx.jface.internal.text.html.BrowserInformationControl#setInformation(java.lang.String)
|
|
382 * @since 3.3
|
|
383 */
|
|
384 public void setInformation(String content) {
|
|
385 content= addCSSToHTMLFragment(content);
|
|
386 super.setInformation(content);
|
|
387 }
|
|
388
|
|
389 /**
|
|
390 * Adds a HTML header and CSS info if <code>html</code> is only an HTML fragment (has no
|
|
391 * <html> section).
|
|
392 *
|
|
393 * @param html the html / text produced by a revision
|
|
394 * @return modified html
|
|
395 */
|
|
396 private String addCSSToHTMLFragment(String html) {
|
|
397 int max= Math.min(100, html.length());
|
|
398 if (html.substring(0, max).indexOf("<html>") !is -1) //$NON-NLS-1$
|
|
399 // there is already a header
|
|
400 return html;
|
|
401
|
|
402 StringBuffer info= new StringBuffer(512 + html.length());
|
|
403 HTMLPrinter.insertPageProlog(info, 0, fgStyleSheet);
|
|
404 info.append(html);
|
|
405 HTMLPrinter.addPageEpilog(info);
|
|
406 return info.toString();
|
|
407 }
|
|
408
|
|
409 };
|
|
410 }
|
|
411 return new DefaultInformationControl(parent, fIsFocusable);
|
|
412 }
|
|
413
|
|
414 /*
|
|
415 * @see dwtx.jface.text.AbstractReusableInformationControlCreator#canReplace(dwtx.jface.text.IInformationControlCreator)
|
|
416 */
|
|
417 public bool canReplace(IInformationControlCreator creator) {
|
|
418 return creator.getClass() is getClass()
|
|
419 && ((HoverInformationControlCreator) creator).fIsFocusable is fIsFocusable;
|
|
420 }
|
|
421 }
|
|
422
|
|
423 private static final String fgStyleSheet= "/* Font definitions */\n" + //$NON-NLS-1$
|
|
424 "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$
|
|
425 "pre { font-family: monospace; font-size: 9pt }\n" + //$NON-NLS-1$
|
|
426 "\n" + //$NON-NLS-1$
|
|
427 "/* Margins */\n" + //$NON-NLS-1$
|
|
428 "body { overflow: auto; margin-top: 0; margin-bottom: 4; margin-left: 3; margin-right: 0 }\n" + //$NON-NLS-1$
|
|
429 "h1 { margin-top: 5; margin-bottom: 1 } \n" + //$NON-NLS-1$
|
|
430 "h2 { margin-top: 25; margin-bottom: 3 }\n" + //$NON-NLS-1$
|
|
431 "h3 { margin-top: 20; margin-bottom: 3 }\n" + //$NON-NLS-1$
|
|
432 "h4 { margin-top: 20; margin-bottom: 3 }\n" + //$NON-NLS-1$
|
|
433 "h5 { margin-top: 0; margin-bottom: 0 }\n" + //$NON-NLS-1$
|
|
434 "p { margin-top: 10px; margin-bottom: 10px }\n" + //$NON-NLS-1$
|
|
435 "pre { margin-left: 6 }\n" + //$NON-NLS-1$
|
|
436 "ul { margin-top: 0; margin-bottom: 10 }\n" + //$NON-NLS-1$
|
|
437 "li { margin-top: 0; margin-bottom: 0 } \n" + //$NON-NLS-1$
|
|
438 "li p { margin-top: 0; margin-bottom: 0 } \n" + //$NON-NLS-1$
|
|
439 "ol { margin-top: 0; margin-bottom: 10 }\n" + //$NON-NLS-1$
|
|
440 "dl { margin-top: 0; margin-bottom: 10 }\n" + //$NON-NLS-1$
|
|
441 "dt { margin-top: 0; margin-bottom: 0; font-weight: bold }\n" + //$NON-NLS-1$
|
|
442 "dd { margin-top: 0; margin-bottom: 0 }\n" + //$NON-NLS-1$
|
|
443 "\n" + //$NON-NLS-1$
|
|
444 "/* Styles and colors */\n" + //$NON-NLS-1$
|
|
445 "a:link { color: #0000FF }\n" + //$NON-NLS-1$
|
|
446 "a:hover { color: #000080 }\n" + //$NON-NLS-1$
|
|
447 "a:visited { text-decoration: underline }\n" + //$NON-NLS-1$
|
|
448 "h4 { font-style: italic }\n" + //$NON-NLS-1$
|
|
449 "strong { font-weight: bold }\n" + //$NON-NLS-1$
|
|
450 "em { font-style: italic }\n" + //$NON-NLS-1$
|
|
451 "var { font-style: italic }\n" + //$NON-NLS-1$
|
|
452 "th { font-weight: bold }\n" + //$NON-NLS-1$
|
|
453 ""; //$NON-NLS-1$
|
|
454
|
|
455 /**
|
|
456 * The revision hover displays information about the currently selected revision.
|
|
457 */
|
|
458 private final class RevisionHover : IAnnotationHover, IAnnotationHoverExtension, IAnnotationHoverExtension2, IInformationProviderExtension2 {
|
|
459
|
|
460 /*
|
|
461 * @see dwtx.jface.text.source.IAnnotationHover#getHoverInfo(dwtx.jface.text.source.ISourceViewer,
|
|
462 * int)
|
|
463 */
|
|
464 public String getHoverInfo(ISourceViewer sourceViewer, int lineNumber) {
|
|
465 Object info= getHoverInfo(sourceViewer, getHoverLineRange(sourceViewer, lineNumber), 0);
|
|
466 return info is null ? null : info.toString();
|
|
467 }
|
|
468
|
|
469 /*
|
|
470 * @see dwtx.jface.text.source.IAnnotationHoverExtension#getHoverControlCreator()
|
|
471 */
|
|
472 public IInformationControlCreator getHoverControlCreator() {
|
|
473 RevisionInformation revisionInfo= fRevisionInfo;
|
|
474 if (revisionInfo !is null) {
|
|
475 IInformationControlCreator creator= revisionInfo.getHoverControlCreator();
|
|
476 if (creator !is null)
|
|
477 return creator;
|
|
478 }
|
|
479 return new HoverInformationControlCreator(false);
|
|
480 }
|
|
481
|
|
482 /*
|
|
483 * @see dwtx.jface.text.source.IAnnotationHoverExtension#canHandleMouseCursor()
|
|
484 */
|
|
485 public bool canHandleMouseCursor() {
|
|
486 return false;
|
|
487 }
|
|
488
|
|
489 /*
|
|
490 * @see dwtx.jface.text.source.IAnnotationHoverExtension2#canHandleMouseWheel()
|
|
491 */
|
|
492 public bool canHandleMouseWheel() {
|
|
493 return true;
|
|
494 }
|
|
495
|
|
496 /*
|
|
497 * @see dwtx.jface.text.source.IAnnotationHoverExtension#getHoverInfo(dwtx.jface.text.source.ISourceViewer,
|
|
498 * dwtx.jface.text.source.ILineRange, int)
|
|
499 */
|
|
500 public Object getHoverInfo(ISourceViewer sourceViewer, ILineRange lineRange, int visibleNumberOfLines) {
|
|
501 RevisionRange range= getRange(lineRange.getStartLine());
|
|
502 Object info= range is null ? null : range.getRevision().getHoverInfo();
|
|
503 return info;
|
|
504 }
|
|
505
|
|
506 /*
|
|
507 * @see dwtx.jface.text.source.IAnnotationHoverExtension#getHoverLineRange(dwtx.jface.text.source.ISourceViewer,
|
|
508 * int)
|
|
509 */
|
|
510 public ILineRange getHoverLineRange(ISourceViewer viewer, int lineNumber) {
|
|
511 RevisionRange range= getRange(lineNumber);
|
|
512 return range is null ? null : new LineRange(lineNumber, 1);
|
|
513 }
|
|
514
|
|
515 /*
|
|
516 * @see dwtx.jface.text.information.IInformationProviderExtension2#getInformationPresenterControlCreator()
|
|
517 */
|
|
518 public IInformationControlCreator getInformationPresenterControlCreator() {
|
|
519 RevisionInformation revisionInfo= fRevisionInfo;
|
|
520 if (revisionInfo !is null) {
|
|
521 IInformationControlCreator creator= revisionInfo.getInformationPresenterControlCreator();
|
|
522 if (creator !is null)
|
|
523 return creator;
|
|
524 }
|
|
525 return new HoverInformationControlCreator(true);
|
|
526 }
|
|
527 }
|
|
528
|
|
529 /* Listeners and helpers. */
|
|
530
|
|
531 /** The shared color provider. */
|
|
532 private final ISharedTextColors fSharedColors;
|
|
533 /** The color tool. */
|
|
534 private final ColorTool fColorTool= new ColorTool();
|
|
535 /** The mouse handler. */
|
|
536 private final MouseHandler fMouseHandler= new MouseHandler();
|
|
537 /** The hover. */
|
|
538 private final RevisionHover fHover= new RevisionHover();
|
|
539 /** The annotation listener. */
|
|
540 private final AnnotationListener fAnnotationListener= new AnnotationListener();
|
|
541 /** The selection provider. */
|
|
542 private final RevisionSelectionProvider fRevisionSelectionProvider= new RevisionSelectionProvider(this);
|
|
543 /**
|
|
544 * The list of revision listeners.
|
|
545 * @since 3.3.
|
|
546 */
|
|
547 private final ListenerList fRevisionListeners= new ListenerList(ListenerList.IDENTITY);
|
|
548
|
|
549 /* The context - column and viewer we are connected to. */
|
|
550
|
|
551 /** The vertical ruler column that delegates painting to this painter. */
|
|
552 private final IVerticalRulerColumn fColumn;
|
|
553 /** The parent ruler. */
|
|
554 private CompositeRuler fParentRuler;
|
|
555 /** The column's control, typically a {@link Canvas}, possibly <code>null</code>. */
|
|
556 private Control fControl;
|
|
557 /** The text viewer that the column is attached to. */
|
|
558 private ITextViewer fViewer;
|
|
559 /** The viewer's text widget. */
|
|
560 private StyledText fWidget;
|
|
561
|
|
562 /* The models we operate on. */
|
|
563
|
|
564 /** The revision model object. */
|
|
565 private RevisionInformation fRevisionInfo;
|
|
566 /** The line differ. */
|
|
567 private ILineDiffer fLineDiffer= null;
|
|
568 /** The annotation model. */
|
|
569 private IAnnotationModel fAnnotationModel= null;
|
|
570 /** The background color, possibly <code>null</code>. */
|
|
571 private Color fBackground;
|
|
572
|
|
573 /* Cache. */
|
|
574
|
|
575 /** The cached list of ranges adapted to quick diff. */
|
|
576 private List fRevisionRanges= null;
|
|
577 /** The annotations created for the overview ruler temporary display. */
|
|
578 private List fAnnotations= new ArrayList();
|
|
579
|
|
580 /* State */
|
|
581
|
|
582 /** The current focus line, -1 for none. */
|
|
583 private int fFocusLine= -1;
|
|
584 /** The current focus region, <code>null</code> if none. */
|
|
585 private RevisionRange fFocusRange= null;
|
|
586 /** The current focus revision, <code>null</code> if none. */
|
|
587 private Revision fFocusRevision= null;
|
|
588 /**
|
|
589 * The currently selected revision, <code>null</code> if none. The difference between
|
|
590 * {@link #fFocusRevision} and {@link #fSelectedRevision} may not be obvious: the focus revision
|
|
591 * is the one focused by the mouse (by hovering over a block of the revision), while the
|
|
592 * selected revision is sticky, i.e. is not removed when the mouse leaves the ruler.
|
|
593 *
|
|
594 * @since 3.3
|
|
595 */
|
|
596 private Revision fSelectedRevision= null;
|
|
597 /** <code>true</code> if the mouse wheel handler is installed, <code>false</code> otherwise. */
|
|
598 private bool fWheelHandlerInstalled= false;
|
|
599 /**
|
|
600 * The revision rendering mode.
|
|
601 */
|
|
602 private RenderingMode fRenderingMode= IRevisionRulerColumnExtension.AUTHOR_SHADED_BY_AGE;
|
|
603 /**
|
|
604 * The required with in characters.
|
|
605 * @since 3.3
|
|
606 */
|
|
607 private int fRequiredWidth= -1;
|
|
608 /**
|
|
609 * The width of the revision field in chars to compute {@link #fAuthorInset} from.
|
|
610 * @since 3.3
|
|
611 */
|
|
612 private int fRevisionIdChars= 0;
|
|
613 /**
|
|
614 * <code>true</code> to show revision ids, <code>false</code> otherwise.
|
|
615 * @since 3.3
|
|
616 */
|
|
617 private bool fShowRevision= false;
|
|
618 /**
|
|
619 * <code>true</code> to show the author, <code>false</code> otherwise.
|
|
620 * @since 3.3
|
|
621 */
|
|
622 private bool fShowAuthor= false;
|
|
623 /**
|
|
624 * The author inset in pixels for when author *and* revision id are shown.
|
|
625 * @since 3.3
|
|
626 */
|
|
627 private int fAuthorInset;
|
|
628 /**
|
|
629 * The remembered ruler width (as changing the ruler width triggers recomputation of the colors.
|
|
630 * @since 3.3
|
|
631 */
|
|
632 private int fLastWidth= -1;
|
|
633
|
|
634 /**
|
|
635 * Creates a new revision painter for a vertical ruler column.
|
|
636 *
|
|
637 * @param column the column that will delegate{@link #paint(GC, ILineRange) painting} to the
|
|
638 * newly created painter.
|
|
639 * @param sharedColors a shared colors object to store shaded colors in
|
|
640 */
|
|
641 public RevisionPainter(IVerticalRulerColumn column, ISharedTextColors sharedColors) {
|
|
642 Assert.isLegal(column !is null);
|
|
643 Assert.isLegal(sharedColors !is null);
|
|
644 fColumn= column;
|
|
645 fSharedColors= sharedColors;
|
|
646 }
|
|
647
|
|
648 /**
|
|
649 * Sets the revision information to be drawn and triggers a redraw.
|
|
650 *
|
|
651 * @param info the revision information to show, <code>null</code> to draw none
|
|
652 */
|
|
653 public void setRevisionInformation(RevisionInformation info) {
|
|
654 if (fRevisionInfo !is info) {
|
|
655 fRequiredWidth= -1;
|
|
656 fRevisionIdChars= 0;
|
|
657 fRevisionInfo= info;
|
|
658 clearRangeCache();
|
|
659 updateFocusRange(null);
|
|
660 handleRevisionSelected((Revision) null);
|
|
661 fColorTool.setInfo(info);
|
|
662 postRedraw();
|
|
663 informListeners();
|
|
664 }
|
|
665 }
|
|
666
|
|
667 /**
|
|
668 * Changes the rendering mode and triggers redrawing if needed.
|
|
669 *
|
|
670 * @param renderingMode the rendering mode
|
|
671 * @since 3.3
|
|
672 */
|
|
673 public void setRenderingMode(RenderingMode renderingMode) {
|
|
674 Assert.isLegal(renderingMode !is null);
|
|
675 if (fRenderingMode !is renderingMode) {
|
|
676 fRenderingMode= renderingMode;
|
|
677 fColorTool.setInfo(fRevisionInfo);
|
|
678 postRedraw();
|
|
679 }
|
|
680 }
|
|
681
|
|
682 /**
|
|
683 * Sets the background color.
|
|
684 *
|
|
685 * @param background the background color, <code>null</code> for the platform's list
|
|
686 * background
|
|
687 */
|
|
688 public void setBackground(Color background) {
|
|
689 fBackground= background;
|
|
690 }
|
|
691
|
|
692 /**
|
|
693 * Sets the parent ruler - the delegating column must call this method as soon as it creates its
|
|
694 * control.
|
|
695 *
|
|
696 * @param parentRuler the parent ruler
|
|
697 */
|
|
698 public void setParentRuler(CompositeRuler parentRuler) {
|
|
699 fParentRuler= parentRuler;
|
|
700 }
|
|
701
|
|
702 /**
|
|
703 * Delegates the painting of the quick diff colors to this painter. The painter will draw the
|
|
704 * color boxes onto the passed {@link GC} for all model (document) lines in
|
|
705 * <code>visibleModelLines</code>.
|
|
706 *
|
|
707 * @param gc the {@link GC} to draw onto
|
|
708 * @param visibleLines the lines (in document offsets) that are currently (perhaps only
|
|
709 * partially) visible
|
|
710 */
|
|
711 public void paint(GC gc, ILineRange visibleLines) {
|
|
712 connectIfNeeded();
|
|
713 if (!isConnected())
|
|
714 return;
|
|
715
|
|
716 // compute the horizontal indent of the author for the case that we show revision
|
|
717 // and author
|
|
718 if (fShowAuthor && fShowRevision) {
|
|
719 char[] string= new char[fRevisionIdChars + 1];
|
|
720 Arrays.fill(string, '9');
|
|
721 if (string.length > 1) {
|
|
722 string[0]= '.';
|
|
723 string[1]= ' ';
|
|
724 }
|
|
725 fAuthorInset= gc.stringExtent(new String(string)).x;
|
|
726 }
|
|
727
|
|
728 // recompute colors (show intense colors if ruler is narrow)
|
|
729 int width= getWidth();
|
|
730 if (width !is fLastWidth) {
|
|
731 fColorTool.setInfo(fRevisionInfo);
|
|
732 fLastWidth= width;
|
|
733 }
|
|
734
|
|
735 // draw change regions
|
|
736 List/* <RevisionRange> */ranges= getRanges(visibleLines);
|
|
737 for (Iterator it= ranges.iterator(); it.hasNext();) {
|
|
738 RevisionRange region= (RevisionRange) it.next();
|
|
739 paintRange(region, gc);
|
|
740 }
|
|
741 }
|
|
742
|
|
743 /**
|
|
744 * Ensures that the column is fully instantiated, i.e. has a control, and that the viewer is
|
|
745 * visible.
|
|
746 */
|
|
747 private void connectIfNeeded() {
|
|
748 if (isConnected() || fParentRuler is null)
|
|
749 return;
|
|
750
|
|
751 fViewer= fParentRuler.getTextViewer();
|
|
752 if (fViewer is null)
|
|
753 return;
|
|
754
|
|
755 fWidget= fViewer.getTextWidget();
|
|
756 if (fWidget is null)
|
|
757 return;
|
|
758
|
|
759 fControl= fColumn.getControl();
|
|
760 if (fControl is null)
|
|
761 return;
|
|
762
|
|
763 fControl.addMouseTrackListener(fMouseHandler);
|
|
764 fControl.addMouseMoveListener(fMouseHandler);
|
|
765 fControl.addListener(DWT.MouseUp, fMouseHandler);
|
|
766 fControl.addListener(DWT.MouseDown, fMouseHandler);
|
|
767 fControl.addDisposeListener(new DisposeListener() {
|
|
768 /*
|
|
769 * @see dwt.events.DisposeListener#widgetDisposed(dwt.events.DisposeEvent)
|
|
770 */
|
|
771 public void widgetDisposed(DisposeEvent e) {
|
|
772 handleDispose();
|
|
773 }
|
|
774 });
|
|
775
|
|
776 fRevisionSelectionProvider.install(fViewer);
|
|
777 }
|
|
778
|
|
779 /**
|
|
780 * Returns <code>true</code> if the column is fully connected.
|
|
781 *
|
|
782 * @return <code>true</code> if the column is fully connected, false otherwise
|
|
783 */
|
|
784 private bool isConnected() {
|
|
785 return fControl !is null;
|
|
786 }
|
|
787
|
|
788 /**
|
|
789 * Sets the annotation model.
|
|
790 *
|
|
791 * @param model the annotation model, possibly <code>null</code>
|
|
792 * @see IVerticalRulerColumn#setModel(IAnnotationModel)
|
|
793 */
|
|
794 public void setModel(IAnnotationModel model) {
|
|
795 IAnnotationModel diffModel;
|
|
796 if (model instanceof IAnnotationModelExtension)
|
|
797 diffModel= ((IAnnotationModelExtension) model).getAnnotationModel(IChangeRulerColumn.QUICK_DIFF_MODEL_ID);
|
|
798 else
|
|
799 diffModel= model;
|
|
800
|
|
801 setDiffer(diffModel);
|
|
802 setAnnotationModel(model);
|
|
803 }
|
|
804
|
|
805 /**
|
|
806 * Sets the annotation model.
|
|
807 *
|
|
808 * @param model the annotation model.
|
|
809 */
|
|
810 private void setAnnotationModel(IAnnotationModel model) {
|
|
811 if (fAnnotationModel !is model)
|
|
812 fAnnotationModel= model;
|
|
813 }
|
|
814
|
|
815 /**
|
|
816 * Sets the line differ.
|
|
817 *
|
|
818 * @param differ the line differ or <code>null</code> if none
|
|
819 */
|
|
820 private void setDiffer(IAnnotationModel differ) {
|
|
821 if (differ instanceof ILineDiffer || differ is null) {
|
|
822 if (fLineDiffer !is differ) {
|
|
823 if (fLineDiffer !is null)
|
|
824 ((IAnnotationModel) fLineDiffer).removeAnnotationModelListener(fAnnotationListener);
|
|
825 fLineDiffer= (ILineDiffer) differ;
|
|
826 if (fLineDiffer !is null)
|
|
827 ((IAnnotationModel) fLineDiffer).addAnnotationModelListener(fAnnotationListener);
|
|
828 }
|
|
829 }
|
|
830 }
|
|
831
|
|
832 /**
|
|
833 * Disposes of the painter's resources.
|
|
834 */
|
|
835 private void handleDispose() {
|
|
836 updateFocusLine(-1);
|
|
837
|
|
838 if (fLineDiffer !is null) {
|
|
839 ((IAnnotationModel) fLineDiffer).removeAnnotationModelListener(fAnnotationListener);
|
|
840 fLineDiffer= null;
|
|
841 }
|
|
842
|
|
843 fRevisionSelectionProvider.uninstall();
|
|
844 }
|
|
845
|
|
846 /**
|
|
847 * Paints a single change region onto <code>gc</code>.
|
|
848 *
|
|
849 * @param range the range to paint
|
|
850 * @param gc the {@link GC} to paint on
|
|
851 */
|
|
852 private void paintRange(RevisionRange range, GC gc) {
|
|
853 ILineRange widgetRange= modelLinesToWidgetLines(range);
|
|
854 if (widgetRange is null)
|
|
855 return;
|
|
856
|
|
857 Revision revision= range.getRevision();
|
|
858 bool drawArmedFocus= range is fMouseHandler.fMouseDownRegion;
|
|
859 bool drawSelection= !drawArmedFocus && revision is fSelectedRevision;
|
|
860 bool drawFocus= !drawSelection && !drawArmedFocus && revision is fFocusRevision;
|
|
861 Rectangle box= computeBoxBounds(widgetRange);
|
|
862
|
|
863 gc.setBackground(lookupColor(revision, false));
|
|
864 if (drawArmedFocus) {
|
|
865 Color foreground= gc.getForeground();
|
|
866 Color focusColor= lookupColor(revision, true);
|
|
867 gc.setForeground(focusColor);
|
|
868 gc.fillRectangle(box);
|
|
869 gc.drawRectangle(box.x, box.y, box.width - 1, box.height - 1); // highlight box
|
|
870 gc.drawRectangle(box.x + 1, box.y + 1, box.width - 3, box.height - 3); // inner highlight box
|
|
871 gc.setForeground(foreground);
|
|
872 } else if (drawFocus || drawSelection) {
|
|
873 Color foreground= gc.getForeground();
|
|
874 Color focusColor= lookupColor(revision, true);
|
|
875 gc.setForeground(focusColor);
|
|
876 gc.fillRectangle(box);
|
|
877 gc.drawRectangle(box.x, box.y, box.width - 1, box.height - 1); // highlight box
|
|
878 gc.setForeground(foreground);
|
|
879 } else {
|
|
880 gc.fillRectangle(box);
|
|
881 }
|
|
882
|
|
883 if ((fShowAuthor || fShowRevision)) {
|
|
884 int indentation= 1;
|
|
885 int baselineBias= getBaselineBias(gc, widgetRange.getStartLine());
|
|
886 if (fShowAuthor && fShowRevision) {
|
|
887 gc.drawString(revision.getId(), indentation, box.y + baselineBias, true);
|
|
888 gc.drawString(revision.getAuthor(), fAuthorInset, box.y + baselineBias, true);
|
|
889 } else if (fShowAuthor) {
|
|
890 gc.drawString(revision.getAuthor(), indentation, box.y + baselineBias, true);
|
|
891 } else if (fShowRevision) {
|
|
892 gc.drawString(revision.getId(), indentation, box.y + baselineBias, true);
|
|
893 }
|
|
894 }
|
|
895 }
|
|
896
|
|
897 /**
|
|
898 * Returns the difference between the baseline of the widget and the
|
|
899 * baseline as specified by the font for <code>gc</code>. When drawing
|
|
900 * line numbers, the returned bias should be added to obtain text lined up
|
|
901 * on the correct base line of the text widget.
|
|
902 *
|
|
903 * @param gc the <code>GC</code> to get the font metrics from
|
|
904 * @param widgetLine the widget line
|
|
905 * @return the baseline bias to use when drawing text that is lined up with
|
|
906 * <code>fCachedTextWidget</code>
|
|
907 * @since 3.3
|
|
908 */
|
|
909 private int getBaselineBias(GC gc, int widgetLine) {
|
|
910 /*
|
|
911 * https://bugs.eclipse.org/bugs/show_bug.cgi?id=62951
|
|
912 * widget line height may be more than the font height used for the
|
|
913 * line numbers, since font styles (bold, italics...) can have larger
|
|
914 * font metrics than the simple font used for the numbers.
|
|
915 */
|
|
916 int offset= fWidget.getOffsetAtLine(widgetLine);
|
|
917 int widgetBaseline= fWidget.getBaseline(offset);
|
|
918
|
|
919 FontMetrics fm = gc.getFontMetrics();
|
|
920 int fontBaseline = fm.getAscent() + fm.getLeading();
|
|
921 int baselineBias= widgetBaseline - fontBaseline;
|
|
922 return Math.max(0, baselineBias);
|
|
923 }
|
|
924
|
|
925 /**
|
|
926 * Looks up the color for a certain revision.
|
|
927 *
|
|
928 * @param revision the revision to get the color for
|
|
929 * @param focus <code>true</code> if it is the focus revision
|
|
930 * @return the color for the revision
|
|
931 */
|
|
932 private Color lookupColor(Revision revision, bool focus) {
|
|
933 return fSharedColors.getColor(fColorTool.getColor(revision, focus));
|
|
934 }
|
|
935
|
|
936 /**
|
|
937 * Returns the revision range that contains the given line, or
|
|
938 * <code>null</code> if there is none.
|
|
939 *
|
|
940 * @param line the line of interest
|
|
941 * @return the corresponding <code>RevisionRange</code> or <code>null</code>
|
|
942 */
|
|
943 private RevisionRange getRange(int line) {
|
|
944 List ranges= getRangeCache();
|
|
945
|
|
946 if (ranges.isEmpty() || line is -1)
|
|
947 return null;
|
|
948
|
|
949 for (Iterator it= ranges.iterator(); it.hasNext();) {
|
|
950 RevisionRange range= (RevisionRange) it.next();
|
|
951 if (contains(range, line))
|
|
952 return range;
|
|
953 }
|
|
954
|
|
955 // line may be right after the last region
|
|
956 RevisionRange lastRegion= (RevisionRange) ranges.get(ranges.size() - 1);
|
|
957 if (line is end(lastRegion))
|
|
958 return lastRegion;
|
|
959 return null;
|
|
960 }
|
|
961
|
|
962 /**
|
|
963 * Returns the sublist of all <code>RevisionRange</code>s that intersect with the given lines.
|
|
964 *
|
|
965 * @param lines the model based lines of interest
|
|
966 * @return elementType: RevisionRange
|
|
967 */
|
|
968 private List getRanges(ILineRange lines) {
|
|
969 List ranges= getRangeCache();
|
|
970
|
|
971 // return the interesting subset
|
|
972 int end= end(lines);
|
|
973 int first= -1, last= -1;
|
|
974 for (int i= 0; i < ranges.size(); i++) {
|
|
975 RevisionRange range= (RevisionRange) ranges.get(i);
|
|
976 int rangeEnd= end(range);
|
|
977 if (first is -1 && rangeEnd > lines.getStartLine())
|
|
978 first= i;
|
|
979 if (first !is -1 && rangeEnd > end) {
|
|
980 last= i;
|
|
981 break;
|
|
982 }
|
|
983 }
|
|
984 if (first is -1)
|
|
985 return Collections.EMPTY_LIST;
|
|
986 if (last is -1)
|
|
987 last= ranges.size() - 1; // bottom index may be one too much
|
|
988
|
|
989 return ranges.subList(first, last + 1);
|
|
990 }
|
|
991
|
|
992 /**
|
|
993 * Gets all change ranges of the revisions in the revision model and adapts them to the current
|
|
994 * quick diff information. The list is cached.
|
|
995 *
|
|
996 * @return the list of all change regions, with diff information applied
|
|
997 */
|
|
998 private List getRangeCache() {
|
|
999 if (fRevisionRanges is null) {
|
|
1000 if (fRevisionInfo is null) {
|
|
1001 fRevisionRanges= Collections.EMPTY_LIST;
|
|
1002 } else {
|
|
1003 Hunk[] hunks= HunkComputer.computeHunks(fLineDiffer, fViewer.getDocument().getNumberOfLines());
|
|
1004 fRevisionInfo.applyDiff(hunks);
|
|
1005 fRevisionRanges= fRevisionInfo.getRanges();
|
|
1006 updateOverviewAnnotations();
|
|
1007 informListeners();
|
|
1008 }
|
|
1009 }
|
|
1010
|
|
1011 return fRevisionRanges;
|
|
1012 }
|
|
1013
|
|
1014 /**
|
|
1015 * Clears the range cache.
|
|
1016 *
|
|
1017 * @since 3.3
|
|
1018 */
|
|
1019 private void clearRangeCache() {
|
|
1020 fRevisionRanges= null;
|
|
1021 }
|
|
1022
|
|
1023 /**
|
|
1024 * Returns <code>true</code> if <code>range</code> contains <code>line</code>. A line is
|
|
1025 * not contained in a range if it is the range's exclusive end line.
|
|
1026 *
|
|
1027 * @param range the range to check whether it contains <code>line</code>
|
|
1028 * @param line the line the line
|
|
1029 * @return <code>true</code> if <code>range</code> contains <code>line</code>,
|
|
1030 * <code>false</code> if not
|
|
1031 */
|
|
1032 private static bool contains(ILineRange range, int line) {
|
|
1033 return range.getStartLine() <= line && end(range) > line;
|
|
1034 }
|
|
1035
|
|
1036 /**
|
|
1037 * Computes the end index of a line range.
|
|
1038 *
|
|
1039 * @param range a line range
|
|
1040 * @return the last line (exclusive) of <code>range</code>
|
|
1041 */
|
|
1042 private static int end(ILineRange range) {
|
|
1043 return range.getStartLine() + range.getNumberOfLines();
|
|
1044 }
|
|
1045
|
|
1046 /**
|
|
1047 * Returns the visible extent of a document line range in widget lines.
|
|
1048 *
|
|
1049 * @param range the document line range
|
|
1050 * @return the visible extent of <code>range</code> in widget lines
|
|
1051 */
|
|
1052 private ILineRange modelLinesToWidgetLines(ILineRange range) {
|
|
1053 int widgetStartLine= -1;
|
|
1054 int widgetEndLine= -1;
|
|
1055 if (fViewer instanceof ITextViewerExtension5) {
|
|
1056 ITextViewerExtension5 extension= (ITextViewerExtension5) fViewer;
|
|
1057 int modelEndLine= end(range);
|
|
1058 for (int modelLine= range.getStartLine(); modelLine < modelEndLine; modelLine++) {
|
|
1059 int widgetLine= extension.modelLine2WidgetLine(modelLine);
|
|
1060 if (widgetLine !is -1) {
|
|
1061 if (widgetStartLine is -1)
|
|
1062 widgetStartLine= widgetLine;
|
|
1063 widgetEndLine= widgetLine;
|
|
1064 }
|
|
1065 }
|
|
1066 } else {
|
|
1067 IRegion region= fViewer.getVisibleRegion();
|
|
1068 IDocument document= fViewer.getDocument();
|
|
1069 try {
|
|
1070 int visibleStartLine= document.getLineOfOffset(region.getOffset());
|
|
1071 int visibleEndLine= document.getLineOfOffset(region.getOffset() + region.getLength());
|
|
1072 widgetStartLine= Math.max(0, range.getStartLine() - visibleStartLine);
|
|
1073 widgetEndLine= Math.min(visibleEndLine, end(range) - 1);
|
|
1074 } catch (BadLocationException x) {
|
|
1075 x.printStackTrace();
|
|
1076 // ignore and return null
|
|
1077 }
|
|
1078 }
|
|
1079 if (widgetStartLine is -1 || widgetEndLine is -1)
|
|
1080 return null;
|
|
1081 return new LineRange(widgetStartLine, widgetEndLine - widgetStartLine + 1);
|
|
1082 }
|
|
1083
|
|
1084 /**
|
|
1085 * Returns the revision hover.
|
|
1086 *
|
|
1087 * @return the revision hover
|
|
1088 */
|
|
1089 public IAnnotationHover getHover() {
|
|
1090 return fHover;
|
|
1091 }
|
|
1092
|
|
1093 /**
|
|
1094 * Computes and returns the bounds of the rectangle corresponding to a widget line range. The
|
|
1095 * rectangle is in pixel coordinates relative to the text widget's
|
|
1096 * {@link StyledText#getClientArea() client area} and has the width of the ruler.
|
|
1097 *
|
|
1098 * @param range the widget line range
|
|
1099 * @return the box bounds corresponding to <code>range</code>
|
|
1100 */
|
|
1101 private Rectangle computeBoxBounds(ILineRange range) {
|
|
1102 int y1= fWidget.getLinePixel(range.getStartLine());
|
|
1103 int y2= fWidget.getLinePixel(range.getStartLine() + range.getNumberOfLines());
|
|
1104
|
|
1105 return new Rectangle(0, y1, getWidth(), y2 - y1 - 1);
|
|
1106 }
|
|
1107
|
|
1108 /**
|
|
1109 * Shows (or hides) the overview annotations.
|
|
1110 */
|
|
1111 private void updateOverviewAnnotations() {
|
|
1112 if (fAnnotationModel is null)
|
|
1113 return;
|
|
1114
|
|
1115 Revision revision= fFocusRevision !is null ? fFocusRevision : fSelectedRevision;
|
|
1116
|
|
1117 Map added= null;
|
|
1118 if (revision !is null) {
|
|
1119 added= new HashMap();
|
|
1120 for (Iterator it= revision.getRegions().iterator(); it.hasNext();) {
|
|
1121 RevisionRange range= (RevisionRange) it.next();
|
|
1122 try {
|
|
1123 IRegion charRegion= toCharRegion(range);
|
|
1124 Position position= new Position(charRegion.getOffset(), charRegion.getLength());
|
|
1125 Annotation annotation= new RevisionAnnotation(revision.getId());
|
|
1126 added.put(annotation, position);
|
|
1127 } catch (BadLocationException x) {
|
|
1128 // ignore - document was changed, show no annotations
|
|
1129 }
|
|
1130 }
|
|
1131 }
|
|
1132
|
|
1133 if (fAnnotationModel instanceof IAnnotationModelExtension) {
|
|
1134 IAnnotationModelExtension ext= (IAnnotationModelExtension) fAnnotationModel;
|
|
1135 ext.replaceAnnotations((Annotation[]) fAnnotations.toArray(new Annotation[fAnnotations.size()]), added);
|
|
1136 } else {
|
|
1137 for (Iterator it= fAnnotations.iterator(); it.hasNext();) {
|
|
1138 Annotation annotation= (Annotation) it.next();
|
|
1139 fAnnotationModel.removeAnnotation(annotation);
|
|
1140 }
|
|
1141 if (added !is null) {
|
|
1142 for (Iterator it= added.entrySet().iterator(); it.hasNext();) {
|
|
1143 Entry entry= (Entry) it.next();
|
|
1144 fAnnotationModel.addAnnotation((Annotation) entry.getKey(), (Position) entry.getValue());
|
|
1145 }
|
|
1146 }
|
|
1147 }
|
|
1148 fAnnotations.clear();
|
|
1149 if (added !is null)
|
|
1150 fAnnotations.addAll(added.keySet());
|
|
1151
|
|
1152 }
|
|
1153
|
|
1154 /**
|
|
1155 * Returns the character offset based region of a line range.
|
|
1156 *
|
|
1157 * @param lines the line range to convert
|
|
1158 * @return the character offset range corresponding to <code>range</code>
|
|
1159 * @throws BadLocationException if the line range is not within the document bounds
|
|
1160 */
|
|
1161 private IRegion toCharRegion(ILineRange lines) throws BadLocationException {
|
|
1162 IDocument document= fViewer.getDocument();
|
|
1163 int offset= document.getLineOffset(lines.getStartLine());
|
|
1164 int nextLine= end(lines);
|
|
1165 int endOffset;
|
|
1166 if (nextLine >= document.getNumberOfLines())
|
|
1167 endOffset= document.getLength();
|
|
1168 else
|
|
1169 endOffset= document.getLineOffset(nextLine);
|
|
1170 return new Region(offset, endOffset - offset);
|
|
1171 }
|
|
1172
|
|
1173 /**
|
|
1174 * Handles the selection of a revision and informs listeners.
|
|
1175 *
|
|
1176 * @param revision the selected revision, <code>null</code> for none
|
|
1177 */
|
|
1178 void handleRevisionSelected(Revision revision) {
|
|
1179 fSelectedRevision= revision;
|
|
1180 fRevisionSelectionProvider.revisionSelected(revision);
|
|
1181 updateOverviewAnnotations();
|
|
1182 postRedraw();
|
|
1183 }
|
|
1184
|
|
1185 /**
|
|
1186 * Handles the selection of a revision id and informs listeners
|
|
1187 *
|
|
1188 * @param id the selected revision id
|
|
1189 */
|
|
1190 void handleRevisionSelected(String id) {
|
|
1191 Assert.isLegal(id !is null);
|
|
1192 if (fRevisionInfo is null)
|
|
1193 return;
|
|
1194
|
|
1195 for (Iterator it= fRevisionInfo.getRevisions().iterator(); it.hasNext();) {
|
|
1196 Revision revision= (Revision) it.next();
|
|
1197 if (id.equals(revision.getId())) {
|
|
1198 handleRevisionSelected(revision);
|
|
1199 return;
|
|
1200 }
|
|
1201 }
|
|
1202
|
|
1203 // clear selection if it does not exist
|
|
1204 handleRevisionSelected((Revision) null);
|
|
1205 }
|
|
1206
|
|
1207 /**
|
|
1208 * Returns the selection provider.
|
|
1209 *
|
|
1210 * @return the selection provider
|
|
1211 */
|
|
1212 public RevisionSelectionProvider getRevisionSelectionProvider() {
|
|
1213 return fRevisionSelectionProvider;
|
|
1214 }
|
|
1215
|
|
1216 /**
|
|
1217 * Updates the focus line with a new line.
|
|
1218 *
|
|
1219 * @param line the new focus line, -1 for no focus
|
|
1220 */
|
|
1221 private void updateFocusLine(int line) {
|
|
1222 if (fFocusLine !is line)
|
|
1223 onFocusLineChanged(fFocusLine, line);
|
|
1224 }
|
|
1225
|
|
1226 /**
|
|
1227 * Handles a changing focus line.
|
|
1228 *
|
|
1229 * @param previousLine the old focus line (-1 for no focus)
|
|
1230 * @param nextLine the new focus line (-1 for no focus)
|
|
1231 */
|
|
1232 private void onFocusLineChanged(int previousLine, int nextLine) {
|
|
1233 if (DEBUG)
|
|
1234 System.out.println("line: " + previousLine + " > " + nextLine); //$NON-NLS-1$ //$NON-NLS-2$
|
|
1235 fFocusLine= nextLine;
|
|
1236 RevisionRange region= getRange(nextLine);
|
|
1237 updateFocusRange(region);
|
|
1238 }
|
|
1239
|
|
1240 /**
|
|
1241 * Updates the focus range.
|
|
1242 *
|
|
1243 * @param range the new focus range, <code>null</code> for no focus
|
|
1244 */
|
|
1245 private void updateFocusRange(RevisionRange range) {
|
|
1246 if (range !is fFocusRange)
|
|
1247 onFocusRangeChanged(fFocusRange, range);
|
|
1248 }
|
|
1249
|
|
1250 /**
|
|
1251 * Handles a changing focus range.
|
|
1252 *
|
|
1253 * @param previousRange the old focus range (<code>null</code> for no focus)
|
|
1254 * @param nextRange the new focus range (<code>null</code> for no focus)
|
|
1255 */
|
|
1256 private void onFocusRangeChanged(RevisionRange previousRange, RevisionRange nextRange) {
|
|
1257 if (DEBUG)
|
|
1258 System.out.println("range: " + previousRange + " > " + nextRange); //$NON-NLS-1$ //$NON-NLS-2$
|
|
1259 fFocusRange= nextRange;
|
|
1260 Revision revision= nextRange is null ? null : nextRange.getRevision();
|
|
1261 updateFocusRevision(revision);
|
|
1262 }
|
|
1263
|
|
1264 private void updateFocusRevision(Revision revision) {
|
|
1265 if (fFocusRevision !is revision)
|
|
1266 onFocusRevisionChanged(fFocusRevision, revision);
|
|
1267 }
|
|
1268
|
|
1269 /**
|
|
1270 * Handles a changing focus revision.
|
|
1271 *
|
|
1272 * @param previousRevision the old focus revision (<code>null</code> for no focus)
|
|
1273 * @param nextRevision the new focus revision (<code>null</code> for no focus)
|
|
1274 */
|
|
1275 private void onFocusRevisionChanged(Revision previousRevision, Revision nextRevision) {
|
|
1276 if (DEBUG)
|
|
1277 System.out.println("revision: " + previousRevision + " > " + nextRevision); //$NON-NLS-1$ //$NON-NLS-2$
|
|
1278 fFocusRevision= nextRevision;
|
|
1279 uninstallWheelHandler();
|
|
1280 installWheelHandler();
|
|
1281 updateOverviewAnnotations();
|
|
1282 redraw(); // pick up new highlights
|
|
1283 }
|
|
1284
|
|
1285 /**
|
|
1286 * Uninstalls the mouse wheel handler.
|
|
1287 */
|
|
1288 private void uninstallWheelHandler() {
|
|
1289 fControl.removeListener(DWT.MouseWheel, fMouseHandler);
|
|
1290 fWheelHandlerInstalled= false;
|
|
1291 }
|
|
1292
|
|
1293 /**
|
|
1294 * Installs the mouse wheel handler.
|
|
1295 */
|
|
1296 private void installWheelHandler() {
|
|
1297 if (fFocusRevision !is null && !fWheelHandlerInstalled) {
|
|
1298 //FIXME: does not work on Windows, because Canvas cannot get focus and therefore does not send out mouse wheel events:
|
|
1299 //https://bugs.eclipse.org/bugs/show_bug.cgi?id=81189
|
|
1300 //see also https://bugs.eclipse.org/bugs/show_bug.cgi?id=75766
|
|
1301 fControl.addListener(DWT.MouseWheel, fMouseHandler);
|
|
1302 fWheelHandlerInstalled= true;
|
|
1303 }
|
|
1304 }
|
|
1305
|
|
1306 /**
|
|
1307 * Handles a mouse wheel event.
|
|
1308 *
|
|
1309 * @param event the mouse wheel event
|
|
1310 */
|
|
1311 private void handleMouseWheel(Event event) {
|
|
1312 bool up= event.count > 0;
|
|
1313 int documentHoverLine= fFocusLine;
|
|
1314
|
|
1315 ILineRange nextWidgetRange= null;
|
|
1316 ILineRange last= null;
|
|
1317 List ranges= fFocusRevision.getRegions();
|
|
1318 if (up) {
|
|
1319 for (Iterator it= ranges.iterator(); it.hasNext();) {
|
|
1320 RevisionRange range= (RevisionRange) it.next();
|
|
1321 ILineRange widgetRange= modelLinesToWidgetLines(range);
|
|
1322 if (contains(range, documentHoverLine)) {
|
|
1323 nextWidgetRange= last;
|
|
1324 break;
|
|
1325 }
|
|
1326 if (widgetRange !is null)
|
|
1327 last= widgetRange;
|
|
1328 }
|
|
1329 } else {
|
|
1330 for (ListIterator it= ranges.listIterator(ranges.size()); it.hasPrevious();) {
|
|
1331 RevisionRange range= (RevisionRange) it.previous();
|
|
1332 ILineRange widgetRange= modelLinesToWidgetLines(range);
|
|
1333 if (contains(range, documentHoverLine)) {
|
|
1334 nextWidgetRange= last;
|
|
1335 break;
|
|
1336 }
|
|
1337 if (widgetRange !is null)
|
|
1338 last= widgetRange;
|
|
1339 }
|
|
1340 }
|
|
1341
|
|
1342 if (nextWidgetRange is null)
|
|
1343 return;
|
|
1344
|
|
1345 int widgetCurrentFocusLine= modelLinesToWidgetLines(new LineRange(documentHoverLine, 1)).getStartLine();
|
|
1346 int widgetNextFocusLine= nextWidgetRange.getStartLine();
|
|
1347 int newTopPixel= fWidget.getTopPixel() + JFaceTextUtil.computeLineHeight(fWidget, widgetCurrentFocusLine, widgetNextFocusLine, widgetNextFocusLine - widgetCurrentFocusLine);
|
|
1348 fWidget.setTopPixel(newTopPixel);
|
|
1349 if (newTopPixel < 0) {
|
|
1350 Point cursorLocation= fWidget.getDisplay().getCursorLocation();
|
|
1351 cursorLocation.y+= newTopPixel;
|
|
1352 fWidget.getDisplay().setCursorLocation(cursorLocation);
|
|
1353 } else {
|
|
1354 int topPixel= fWidget.getTopPixel();
|
|
1355 if (topPixel < newTopPixel) {
|
|
1356 Point cursorLocation= fWidget.getDisplay().getCursorLocation();
|
|
1357 cursorLocation.y+= newTopPixel - topPixel;
|
|
1358 fWidget.getDisplay().setCursorLocation(cursorLocation);
|
|
1359 }
|
|
1360 }
|
|
1361 updateFocusLine(toDocumentLineNumber(fWidget.toControl(fWidget.getDisplay().getCursorLocation()).y));
|
|
1362 immediateUpdate();
|
|
1363 }
|
|
1364
|
|
1365 /**
|
|
1366 * Triggers a redraw in the display thread.
|
|
1367 */
|
|
1368 private final void postRedraw() {
|
|
1369 if (isConnected() && !fControl.isDisposed()) {
|
|
1370 Display d= fControl.getDisplay();
|
|
1371 if (d !is null) {
|
|
1372 d.asyncExec(new Runnable() {
|
|
1373 public void run() {
|
|
1374 redraw();
|
|
1375 }
|
|
1376 });
|
|
1377 }
|
|
1378 }
|
|
1379 }
|
|
1380
|
|
1381 /**
|
|
1382 * Translates a y coordinate in the pixel coordinates of the column's control to a document line
|
|
1383 * number.
|
|
1384 *
|
|
1385 * @param y the y coordinate
|
|
1386 * @return the corresponding document line, -1 for no line
|
|
1387 * @see CompositeRuler#toDocumentLineNumber(int)
|
|
1388 */
|
|
1389 private int toDocumentLineNumber(int y) {
|
|
1390 return fParentRuler.toDocumentLineNumber(y);
|
|
1391 }
|
|
1392
|
|
1393 /**
|
|
1394 * Triggers redrawing of the column.
|
|
1395 */
|
|
1396 private void redraw() {
|
|
1397 fColumn.redraw();
|
|
1398 }
|
|
1399
|
|
1400 /**
|
|
1401 * Triggers immediate redrawing of the entire column - use with care.
|
|
1402 */
|
|
1403 private void immediateUpdate() {
|
|
1404 fParentRuler.immediateUpdate();
|
|
1405 }
|
|
1406
|
|
1407 /**
|
|
1408 * Returns the width of the column.
|
|
1409 *
|
|
1410 * @return the width of the column
|
|
1411 */
|
|
1412 private int getWidth() {
|
|
1413 return fColumn.getWidth();
|
|
1414 }
|
|
1415
|
|
1416 /**
|
|
1417 * Returns the System background color for list widgets.
|
|
1418 *
|
|
1419 * @return the System background color for list widgets
|
|
1420 */
|
|
1421 private Color getBackground() {
|
|
1422 if (fBackground is null)
|
|
1423 return fWidget.getDisplay().getSystemColor(DWT.COLOR_LIST_BACKGROUND);
|
|
1424 return fBackground;
|
|
1425 }
|
|
1426
|
|
1427 /**
|
|
1428 * Sets the hover later returned by {@link #getHover()}.
|
|
1429 *
|
|
1430 * @param hover the hover
|
|
1431 */
|
|
1432 public void setHover(IAnnotationHover hover) {
|
|
1433 // TODO ignore for now - must make revision hover settable from outside
|
|
1434 }
|
|
1435
|
|
1436 /**
|
|
1437 * Returns <code>true</code> if the receiver can provide a hover for a certain document line.
|
|
1438 *
|
|
1439 * @param activeLine the document line of interest
|
|
1440 * @return <code>true</code> if the receiver can provide a hover
|
|
1441 */
|
|
1442 public bool hasHover(int activeLine) {
|
|
1443 return fViewer instanceof ISourceViewer && fHover.getHoverLineRange((ISourceViewer) fViewer, activeLine) !is null;
|
|
1444 }
|
|
1445
|
|
1446 /**
|
|
1447 * Returns the revision at a certain document offset, or <code>null</code> for none.
|
|
1448 *
|
|
1449 * @param offset the document offset
|
|
1450 * @return the revision at offset, or <code>null</code> for none
|
|
1451 */
|
|
1452 Revision getRevision(int offset) {
|
|
1453 IDocument document= fViewer.getDocument();
|
|
1454 int line;
|
|
1455 try {
|
|
1456 line= document.getLineOfOffset(offset);
|
|
1457 } catch (BadLocationException x) {
|
|
1458 return null;
|
|
1459 }
|
|
1460 if (line !is -1) {
|
|
1461 RevisionRange range= getRange(line);
|
|
1462 if (range !is null)
|
|
1463 return range.getRevision();
|
|
1464 }
|
|
1465 return null;
|
|
1466 }
|
|
1467
|
|
1468 /**
|
|
1469 * Returns <code>true</code> if a revision model has been set, <code>false</code> otherwise.
|
|
1470 *
|
|
1471 * @return <code>true</code> if a revision model has been set, <code>false</code> otherwise
|
|
1472 */
|
|
1473 public bool hasInformation() {
|
|
1474 return fRevisionInfo !is null;
|
|
1475 }
|
|
1476
|
|
1477 /**
|
|
1478 * Returns the width in chars required to display information.
|
|
1479 *
|
|
1480 * @return the width in chars required to display information
|
|
1481 * @since 3.3
|
|
1482 */
|
|
1483 public int getRequiredWidth() {
|
|
1484 if (fRequiredWidth is -1) {
|
|
1485 if (hasInformation() && (fShowRevision || fShowAuthor)) {
|
|
1486 int revisionWidth= 0;
|
|
1487 int authorWidth= 0;
|
|
1488 for (Iterator it= fRevisionInfo.getRevisions().iterator(); it.hasNext();) {
|
|
1489 Revision revision= (Revision) it.next();
|
|
1490 revisionWidth= Math.max(revisionWidth, revision.getId().length());
|
|
1491 authorWidth= Math.max(authorWidth, revision.getAuthor().length());
|
|
1492 }
|
|
1493 fRevisionIdChars= revisionWidth + 1;
|
|
1494 if (fShowAuthor && fShowRevision)
|
|
1495 fRequiredWidth= revisionWidth + authorWidth + 2;
|
|
1496 else if (fShowAuthor)
|
|
1497 fRequiredWidth= authorWidth + 1;
|
|
1498 else
|
|
1499 fRequiredWidth= revisionWidth + 1;
|
|
1500 } else {
|
|
1501 fRequiredWidth= 0;
|
|
1502 }
|
|
1503 }
|
|
1504 return fRequiredWidth;
|
|
1505 }
|
|
1506
|
|
1507 /**
|
|
1508 * Enables showing the revision id.
|
|
1509 *
|
|
1510 * @param show <code>true</code> to show the revision, <code>false</code> to hide it
|
|
1511 */
|
|
1512 public void showRevisionId(bool show) {
|
|
1513 if (fShowRevision !is show) {
|
|
1514 fRequiredWidth= -1;
|
|
1515 fRevisionIdChars= 0;
|
|
1516 fShowRevision= show;
|
|
1517 postRedraw();
|
|
1518 }
|
|
1519 }
|
|
1520
|
|
1521 /**
|
|
1522 * Enables showing the revision author.
|
|
1523 *
|
|
1524 * @param show <code>true</code> to show the author, <code>false</code> to hide it
|
|
1525 */
|
|
1526 public void showRevisionAuthor(bool show) {
|
|
1527 if (fShowAuthor !is show) {
|
|
1528 fRequiredWidth= -1;
|
|
1529 fRevisionIdChars= 0;
|
|
1530 fShowAuthor= show;
|
|
1531 postRedraw();
|
|
1532 }
|
|
1533 }
|
|
1534
|
|
1535 /**
|
|
1536 * Adds a revision listener.
|
|
1537 *
|
|
1538 * @param listener the listener
|
|
1539 * @since 3.3
|
|
1540 */
|
|
1541 public void addRevisionListener(IRevisionListener listener) {
|
|
1542 fRevisionListeners.add(listener);
|
|
1543 }
|
|
1544
|
|
1545 /**
|
|
1546 * Removes a revision listener.
|
|
1547 *
|
|
1548 * @param listener the listener
|
|
1549 * @since 3.3
|
|
1550 */
|
|
1551 public void removeRevisionListener(IRevisionListener listener) {
|
|
1552 fRevisionListeners.remove(listener);
|
|
1553 }
|
|
1554
|
|
1555 /**
|
|
1556 * Informs the revision listeners about a change.
|
|
1557 *
|
|
1558 * @since 3.3
|
|
1559 */
|
|
1560 private void informListeners() {
|
|
1561 if (fRevisionInfo is null || fRevisionListeners.isEmpty())
|
|
1562 return;
|
|
1563
|
|
1564 RevisionEvent event= new RevisionEvent(fRevisionInfo);
|
|
1565 Object[] listeners= fRevisionListeners.getListeners();
|
|
1566 for (int i= 0; i < listeners.length; i++) {
|
|
1567 IRevisionListener listener= (IRevisionListener) listeners[i];
|
|
1568 listener.revisionInformationChanged(event);
|
|
1569 }
|
|
1570 }
|
|
1571 }
|