1 | /*
|
---|
2 | * 02/24/2004
|
---|
3 | *
|
---|
4 | * SyntaxView.java - The View object used by RSyntaxTextArea when word wrap is
|
---|
5 | * disabled.
|
---|
6 | *
|
---|
7 | * This library is distributed under a modified BSD license. See the included
|
---|
8 | * RSyntaxTextArea.License.txt file for details.
|
---|
9 | */
|
---|
10 | package org.fife.ui.rsyntaxtextarea;
|
---|
11 |
|
---|
12 | import java.awt.*;
|
---|
13 | import javax.swing.event.*;
|
---|
14 | import javax.swing.text.*;
|
---|
15 |
|
---|
16 | import org.fife.ui.rsyntaxtextarea.folding.Fold;
|
---|
17 | import org.fife.ui.rsyntaxtextarea.folding.FoldManager;
|
---|
18 |
|
---|
19 |
|
---|
20 | /**
|
---|
21 | * The <code>javax.swing.text.View</code> object used by {@link RSyntaxTextArea}
|
---|
22 | * when word wrap is disabled. It implements syntax highlighting for
|
---|
23 | * programming languages using the colors and font styles specified by the
|
---|
24 | * <code>RSyntaxTextArea</code>.<p>
|
---|
25 | *
|
---|
26 | * You don't really have to do anything to use this class, as
|
---|
27 | * {@link RSyntaxTextAreaUI} automatically sets the text area's view to be
|
---|
28 | * an instance of this class if word wrap is disabled.<p>
|
---|
29 | *
|
---|
30 | * The tokens that specify how to paint the syntax-highlighted text are gleaned
|
---|
31 | * from the text area's {@link RSyntaxDocument}.
|
---|
32 | *
|
---|
33 | * @author Robert Futrell
|
---|
34 | * @version 0.3
|
---|
35 | */
|
---|
36 | public class SyntaxView extends View implements TabExpander,
|
---|
37 | TokenOrientedView, RSTAView {
|
---|
38 |
|
---|
39 | /**
|
---|
40 | * The default font used by the text area. If this changes we need to
|
---|
41 | * recalculate the longest line.
|
---|
42 | */
|
---|
43 | Font font;
|
---|
44 |
|
---|
45 | /**
|
---|
46 | * Font metrics for the current font.
|
---|
47 | */
|
---|
48 | protected FontMetrics metrics;
|
---|
49 |
|
---|
50 | /**
|
---|
51 | * The current longest line. This is used to calculate the preferred width
|
---|
52 | * of the view. Since the calculation is potentially expensive, we try to
|
---|
53 | * avoid it by stashing which line is currently the longest.
|
---|
54 | */
|
---|
55 | Element longLine;
|
---|
56 | float longLineWidth;
|
---|
57 |
|
---|
58 | private int tabSize;
|
---|
59 | protected int tabBase;
|
---|
60 |
|
---|
61 |
|
---|
62 | /**
|
---|
63 | * Cached for each paint() call so each drawLine() call has access to it.
|
---|
64 | */
|
---|
65 | private RSyntaxTextArea host;
|
---|
66 |
|
---|
67 | /**
|
---|
68 | * Cached values to speed up the painting a tad.
|
---|
69 | */
|
---|
70 | private int lineHeight = 0;
|
---|
71 | private int ascent;
|
---|
72 | private int clipStart;
|
---|
73 | private int clipEnd;
|
---|
74 |
|
---|
75 | // /**
|
---|
76 | // * The end-of-line marker.
|
---|
77 | // */
|
---|
78 | // private static final char[] eolMarker = { '.' };
|
---|
79 |
|
---|
80 |
|
---|
81 | /**
|
---|
82 | * Constructs a new <code>SyntaxView</code> wrapped around an element.
|
---|
83 | *
|
---|
84 | * @param elem The element representing the text to display.
|
---|
85 | */
|
---|
86 | public SyntaxView(Element elem) {
|
---|
87 | super(elem);
|
---|
88 | }
|
---|
89 |
|
---|
90 |
|
---|
91 | /**
|
---|
92 | * Iterate over the lines represented by the child elements
|
---|
93 | * of the element this view represents, looking for the line
|
---|
94 | * that is the longest. The <em>longLine</em> variable is updated to
|
---|
95 | * represent the longest line contained. The <em>font</em> variable
|
---|
96 | * is updated to indicate the font used to calculate the
|
---|
97 | * longest line.
|
---|
98 | */
|
---|
99 | void calculateLongestLine() {
|
---|
100 | Component c = getContainer();
|
---|
101 | font = c.getFont();
|
---|
102 | metrics = c.getFontMetrics(font);
|
---|
103 | tabSize = getTabSize() * metrics.charWidth(' ');
|
---|
104 | Element lines = getElement();
|
---|
105 | int n = lines.getElementCount();
|
---|
106 | for (int i=0; i<n; i++) {
|
---|
107 | Element line = lines.getElement(i);
|
---|
108 | float w = getLineWidth(i);
|
---|
109 | if (w > longLineWidth) {
|
---|
110 | longLineWidth = w;
|
---|
111 | longLine = line;
|
---|
112 | }
|
---|
113 | }
|
---|
114 | }
|
---|
115 |
|
---|
116 |
|
---|
117 | /**
|
---|
118 | * Gives notification from the document that attributes were changed
|
---|
119 | * in a location that this view is responsible for.
|
---|
120 | *
|
---|
121 | * @param changes the change information from the associated document
|
---|
122 | * @param a the current allocation of the view
|
---|
123 | * @param f the factory to use to rebuild if the view has children
|
---|
124 | * @see View#changedUpdate
|
---|
125 | */
|
---|
126 | public void changedUpdate(DocumentEvent changes, Shape a, ViewFactory f) {
|
---|
127 | updateDamage(changes, a, f);
|
---|
128 | }
|
---|
129 |
|
---|
130 |
|
---|
131 | /**
|
---|
132 | * Repaint the given line range.
|
---|
133 | *
|
---|
134 | * @param line0 The starting line number to repaint. This must
|
---|
135 | * be a valid line number in the model.
|
---|
136 | * @param line1 The ending line number to repaint. This must
|
---|
137 | * be a valid line number in the model.
|
---|
138 | * @param a The region allocated for the view to render into.
|
---|
139 | * @param host The component hosting the view (used to call repaint).
|
---|
140 | */
|
---|
141 | protected void damageLineRange(int line0, int line1, Shape a,
|
---|
142 | Component host) {
|
---|
143 | if (a != null) {
|
---|
144 | Rectangle area0 = lineToRect(a, line0);
|
---|
145 | Rectangle area1 = lineToRect(a, line1);
|
---|
146 | if ((area0 != null) && (area1 != null)) {
|
---|
147 | Rectangle dmg = area0.union(area1); // damage.
|
---|
148 | host.repaint(dmg.x, dmg.y, dmg.width, dmg.height);
|
---|
149 | }
|
---|
150 | else
|
---|
151 | host.repaint();
|
---|
152 | }
|
---|
153 | }
|
---|
154 |
|
---|
155 |
|
---|
156 | /**
|
---|
157 | * Draws the passed-in text using syntax highlighting for the current
|
---|
158 | * language. The tokens used to decide how to paint the syntax
|
---|
159 | * highlighting are grabbed from the text area's document.
|
---|
160 | *
|
---|
161 | * @param token The list of tokens to draw.
|
---|
162 | * @param g The graphics context in which to draw.
|
---|
163 | * @param x The x-coordinate at which to draw.
|
---|
164 | * @param y The y-coordinate at which to draw.
|
---|
165 | * @return The x-coordinate representing the end of the painted text.
|
---|
166 | */
|
---|
167 | public float drawLine(Token token, Graphics2D g, float x, float y) {
|
---|
168 |
|
---|
169 | float nextX = x; // The x-value at the end of our text.
|
---|
170 |
|
---|
171 | while (token!=null && token.isPaintable() && nextX<clipEnd) {
|
---|
172 | nextX = token.paint(g, nextX,y, host, this, clipStart);
|
---|
173 | token = token.getNextToken();
|
---|
174 | }
|
---|
175 |
|
---|
176 | // NOTE: We should re-use code from Token (paintBackground()) here,
|
---|
177 | // but don't because I'm just too lazy.
|
---|
178 | if (host.getEOLMarkersVisible()) {
|
---|
179 | g.setColor(host.getForegroundForTokenType(Token.WHITESPACE));
|
---|
180 | g.setFont(host.getFontForTokenType(Token.WHITESPACE));
|
---|
181 | g.drawString("\u00B6", nextX, y);
|
---|
182 | }
|
---|
183 |
|
---|
184 | // Return the x-coordinate at the end of the painted text.
|
---|
185 | return nextX;
|
---|
186 |
|
---|
187 | }
|
---|
188 |
|
---|
189 |
|
---|
190 | /**
|
---|
191 | * Calculates the width of the line represented by the given element.
|
---|
192 | *
|
---|
193 | * @param line The line for which to get the length.
|
---|
194 | * @param lineNumber The line number of the specified line in the document.
|
---|
195 | * @return The width of the line.
|
---|
196 | */
|
---|
197 | private float getLineWidth(int lineNumber) {
|
---|
198 | Token tokenList = ((RSyntaxDocument)getDocument()).
|
---|
199 | getTokenListForLine(lineNumber);
|
---|
200 | return RSyntaxUtilities.getTokenListWidth(tokenList,
|
---|
201 | (RSyntaxTextArea)getContainer(),
|
---|
202 | this);
|
---|
203 | }
|
---|
204 |
|
---|
205 |
|
---|
206 | /**
|
---|
207 | * Provides a way to determine the next visually represented model
|
---|
208 | * location that one might place a caret. Some views may not be visible,
|
---|
209 | * they might not be in the same order found in the model, or they just
|
---|
210 | * might not allow access to some of the locations in the model.
|
---|
211 | *
|
---|
212 | * @param pos the position to convert >= 0
|
---|
213 | * @param a the allocated region to render into
|
---|
214 | * @param direction the direction from the current position that can
|
---|
215 | * be thought of as the arrow keys typically found on a keyboard.
|
---|
216 | * This may be SwingConstants.WEST, SwingConstants.EAST,
|
---|
217 | * SwingConstants.NORTH, or SwingConstants.SOUTH.
|
---|
218 | * @return the location within the model that best represents the next
|
---|
219 | * location visual position.
|
---|
220 | * @exception BadLocationException
|
---|
221 | * @exception IllegalArgumentException for an invalid direction
|
---|
222 | */
|
---|
223 | public int getNextVisualPositionFrom(int pos, Position.Bias b, Shape a,
|
---|
224 | int direction, Position.Bias[] biasRet)
|
---|
225 | throws BadLocationException {
|
---|
226 | return RSyntaxUtilities.getNextVisualPositionFrom(pos, b, a,
|
---|
227 | direction, biasRet, this);
|
---|
228 | }
|
---|
229 |
|
---|
230 |
|
---|
231 | /**
|
---|
232 | * Determines the preferred span for this view along an
|
---|
233 | * axis.
|
---|
234 | *
|
---|
235 | * @param axis may be either View.X_AXIS or View.Y_AXIS
|
---|
236 | * @return the span the view would like to be rendered into >= 0.
|
---|
237 | * Typically the view is told to render into the span
|
---|
238 | * that is returned, although there is no guarantee.
|
---|
239 | * The parent may choose to resize or break the view.
|
---|
240 | * @exception IllegalArgumentException for an invalid axis
|
---|
241 | */
|
---|
242 | public float getPreferredSpan(int axis) {
|
---|
243 | updateMetrics();
|
---|
244 | switch (axis) {
|
---|
245 | case View.X_AXIS:
|
---|
246 | float span = longLineWidth + 10; // "fudge factor."
|
---|
247 | if (host.getEOLMarkersVisible()) {
|
---|
248 | span += metrics.charWidth('\u00B6');
|
---|
249 | }
|
---|
250 | return span;
|
---|
251 | case View.Y_AXIS:
|
---|
252 | // We update lineHeight here as when this method is first
|
---|
253 | // called, lineHeight isn't initialized. If we don't do it
|
---|
254 | // here, we get no vertical scrollbar (as lineHeight==0).
|
---|
255 | lineHeight = host!=null ? host.getLineHeight() : lineHeight;
|
---|
256 | // return getElement().getElementCount() * lineHeight;
|
---|
257 | int visibleLineCount = getElement().getElementCount();
|
---|
258 | if (host.isCodeFoldingEnabled()) {
|
---|
259 | visibleLineCount -= host.getFoldManager().getHiddenLineCount();
|
---|
260 | }
|
---|
261 | return visibleLineCount * lineHeight;
|
---|
262 | default:
|
---|
263 | throw new IllegalArgumentException("Invalid axis: " + axis);
|
---|
264 | }
|
---|
265 | }
|
---|
266 |
|
---|
267 |
|
---|
268 | /**
|
---|
269 | * Returns the tab size set for the document, defaulting to 5.
|
---|
270 | *
|
---|
271 | * @return The tab size.
|
---|
272 | */
|
---|
273 | protected int getTabSize() {
|
---|
274 | Integer i = (Integer)getDocument().getProperty(
|
---|
275 | PlainDocument.tabSizeAttribute);
|
---|
276 | int size = (i != null) ? i.intValue() : 5;
|
---|
277 | return size;
|
---|
278 | }
|
---|
279 |
|
---|
280 |
|
---|
281 | /**
|
---|
282 | * Returns a token list for the <i>physical</i> line above the physical
|
---|
283 | * line containing the specified offset into the document. Note that for
|
---|
284 | * this plain (non-wrapped) view, this is simply the token list for the
|
---|
285 | * logical line above the line containing <code>offset</code>, since lines
|
---|
286 | * are not wrapped.
|
---|
287 | *
|
---|
288 | * @param offset The offset in question.
|
---|
289 | * @return A token list for the physical (and in this view, logical) line
|
---|
290 | * before this one. If <code>offset</code> is in the first line in
|
---|
291 | * the document, <code>null</code> is returned.
|
---|
292 | */
|
---|
293 | public Token getTokenListForPhysicalLineAbove(int offset) {
|
---|
294 | RSyntaxDocument document = (RSyntaxDocument)getDocument();
|
---|
295 | Element map = document.getDefaultRootElement();
|
---|
296 | int line = map.getElementIndex(offset);
|
---|
297 | FoldManager fm = host.getFoldManager();
|
---|
298 | if (fm==null) {
|
---|
299 | line--;
|
---|
300 | if (line>=0) {
|
---|
301 | return document.getTokenListForLine(line);
|
---|
302 | }
|
---|
303 | }
|
---|
304 | else {
|
---|
305 | line = fm.getVisibleLineAbove(line);
|
---|
306 | if (line>=0) {
|
---|
307 | return document.getTokenListForLine(line);
|
---|
308 | }
|
---|
309 | }
|
---|
310 | // int line = map.getElementIndex(offset) - 1;
|
---|
311 | // if (line>=0)
|
---|
312 | // return document.getTokenListForLine(line);
|
---|
313 | return null;
|
---|
314 | }
|
---|
315 |
|
---|
316 |
|
---|
317 | /**
|
---|
318 | * Returns a token list for the <i>physical</i> line below the physical
|
---|
319 | * line containing the specified offset into the document. Note that for
|
---|
320 | * this plain (non-wrapped) view, this is simply the token list for the
|
---|
321 | * logical line below the line containing <code>offset</code>, since lines
|
---|
322 | * are not wrapped.
|
---|
323 | *
|
---|
324 | * @param offset The offset in question.
|
---|
325 | * @return A token list for the physical (and in this view, logical) line
|
---|
326 | * after this one. If <code>offset</code> is in the last physical
|
---|
327 | * line in the document, <code>null</code> is returned.
|
---|
328 | */
|
---|
329 | public Token getTokenListForPhysicalLineBelow(int offset) {
|
---|
330 | RSyntaxDocument document = (RSyntaxDocument)getDocument();
|
---|
331 | Element map = document.getDefaultRootElement();
|
---|
332 | int lineCount = map.getElementCount();
|
---|
333 | int line = map.getElementIndex(offset);
|
---|
334 | if (!host.isCodeFoldingEnabled()) {
|
---|
335 | if (line<lineCount-1) {
|
---|
336 | return document.getTokenListForLine(line+1);
|
---|
337 | }
|
---|
338 | }
|
---|
339 | else {
|
---|
340 | FoldManager fm = host.getFoldManager();
|
---|
341 | line = fm.getVisibleLineBelow(line);
|
---|
342 | if (line>=0 && line<lineCount) {
|
---|
343 | return document.getTokenListForLine(line);
|
---|
344 | }
|
---|
345 | }
|
---|
346 | // int line = map.getElementIndex(offset);
|
---|
347 | // int lineCount = map.getElementCount();
|
---|
348 | // if (line<lineCount-1)
|
---|
349 | // return document.getTokenListForLine(line+1);
|
---|
350 | return null;
|
---|
351 | }
|
---|
352 |
|
---|
353 |
|
---|
354 | /**
|
---|
355 | * Gives notification that something was inserted into the document
|
---|
356 | * in a location that this view is responsible for.
|
---|
357 | *
|
---|
358 | * @param changes The change information from the associated document.
|
---|
359 | * @param a The current allocation of the view.
|
---|
360 | * @param f The factory to use to rebuild if the view has children.
|
---|
361 | */
|
---|
362 | public void insertUpdate(DocumentEvent changes, Shape a, ViewFactory f) {
|
---|
363 | updateDamage(changes, a, f);
|
---|
364 | }
|
---|
365 |
|
---|
366 |
|
---|
367 | /**
|
---|
368 | * Determine the rectangle that represents the given line.
|
---|
369 | *
|
---|
370 | * @param a The region allocated for the view to render into
|
---|
371 | * @param line The line number to find the region of. This must
|
---|
372 | * be a valid line number in the model.
|
---|
373 | */
|
---|
374 | protected Rectangle lineToRect(Shape a, int line) {
|
---|
375 | Rectangle r = null;
|
---|
376 | updateMetrics();
|
---|
377 | if (metrics != null) {
|
---|
378 | Rectangle alloc = a.getBounds();
|
---|
379 | // NOTE: lineHeight is not initially set here, leading to the
|
---|
380 | // current line not being highlighted when a document is first
|
---|
381 | // opened. So, we set it here just in case.
|
---|
382 | lineHeight = host!=null ? host.getLineHeight() : lineHeight;
|
---|
383 | if (host.isCodeFoldingEnabled()) {
|
---|
384 | FoldManager fm = host.getFoldManager();
|
---|
385 | int hiddenCount = fm.getHiddenLineCountAbove(line);
|
---|
386 | line -= hiddenCount;
|
---|
387 | }
|
---|
388 | r = new Rectangle(alloc.x, alloc.y + line*lineHeight,
|
---|
389 | alloc.width, lineHeight);
|
---|
390 | }
|
---|
391 | return r;
|
---|
392 | }
|
---|
393 |
|
---|
394 |
|
---|
395 | /**
|
---|
396 | * Provides a mapping from the document model coordinate space
|
---|
397 | * to the coordinate space of the view mapped to it.
|
---|
398 | *
|
---|
399 | * @param pos the position to convert >= 0
|
---|
400 | * @param a the allocated region to render into
|
---|
401 | * @return the bounding box of the given position
|
---|
402 | * @exception BadLocationException if the given position does not
|
---|
403 | * represent a valid location in the associated document
|
---|
404 | * @see View#modelToView
|
---|
405 | */
|
---|
406 | public Shape modelToView(int pos, Shape a, Position.Bias b)
|
---|
407 | throws BadLocationException {
|
---|
408 |
|
---|
409 | // line coordinates
|
---|
410 | Element map = getElement();
|
---|
411 | RSyntaxDocument doc = (RSyntaxDocument)getDocument();
|
---|
412 | int lineIndex = map.getElementIndex(pos);
|
---|
413 | Token tokenList = doc.getTokenListForLine(lineIndex);
|
---|
414 | Rectangle lineArea = lineToRect(a, lineIndex);
|
---|
415 | tabBase = lineArea.x; // Used by listOffsetToView().
|
---|
416 |
|
---|
417 | //int x = (int)RSyntaxUtilities.getTokenListWidthUpTo(tokenList,
|
---|
418 | // (RSyntaxTextArea)getContainer(),
|
---|
419 | // this, 0, pos);
|
---|
420 | // We use this method instead as it returns the actual bounding box,
|
---|
421 | // not just the x-coordinate.
|
---|
422 | lineArea = tokenList.listOffsetToView(
|
---|
423 | (RSyntaxTextArea)getContainer(), this, pos,
|
---|
424 | tabBase, lineArea);
|
---|
425 |
|
---|
426 | return lineArea;
|
---|
427 |
|
---|
428 | }
|
---|
429 |
|
---|
430 |
|
---|
431 | /**
|
---|
432 | * Provides a mapping, for a given region, from the document model
|
---|
433 | * coordinate space to the view coordinate space. The specified region is
|
---|
434 | * created as a union of the first and last character positions.<p>
|
---|
435 | *
|
---|
436 | * This is implemented to subtract the width of the second character, as
|
---|
437 | * this view's <code>modelToView</code> actually returns the width of the
|
---|
438 | * character instead of "1" or "0" like the View implementations in
|
---|
439 | * <code>javax.swing.text</code>. Thus, if we don't override this method,
|
---|
440 | * the <code>View</code> implementation will return one character's width
|
---|
441 | * too much for its consumers (implementations of
|
---|
442 | * <code>javax.swing.text.Highlighter</code>).
|
---|
443 | *
|
---|
444 | * @param p0 the position of the first character (>=0)
|
---|
445 | * @param b0 The bias of the first character position, toward the previous
|
---|
446 | * character or the next character represented by the offset, in
|
---|
447 | * case the position is a boundary of two views; <code>b0</code>
|
---|
448 | * will have one of these values:
|
---|
449 | * <ul>
|
---|
450 | * <li> <code>Position.Bias.Forward</code>
|
---|
451 | * <li> <code>Position.Bias.Backward</code>
|
---|
452 | * </ul>
|
---|
453 | * @param p1 the position of the last character (>=0)
|
---|
454 | * @param b1 the bias for the second character position, defined
|
---|
455 | * one of the legal values shown above
|
---|
456 | * @param a the area of the view, which encompasses the requested region
|
---|
457 | * @return the bounding box which is a union of the region specified
|
---|
458 | * by the first and last character positions
|
---|
459 | * @exception BadLocationException if the given position does
|
---|
460 | * not represent a valid location in the associated document
|
---|
461 | * @exception IllegalArgumentException if <code>b0</code> or
|
---|
462 | * <code>b1</code> are not one of the
|
---|
463 | * legal <code>Position.Bias</code> values listed above
|
---|
464 | * @see View#viewToModel
|
---|
465 | */
|
---|
466 | public Shape modelToView(int p0, Position.Bias b0,
|
---|
467 | int p1, Position.Bias b1,
|
---|
468 | Shape a) throws BadLocationException {
|
---|
469 |
|
---|
470 | Shape s0 = modelToView(p0, a, b0);
|
---|
471 | Shape s1;
|
---|
472 | if (p1 ==getEndOffset()) {
|
---|
473 | try {
|
---|
474 | s1 = modelToView(p1, a, b1);
|
---|
475 | } catch (BadLocationException ble) {
|
---|
476 | s1 = null;
|
---|
477 | }
|
---|
478 | if (s1 == null) {
|
---|
479 | // Assume extends left to right.
|
---|
480 | Rectangle alloc = (a instanceof Rectangle) ? (Rectangle)a :
|
---|
481 | a.getBounds();
|
---|
482 | s1 = new Rectangle(alloc.x + alloc.width - 1, alloc.y,
|
---|
483 | 1, alloc.height);
|
---|
484 | }
|
---|
485 | }
|
---|
486 | else {
|
---|
487 | s1 = modelToView(p1, a, b1);
|
---|
488 | }
|
---|
489 | Rectangle r0 = s0.getBounds();
|
---|
490 | Rectangle r1 = (s1 instanceof Rectangle) ? (Rectangle) s1 :
|
---|
491 | s1.getBounds();
|
---|
492 | if (r0.y != r1.y) {
|
---|
493 | // If it spans lines, force it to be the width of the view.
|
---|
494 | Rectangle alloc = (a instanceof Rectangle) ? (Rectangle)a :
|
---|
495 | a.getBounds();
|
---|
496 | r0.x = alloc.x;
|
---|
497 | r0.width = alloc.width;
|
---|
498 | }
|
---|
499 |
|
---|
500 | r0.add(r1);
|
---|
501 | // The next line is the only difference between this method and
|
---|
502 | // View's implementation. We're subtracting the width of the second
|
---|
503 | // character. This is because this method is used by Highlighter
|
---|
504 | // implementations to get the area to "highlight", and if we don't do
|
---|
505 | // this, one character too many is highlighted thanks to our
|
---|
506 | // modelToView() implementation returning the actual width of the
|
---|
507 | // character requested!
|
---|
508 | if (p1>p0) r0.width -= r1.width;
|
---|
509 |
|
---|
510 | return r0;
|
---|
511 |
|
---|
512 | }
|
---|
513 |
|
---|
514 |
|
---|
515 | /**
|
---|
516 | * Returns the next tab stop position after a given reference position.
|
---|
517 | * This implementation does not support things like centering so it
|
---|
518 | * ignores the tabOffset argument.
|
---|
519 | *
|
---|
520 | * @param x the current position >= 0
|
---|
521 | * @param tabOffset the position within the text stream
|
---|
522 | * that the tab occurred at >= 0.
|
---|
523 | * @return the tab stop, measured in points >= 0
|
---|
524 | */
|
---|
525 | public float nextTabStop(float x, int tabOffset) {
|
---|
526 | if (tabSize == 0)
|
---|
527 | return x;
|
---|
528 | int ntabs = (((int)x) - tabBase) / tabSize;
|
---|
529 | return tabBase + ((ntabs + 1) * tabSize);
|
---|
530 | }
|
---|
531 |
|
---|
532 |
|
---|
533 | /**
|
---|
534 | * Actually paints the text area. Only lines that have been damaged are
|
---|
535 | * repainted.
|
---|
536 | *
|
---|
537 | * @param g The graphics context with which to paint.
|
---|
538 | * @param a The allocated region in which to render.
|
---|
539 | * @see #drawLine
|
---|
540 | */
|
---|
541 | public void paint(Graphics g, Shape a) {
|
---|
542 |
|
---|
543 | RSyntaxDocument document = (RSyntaxDocument)getDocument();
|
---|
544 |
|
---|
545 | Rectangle alloc = a.getBounds();
|
---|
546 |
|
---|
547 | tabBase = alloc.x;
|
---|
548 | host = (RSyntaxTextArea)getContainer();
|
---|
549 |
|
---|
550 | Rectangle clip = g.getClipBounds();
|
---|
551 | // An attempt to speed things up for files with long lines. Note that
|
---|
552 | // this will actually slow things down a tad for the common case of
|
---|
553 | // regular-length lines, but I don't think it'll make a difference
|
---|
554 | // visually. We'll see...
|
---|
555 | clipStart = clip.x;
|
---|
556 | clipEnd = clipStart + clip.width;
|
---|
557 |
|
---|
558 | lineHeight = host.getLineHeight();
|
---|
559 | ascent = host.getMaxAscent();//metrics.getAscent();
|
---|
560 | int heightAbove = clip.y - alloc.y;
|
---|
561 | int linesAbove = Math.max(0, heightAbove / lineHeight);
|
---|
562 |
|
---|
563 | FoldManager fm = host.getFoldManager();
|
---|
564 | linesAbove += fm.getHiddenLineCountAbove(linesAbove, true);
|
---|
565 | Rectangle lineArea = lineToRect(a, linesAbove);
|
---|
566 | int y = lineArea.y + ascent;
|
---|
567 | int x = lineArea.x;
|
---|
568 | Element map = getElement();
|
---|
569 | int lineCount = map.getElementCount();
|
---|
570 |
|
---|
571 | RSyntaxTextAreaHighlighter h =
|
---|
572 | (RSyntaxTextAreaHighlighter)host.getHighlighter();
|
---|
573 |
|
---|
574 | Graphics2D g2d = (Graphics2D)g;
|
---|
575 | Token token;
|
---|
576 | //System.err.println("Painting lines: " + linesAbove + " to " + (endLine-1));
|
---|
577 |
|
---|
578 |
|
---|
579 | int line = linesAbove;
|
---|
580 | //int count = 0;
|
---|
581 | while (y<clip.y+clip.height+lineHeight && line<lineCount) {
|
---|
582 |
|
---|
583 | Fold fold = fm.getFoldForLine(line);
|
---|
584 | Element lineElement = map.getElement(line);
|
---|
585 | int startOffset = lineElement.getStartOffset();
|
---|
586 | //int endOffset = (line==lineCount ? lineElement.getEndOffset()-1 :
|
---|
587 | // lineElement.getEndOffset()-1);
|
---|
588 | int endOffset = lineElement.getEndOffset()-1; // Why always "-1"?
|
---|
589 | h.paintLayeredHighlights(g2d, startOffset, endOffset,
|
---|
590 | a, host, this);
|
---|
591 |
|
---|
592 | // Paint a line of text.
|
---|
593 | token = document.getTokenListForLine(line);
|
---|
594 | drawLine(token, g2d, x,y);
|
---|
595 |
|
---|
596 | if (fold!=null && fold.isCollapsed()) {
|
---|
597 |
|
---|
598 | // Visible indicator of collapsed lines
|
---|
599 | Color c = RSyntaxUtilities.getFoldedLineBottomColor(host);
|
---|
600 | if (c!=null) {
|
---|
601 | g.setColor(c);
|
---|
602 | g.drawLine(x,y+lineHeight-ascent-1,
|
---|
603 | alloc.width,y+lineHeight-ascent-1);
|
---|
604 | }
|
---|
605 |
|
---|
606 | // Skip to next line to paint, taking extra care for lines with
|
---|
607 | // block ends and begins together, e.g. "} else {"
|
---|
608 | do {
|
---|
609 | int hiddenLineCount = fold.getLineCount();
|
---|
610 | if (hiddenLineCount==0) {
|
---|
611 | // Fold parser identified a zero-line fold region.
|
---|
612 | // This is really a bug, but we'll be graceful here
|
---|
613 | // and avoid an infinite loop.
|
---|
614 | break;
|
---|
615 | }
|
---|
616 | line += hiddenLineCount;
|
---|
617 | fold = fm.getFoldForLine(line);
|
---|
618 | } while (fold!=null && fold.isCollapsed());
|
---|
619 |
|
---|
620 | }
|
---|
621 |
|
---|
622 | y += lineHeight;
|
---|
623 | line++;
|
---|
624 | //count++;
|
---|
625 |
|
---|
626 | }
|
---|
627 |
|
---|
628 | //System.out.println("SyntaxView: lines painted=" + count);
|
---|
629 |
|
---|
630 | }
|
---|
631 |
|
---|
632 |
|
---|
633 | /**
|
---|
634 | * If the passed-in line is longer than the current longest line, then
|
---|
635 | * the longest line is updated.
|
---|
636 | *
|
---|
637 | * @param line The line to test against the current longest.
|
---|
638 | * @param lineNumber The line number of the passed-in line.
|
---|
639 | * @return <code>true</code> iff the current longest line was updated.
|
---|
640 | */
|
---|
641 | protected boolean possiblyUpdateLongLine(Element line, int lineNumber) {
|
---|
642 | float w = getLineWidth(lineNumber);
|
---|
643 | if (w > longLineWidth) {
|
---|
644 | longLineWidth = w;
|
---|
645 | longLine = line;
|
---|
646 | return true;
|
---|
647 | }
|
---|
648 | return false;
|
---|
649 | }
|
---|
650 |
|
---|
651 |
|
---|
652 | /**
|
---|
653 | * Gives notification that something was removed from the document
|
---|
654 | * in a location that this view is responsible for.
|
---|
655 | *
|
---|
656 | * @param changes the change information from the associated document
|
---|
657 | * @param a the current allocation of the view
|
---|
658 | * @param f the factory to use to rebuild if the view has children
|
---|
659 | */
|
---|
660 | public void removeUpdate(DocumentEvent changes, Shape a, ViewFactory f) {
|
---|
661 | updateDamage(changes, a, f);
|
---|
662 | }
|
---|
663 |
|
---|
664 |
|
---|
665 | public void setSize(float width, float height) {
|
---|
666 | super.setSize(width, height);
|
---|
667 | updateMetrics();
|
---|
668 | }
|
---|
669 |
|
---|
670 |
|
---|
671 | /**
|
---|
672 | * Repaint the region of change covered by the given document
|
---|
673 | * event. Damages the line that begins the range to cover
|
---|
674 | * the case when the insert/remove is only on one line.
|
---|
675 | * If lines are added or removed, damages the whole
|
---|
676 | * view. The longest line is checked to see if it has
|
---|
677 | * changed.
|
---|
678 | */
|
---|
679 | protected void updateDamage(DocumentEvent changes, Shape a, ViewFactory f) {
|
---|
680 | Component host = getContainer();
|
---|
681 | updateMetrics();
|
---|
682 | Element elem = getElement();
|
---|
683 | DocumentEvent.ElementChange ec = changes.getChange(elem);
|
---|
684 | Element[] added = (ec != null) ? ec.getChildrenAdded() : null;
|
---|
685 | Element[] removed = (ec != null) ? ec.getChildrenRemoved() : null;
|
---|
686 | if (((added != null) && (added.length > 0)) ||
|
---|
687 | ((removed != null) && (removed.length > 0))) {
|
---|
688 | // lines were added or removed...
|
---|
689 | if (added != null) {
|
---|
690 | int addedAt = ec.getIndex(); // FIXME: Is this correct?????
|
---|
691 | for (int i = 0; i < added.length; i++)
|
---|
692 | possiblyUpdateLongLine(added[i], addedAt+i);
|
---|
693 | }
|
---|
694 | if (removed != null) {
|
---|
695 | for (int i = 0; i < removed.length; i++) {
|
---|
696 | if (removed[i] == longLine) {
|
---|
697 | longLineWidth = -1; // Must do this!!
|
---|
698 | calculateLongestLine();
|
---|
699 | break;
|
---|
700 | }
|
---|
701 | }
|
---|
702 | }
|
---|
703 | preferenceChanged(null, true, true);
|
---|
704 | host.repaint();
|
---|
705 | }
|
---|
706 |
|
---|
707 | // This occurs when syntax highlighting only changes on lines
|
---|
708 | // (i.e. beginning a multiline comment).
|
---|
709 | else if (changes.getType()==DocumentEvent.EventType.CHANGE) {
|
---|
710 | //System.err.println("Updating the damage due to a CHANGE event...");
|
---|
711 | int startLine = changes.getOffset();
|
---|
712 | int endLine = changes.getLength();
|
---|
713 | damageLineRange(startLine,endLine, a, host);
|
---|
714 | }
|
---|
715 |
|
---|
716 | else {
|
---|
717 | Element map = getElement();
|
---|
718 | int line = map.getElementIndex(changes.getOffset());
|
---|
719 | damageLineRange(line, line, a, host);
|
---|
720 | if (changes.getType() == DocumentEvent.EventType.INSERT) {
|
---|
721 | // check to see if the line is longer than current
|
---|
722 | // longest line.
|
---|
723 | Element e = map.getElement(line);
|
---|
724 | if (e == longLine) {
|
---|
725 | // We must recalculate longest line's width here
|
---|
726 | // because it has gotten longer.
|
---|
727 | longLineWidth = getLineWidth(line);
|
---|
728 | preferenceChanged(null, true, false);
|
---|
729 | }
|
---|
730 | else {
|
---|
731 | // If long line gets updated, update the status bars too.
|
---|
732 | if (possiblyUpdateLongLine(e, line))
|
---|
733 | preferenceChanged(null, true, false);
|
---|
734 | }
|
---|
735 | }
|
---|
736 | else if (changes.getType() == DocumentEvent.EventType.REMOVE) {
|
---|
737 | if (map.getElement(line) == longLine) {
|
---|
738 | // removed from longest line... recalc
|
---|
739 | longLineWidth = -1; // Must do this!
|
---|
740 | calculateLongestLine();
|
---|
741 | preferenceChanged(null, true, false);
|
---|
742 | }
|
---|
743 | }
|
---|
744 | }
|
---|
745 | }
|
---|
746 |
|
---|
747 |
|
---|
748 | /**
|
---|
749 | * Checks to see if the font metrics and longest line are up-to-date.
|
---|
750 | */
|
---|
751 | protected void updateMetrics() {
|
---|
752 | host = (RSyntaxTextArea)getContainer();
|
---|
753 | Font f = host.getFont();
|
---|
754 | if (font != f) {
|
---|
755 | // The font changed, we need to recalculate the longest line!
|
---|
756 | // This also updates cached font and tab size.
|
---|
757 | calculateLongestLine();
|
---|
758 | }
|
---|
759 | }
|
---|
760 |
|
---|
761 |
|
---|
762 | /**
|
---|
763 | * Provides a mapping from the view coordinate space to the logical
|
---|
764 | * coordinate space of the model.
|
---|
765 | *
|
---|
766 | * @param fx the X coordinate >= 0
|
---|
767 | * @param fy the Y coordinate >= 0
|
---|
768 | * @param a the allocated region to render into
|
---|
769 | * @return the location within the model that best represents the
|
---|
770 | * given point in the view >= 0
|
---|
771 | */
|
---|
772 | public int viewToModel(float fx, float fy, Shape a, Position.Bias[] bias) {
|
---|
773 |
|
---|
774 | bias[0] = Position.Bias.Forward;
|
---|
775 |
|
---|
776 | Rectangle alloc = a.getBounds();
|
---|
777 | RSyntaxDocument doc = (RSyntaxDocument)getDocument();
|
---|
778 | int x = (int) fx;
|
---|
779 | int y = (int) fy;
|
---|
780 |
|
---|
781 | // If they're asking about a view position above the area covered by
|
---|
782 | // this view, then the position is assumed to be the starting position
|
---|
783 | // of this view.
|
---|
784 | if (y < alloc.y) {
|
---|
785 | return getStartOffset();
|
---|
786 | }
|
---|
787 |
|
---|
788 | // If they're asking about a position below this view, the position
|
---|
789 | // is assumed to be the ending position of this view.
|
---|
790 | else if (y > alloc.y + alloc.height) {
|
---|
791 | return host.getLastVisibleOffset();
|
---|
792 | }
|
---|
793 |
|
---|
794 | // They're asking about a position within the coverage of this view
|
---|
795 | // vertically. So, we figure out which line the point corresponds to.
|
---|
796 | // If the line is greater than the number of lines contained, then
|
---|
797 | // simply use the last line as it represents the last possible place
|
---|
798 | // we can position to.
|
---|
799 | else {
|
---|
800 |
|
---|
801 | Element map = doc.getDefaultRootElement();
|
---|
802 | int lineIndex = Math.abs((y - alloc.y) / lineHeight);//metrics.getHeight() );
|
---|
803 | FoldManager fm = host.getFoldManager();
|
---|
804 | //System.out.print("--- " + lineIndex);
|
---|
805 | lineIndex += fm.getHiddenLineCountAbove(lineIndex, true);
|
---|
806 | //System.out.println(" => " + lineIndex);
|
---|
807 | if (lineIndex >= map.getElementCount()) {
|
---|
808 | return host.getLastVisibleOffset();
|
---|
809 | }
|
---|
810 |
|
---|
811 | Element line = map.getElement(lineIndex);
|
---|
812 |
|
---|
813 | // If the point is to the left of the line...
|
---|
814 | if (x < alloc.x)
|
---|
815 | return line.getStartOffset();
|
---|
816 |
|
---|
817 | // If the point is to the right of the line...
|
---|
818 | else if (x > alloc.x + alloc.width)
|
---|
819 | return line.getEndOffset() - 1;
|
---|
820 |
|
---|
821 | else {
|
---|
822 | // Determine the offset into the text
|
---|
823 | int p0 = line.getStartOffset();
|
---|
824 | Token tokenList = doc.getTokenListForLine(lineIndex);
|
---|
825 | tabBase = alloc.x;
|
---|
826 | int offs = tokenList.getListOffset(
|
---|
827 | (RSyntaxTextArea)getContainer(),
|
---|
828 | this, tabBase, x);
|
---|
829 | return offs!=-1 ? offs : p0;
|
---|
830 | }
|
---|
831 |
|
---|
832 | } // End of else.
|
---|
833 |
|
---|
834 | }
|
---|
835 |
|
---|
836 |
|
---|
837 | /**
|
---|
838 | * {@inheritDoc}
|
---|
839 | */
|
---|
840 | public int yForLine(Rectangle alloc, int line) throws BadLocationException {
|
---|
841 |
|
---|
842 | //Rectangle lineArea = lineToRect(alloc, lineIndex);
|
---|
843 | updateMetrics();
|
---|
844 | if (metrics != null) {
|
---|
845 | // NOTE: lineHeight is not initially set here, leading to the
|
---|
846 | // current line not being highlighted when a document is first
|
---|
847 | // opened. So, we set it here just in case.
|
---|
848 | lineHeight = host!=null ? host.getLineHeight() : lineHeight;
|
---|
849 | FoldManager fm = host.getFoldManager();
|
---|
850 | if (!fm.isLineHidden(line)) {
|
---|
851 | line -= fm.getHiddenLineCountAbove(line);
|
---|
852 | return alloc.y + line*lineHeight;
|
---|
853 | }
|
---|
854 | }
|
---|
855 |
|
---|
856 | return -1;
|
---|
857 |
|
---|
858 | }
|
---|
859 |
|
---|
860 |
|
---|
861 | /**
|
---|
862 | * {@inheritDoc}
|
---|
863 | */
|
---|
864 | public int yForLineContaining(Rectangle alloc, int offs)
|
---|
865 | throws BadLocationException {
|
---|
866 | Element map = getElement();
|
---|
867 | int line = map.getElementIndex(offs);
|
---|
868 | return yForLine(alloc, line);
|
---|
869 | }
|
---|
870 |
|
---|
871 |
|
---|
872 | } |
---|