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