Mercurial > projects > dwt-addons
comparison dwtx/jface/text/WhitespaceCharacterPainter.d @ 129:eb30df5ca28b
Added JFace Text sources
author | Frank Benoit <benoit@tionex.de> |
---|---|
date | Sat, 23 Aug 2008 19:10:48 +0200 |
parents | |
children | c4fb132a086c |
comparison
equal
deleted
inserted
replaced
128:8df1d4193877 | 129:eb30df5ca28b |
---|---|
1 /******************************************************************************* | |
2 * Copyright (c) 2006, 2007 Wind River Systems, Inc. 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 * Anton Leherbauer (Wind River Systems) - initial API and implementation - https://bugs.eclipse.org/bugs/show_bug.cgi?id=22712 | |
10 * Anton Leherbauer (Wind River Systems) - [painting] Long lines take too long to display when "Show Whitespace Characters" is enabled - https://bugs.eclipse.org/bugs/show_bug.cgi?id=196116 | |
11 * Anton Leherbauer (Wind River Systems) - [painting] Whitespace characters not drawn when scrolling to right slowly - https://bugs.eclipse.org/bugs/show_bug.cgi?id=206633 | |
12 * Port to the D programming language: | |
13 * Frank Benoit <benoit@tionex.de> | |
14 *******************************************************************************/ | |
15 module dwtx.jface.text.WhitespaceCharacterPainter; | |
16 | |
17 import dwt.dwthelper.utils; | |
18 | |
19 import dwt.custom.StyleRange; | |
20 import dwt.custom.StyledText; | |
21 import dwt.custom.StyledTextContent; | |
22 import dwt.events.PaintEvent; | |
23 import dwt.events.PaintListener; | |
24 import dwt.graphics.Color; | |
25 import dwt.graphics.FontMetrics; | |
26 import dwt.graphics.GC; | |
27 import dwt.graphics.Point; | |
28 | |
29 | |
30 /** | |
31 * A painter for drawing visible characters for (invisible) whitespace | |
32 * characters. | |
33 * | |
34 * @since 3.3 | |
35 */ | |
36 public class WhitespaceCharacterPainter : IPainter, PaintListener { | |
37 | |
38 private static final char SPACE_SIGN= '\u00b7'; | |
39 private static final char IDEOGRAPHIC_SPACE_SIGN= '\u00b0'; | |
40 private static final char TAB_SIGN= '\u00bb'; | |
41 private static final char CARRIAGE_RETURN_SIGN= '\u00a4'; | |
42 private static final char LINE_FEED_SIGN= '\u00b6'; | |
43 | |
44 /** Indicates whether this painter is active. */ | |
45 private bool fIsActive= false; | |
46 /** The source viewer this painter is attached to. */ | |
47 private ITextViewer fTextViewer; | |
48 /** The viewer's widget. */ | |
49 private StyledText fTextWidget; | |
50 /** Tells whether the advanced graphics sub system is available. */ | |
51 private bool fIsAdvancedGraphicsPresent; | |
52 | |
53 /** | |
54 * Creates a new painter for the given text viewer. | |
55 * | |
56 * @param textViewer the text viewer the painter should be attached to | |
57 */ | |
58 public WhitespaceCharacterPainter(ITextViewer textViewer) { | |
59 super(); | |
60 fTextViewer= textViewer; | |
61 fTextWidget= textViewer.getTextWidget(); | |
62 GC gc= new GC(fTextWidget); | |
63 gc.setAdvanced(true); | |
64 fIsAdvancedGraphicsPresent= gc.getAdvanced(); | |
65 gc.dispose(); | |
66 } | |
67 | |
68 /* | |
69 * @see dwtx.jface.text.IPainter#dispose() | |
70 */ | |
71 public void dispose() { | |
72 fTextViewer= null; | |
73 fTextWidget= null; | |
74 } | |
75 | |
76 /* | |
77 * @see dwtx.jface.text.IPainter#paint(int) | |
78 */ | |
79 public void paint(int reason) { | |
80 IDocument document= fTextViewer.getDocument(); | |
81 if (document is null) { | |
82 deactivate(false); | |
83 return; | |
84 } | |
85 if (!fIsActive) { | |
86 fIsActive= true; | |
87 fTextWidget.addPaintListener(this); | |
88 redrawAll(); | |
89 } else if (reason is CONFIGURATION || reason is INTERNAL) { | |
90 redrawAll(); | |
91 } else if (reason is TEXT_CHANGE) { | |
92 // redraw current line only | |
93 try { | |
94 IRegion lineRegion = | |
95 document.getLineInformationOfOffset(getDocumentOffset(fTextWidget.getCaretOffset())); | |
96 int widgetOffset= getWidgetOffset(lineRegion.getOffset()); | |
97 int charCount= fTextWidget.getCharCount(); | |
98 int redrawLength= Math.min(lineRegion.getLength(), charCount - widgetOffset); | |
99 if (widgetOffset >= 0 && redrawLength > 0) { | |
100 fTextWidget.redrawRange(widgetOffset, redrawLength, true); | |
101 } | |
102 } catch (BadLocationException e) { | |
103 // ignore | |
104 } | |
105 } | |
106 } | |
107 | |
108 /* | |
109 * @see dwtx.jface.text.IPainter#deactivate(bool) | |
110 */ | |
111 public void deactivate(bool redraw) { | |
112 if (fIsActive) { | |
113 fIsActive= false; | |
114 fTextWidget.removePaintListener(this); | |
115 if (redraw) { | |
116 redrawAll(); | |
117 } | |
118 } | |
119 } | |
120 | |
121 /* | |
122 * @see dwtx.jface.text.IPainter#setPositionManager(dwtx.jface.text.IPaintPositionManager) | |
123 */ | |
124 public void setPositionManager(IPaintPositionManager manager) { | |
125 // no need for a position manager | |
126 } | |
127 | |
128 /* | |
129 * @see dwt.events.PaintListener#paintControl(dwt.events.PaintEvent) | |
130 */ | |
131 public void paintControl(PaintEvent event) { | |
132 if (fTextWidget !is null) { | |
133 handleDrawRequest(event.gc, event.x, event.y, event.width, event.height); | |
134 } | |
135 } | |
136 | |
137 /** | |
138 * Draw characters in view range. | |
139 * | |
140 * @param gc | |
141 * @param x | |
142 * @param y | |
143 * @param w | |
144 * @param h | |
145 */ | |
146 private void handleDrawRequest(GC gc, int x, int y, int w, int h) { | |
147 int startLine= fTextWidget.getLineIndex(y); | |
148 int endLine= fTextWidget.getLineIndex(y + h - 1); | |
149 if (startLine <= endLine && startLine < fTextWidget.getLineCount()) { | |
150 if (fIsAdvancedGraphicsPresent) { | |
151 int alpha= gc.getAlpha(); | |
152 gc.setAlpha(100); | |
153 drawLineRange(gc, startLine, endLine, x, w); | |
154 gc.setAlpha(alpha); | |
155 } else | |
156 drawLineRange(gc, startLine, endLine, x, w); | |
157 } | |
158 } | |
159 | |
160 /** | |
161 * Draw the given line range. | |
162 * | |
163 * @param gc | |
164 * @param startLine first line number | |
165 * @param endLine last line number (inclusive) | |
166 * @param x the X-coordinate of the drawing range | |
167 * @param w the width of the drawing range | |
168 */ | |
169 private void drawLineRange(GC gc, int startLine, int endLine, int x, int w) { | |
170 final int viewPortWidth= fTextWidget.getClientArea().width; | |
171 for (int line= startLine; line <= endLine; line++) { | |
172 int lineOffset= fTextWidget.getOffsetAtLine(line); | |
173 // line end offset including line delimiter | |
174 int lineEndOffset; | |
175 if (line < fTextWidget.getLineCount() - 1) { | |
176 lineEndOffset= fTextWidget.getOffsetAtLine(line + 1); | |
177 } else { | |
178 lineEndOffset= fTextWidget.getCharCount(); | |
179 } | |
180 // line length excluding line delimiter | |
181 int lineLength= lineEndOffset - lineOffset; | |
182 while (lineLength > 0) { | |
183 char c= fTextWidget.getTextRange(lineOffset + lineLength - 1, 1).charAt(0); | |
184 if (c !is '\r' && c !is '\n') { | |
185 break; | |
186 } | |
187 --lineLength; | |
188 } | |
189 // compute coordinates of last character on line | |
190 Point endOfLine= fTextWidget.getLocationAtOffset(lineOffset + lineLength); | |
191 if (x - endOfLine.x > viewPortWidth) { | |
192 // line is not visible | |
193 continue; | |
194 } | |
195 // Y-coordinate of line | |
196 int y= fTextWidget.getLinePixel(line); | |
197 // compute first visible char offset | |
198 int startOffset; | |
199 try { | |
200 startOffset= fTextWidget.getOffsetAtLocation(new Point(x, y)) - 1; | |
201 if (startOffset - 2 <= lineOffset) { | |
202 startOffset= lineOffset; | |
203 } | |
204 } catch (IllegalArgumentException iae) { | |
205 startOffset= lineOffset; | |
206 } | |
207 // compute last visible char offset | |
208 int endOffset; | |
209 if (x + w >= endOfLine.x) { | |
210 // line end is visible | |
211 endOffset= lineEndOffset; | |
212 } else { | |
213 try { | |
214 endOffset= fTextWidget.getOffsetAtLocation(new Point(x + w - 1, y)) + 1; | |
215 if (endOffset + 2 >= lineEndOffset) { | |
216 endOffset= lineEndOffset; | |
217 } | |
218 } catch (IllegalArgumentException iae) { | |
219 endOffset= lineEndOffset; | |
220 } | |
221 } | |
222 // draw character range | |
223 if (endOffset > startOffset) { | |
224 drawCharRange(gc, startOffset, endOffset); | |
225 } | |
226 } | |
227 } | |
228 | |
229 /** | |
230 * Draw characters of content range. | |
231 * | |
232 * @param gc the GC | |
233 * @param startOffset inclusive start index | |
234 * @param endOffset exclusive end index | |
235 */ | |
236 private void drawCharRange(GC gc, int startOffset, int endOffset) { | |
237 StyledTextContent content= fTextWidget.getContent(); | |
238 int length= endOffset - startOffset; | |
239 String text= content.getTextRange(startOffset, length); | |
240 StyleRange styleRange= null; | |
241 Color fg= null; | |
242 Point selection= fTextWidget.getSelection(); | |
243 StringBuffer visibleChar= new StringBuffer(10); | |
244 for (int textOffset= 0; textOffset <= length; ++textOffset) { | |
245 int delta= 0; | |
246 bool eol= false; | |
247 if (textOffset < length) { | |
248 delta= 1; | |
249 char c= text.charAt(textOffset); | |
250 switch (c) { | |
251 case ' ' : | |
252 visibleChar.append(SPACE_SIGN); | |
253 // 'continue' would improve performance but may produce drawing errors | |
254 // for long runs of space if width of space and dot differ | |
255 break; | |
256 case '\u3000' : // ideographic whitespace | |
257 visibleChar.append(IDEOGRAPHIC_SPACE_SIGN); | |
258 // 'continue' would improve performance but may produce drawing errors | |
259 // for long runs of space if width of space and dot differ | |
260 break; | |
261 case '\t' : | |
262 visibleChar.append(TAB_SIGN); | |
263 break; | |
264 case '\r' : | |
265 visibleChar.append(CARRIAGE_RETURN_SIGN); | |
266 if (textOffset >= length - 1 || text.charAt(textOffset + 1) !is '\n') { | |
267 eol= true; | |
268 break; | |
269 } | |
270 continue; | |
271 case '\n' : | |
272 visibleChar.append(LINE_FEED_SIGN); | |
273 eol= true; | |
274 break; | |
275 default : | |
276 delta= 0; | |
277 break; | |
278 } | |
279 } | |
280 if (visibleChar.length() > 0) { | |
281 int widgetOffset= startOffset + textOffset - visibleChar.length() + delta; | |
282 if (!eol || !isFoldedLine(content.getLineAtOffset(widgetOffset))) { | |
283 if (widgetOffset >= selection.x && widgetOffset < selection.y) { | |
284 fg= fTextWidget.getSelectionForeground(); | |
285 } else if (styleRange is null || styleRange.start + styleRange.length <= widgetOffset) { | |
286 styleRange= fTextWidget.getStyleRangeAtOffset(widgetOffset); | |
287 if (styleRange is null || styleRange.foreground is null) { | |
288 fg= fTextWidget.getForeground(); | |
289 } else { | |
290 fg= styleRange.foreground; | |
291 } | |
292 } | |
293 draw(gc, widgetOffset, visibleChar.toString(), fg); | |
294 } | |
295 visibleChar.delete(0, visibleChar.length()); | |
296 } | |
297 } | |
298 } | |
299 | |
300 /** | |
301 * Check if the given widget line is a folded line. | |
302 * | |
303 * @param widgetLine the widget line number | |
304 * @return <code>true</code> if the line is folded | |
305 */ | |
306 private bool isFoldedLine(int widgetLine) { | |
307 if (fTextViewer instanceof ITextViewerExtension5) { | |
308 ITextViewerExtension5 extension= (ITextViewerExtension5)fTextViewer; | |
309 int modelLine= extension.widgetLine2ModelLine(widgetLine); | |
310 int widgetLine2= extension.modelLine2WidgetLine(modelLine + 1); | |
311 return widgetLine2 is -1; | |
312 } | |
313 return false; | |
314 } | |
315 | |
316 /** | |
317 * Redraw all of the text widgets visible content. | |
318 */ | |
319 private void redrawAll() { | |
320 fTextWidget.redraw(); | |
321 } | |
322 | |
323 /** | |
324 * Draw string at widget offset. | |
325 * | |
326 * @param gc | |
327 * @param offset the widget offset | |
328 * @param s the string to be drawn | |
329 * @param fg the foreground color | |
330 */ | |
331 private void draw(GC gc, int offset, String s, Color fg) { | |
332 // Compute baseline delta (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=165640) | |
333 int baseline= fTextWidget.getBaseline(offset); | |
334 FontMetrics fontMetrics= gc.getFontMetrics(); | |
335 int fontBaseline= fontMetrics.getAscent() + fontMetrics.getLeading(); | |
336 int baslineDelta= baseline - fontBaseline; | |
337 | |
338 Point pos= fTextWidget.getLocationAtOffset(offset); | |
339 gc.setForeground(fg); | |
340 gc.drawString(s, pos.x, pos.y + baslineDelta, true); | |
341 } | |
342 | |
343 /** | |
344 * Convert a document offset to the corresponding widget offset. | |
345 * | |
346 * @param documentOffset | |
347 * @return widget offset | |
348 */ | |
349 private int getWidgetOffset(int documentOffset) { | |
350 if (fTextViewer instanceof ITextViewerExtension5) { | |
351 ITextViewerExtension5 extension= (ITextViewerExtension5)fTextViewer; | |
352 return extension.modelOffset2WidgetOffset(documentOffset); | |
353 } | |
354 IRegion visible= fTextViewer.getVisibleRegion(); | |
355 int widgetOffset= documentOffset - visible.getOffset(); | |
356 if (widgetOffset > visible.getLength()) { | |
357 return -1; | |
358 } | |
359 return widgetOffset; | |
360 } | |
361 | |
362 /** | |
363 * Convert a widget offset to the corresponding document offset. | |
364 * | |
365 * @param widgetOffset | |
366 * @return document offset | |
367 */ | |
368 private int getDocumentOffset(int widgetOffset) { | |
369 if (fTextViewer instanceof ITextViewerExtension5) { | |
370 ITextViewerExtension5 extension= (ITextViewerExtension5)fTextViewer; | |
371 return extension.widgetOffset2ModelOffset(widgetOffset); | |
372 } | |
373 IRegion visible= fTextViewer.getVisibleRegion(); | |
374 if (widgetOffset > visible.getLength()) { | |
375 return -1; | |
376 } | |
377 return widgetOffset + visible.getOffset(); | |
378 } | |
379 | |
380 } |