source: other-projects/rsyntax-textarea/src/java/org/fife/ui/rsyntaxtextarea/WrappedSyntaxView.java@ 25584

Last change on this file since 25584 was 25584, checked in by davidb, 12 years ago

Initial cut an a text edit area for GLI that supports color syntax highlighting

File size: 36.9 KB
Line 
1/*
2 * 08/06/2004
3 *
4 * WrappedSyntaxView.java - Test implementation of WrappedSyntaxView that
5 * is also aware of RSyntaxTextArea's different fonts per token type.
6 *
7 * This library is distributed under a modified BSD license. See the included
8 * RSyntaxTextArea.License.txt file for details.
9 */
10package org.fife.ui.rsyntaxtextarea;
11
12import java.awt.*;
13import javax.swing.text.*;
14import javax.swing.text.Position.Bias;
15import javax.swing.event.*;
16
17import org.fife.ui.rsyntaxtextarea.folding.Fold;
18import org.fife.ui.rsyntaxtextarea.folding.FoldManager;
19import org.fife.ui.rtextarea.Gutter;
20
21
22/**
23 * The view used by {@link RSyntaxTextArea} when word wrap is enabled.
24 *
25 * @author Robert Futrell
26 * @version 0.2
27 */
28public class WrappedSyntaxView extends BoxView implements TabExpander,
29 RSTAView {
30
31 boolean widthChanging;
32 int tabBase;
33 int tabSize;
34
35 /**
36 * This is reused to keep from allocating/deallocating.
37 */
38 private Segment s, drawSeg;
39
40 /**
41 * Another variable initialized once to keep from allocating/deallocating.
42 */
43 private Rectangle tempRect;
44
45 /**
46 * Cached for each paint() call so each drawView() call has access to it.
47 */
48 private RSyntaxTextArea host;
49 private FontMetrics metrics;
50
51// /**
52// * The end-of-line marker.
53// */
54// private static final char[] eolMarker = { '.' };
55
56 /**
57 * The width of this view cannot be below this amount, as if the width
58 * is ever 0 (really a bug), we'll go into an infinite loop.
59 */
60 private static final int MIN_WIDTH = 20;
61
62
63 /**
64 * Creates a new WrappedSyntaxView. Lines will be wrapped
65 * on character boundaries.
66 *
67 * @param elem the element underlying the view
68 */
69 public WrappedSyntaxView(Element elem) {
70 super(elem, Y_AXIS);
71 s = new Segment();
72 drawSeg = new Segment();
73 tempRect = new Rectangle();
74 }
75
76
77
78 /**
79 * This is called by the nested wrapped line
80 * views to determine the break location. This can
81 * be reimplemented to alter the breaking behavior.
82 * It will either break at word or character boundaries
83 * depending upon the break argument given at
84 * construction.
85 */
86 protected int calculateBreakPosition(int p0, Token tokenList, float x0) {
87//System.err.println("------ beginning calculateBreakPosition() --------");
88 int p = p0;
89 RSyntaxTextArea textArea = (RSyntaxTextArea)getContainer();
90 float currentWidth = getWidth();
91 if (currentWidth==Integer.MAX_VALUE)
92 currentWidth = getPreferredSpan(X_AXIS);
93 // Make sure width>0; this is a huge hack to fix a bug where
94 // loading text into an RTextArea before it is visible if word wrap
95 // is enabled causes an infinite loop in calculateBreakPosition()
96 // because of the 0-width! We cannot simply check in setSize()
97 // because the width is set to 0 somewhere else too somehow...
98 currentWidth = Math.max(currentWidth, MIN_WIDTH);
99 Token t = tokenList;
100 while (t!=null && t.isPaintable()) {
101// FIXME: Replace the code below with the commented-out line below. This will
102// allow long tokens to be broken at embedded spaces (such as MLC's). But it
103// currently throws BadLocationExceptions sometimes...
104 float tokenWidth = t.getWidth(textArea, this, x0);
105 if (tokenWidth>currentWidth) {
106 // If the current token alone is too long for this line,
107 // break at a character boundary.
108 if (p==p0) {
109 return t.getOffsetBeforeX(textArea, this, 0, currentWidth);
110 }
111 // Return the first non-whitespace char (i.e., don't start
112 // off the continuation of a wrapped line with whitespace).
113 return t.isWhitespace() ? p+t.textCount : p;
114//return getBreakLocation(t, fm, x0, currentWidth, this);
115 }
116 currentWidth -= tokenWidth;
117 x0 += tokenWidth;
118 p += t.textCount;
119//System.err.println("*** *** *** token fit entirely (width==" + tokenWidth + "), adding " + t.textCount + " to p, now p==" + p);
120 t = t.getNextToken();
121 }
122//System.err.println("... ... whole line fits; returning p==" + p);
123//System.err.println("------ ending calculateBreakPosition() --------");
124
125// return p;
126return p + 1;
127 }
128
129//private int getBreakLocation(Token t, FontMetrics fm, int x0, int x,
130// TabExpander e) {
131// Segment s = new Segment();
132// s.array = t.text;
133// s.offset = t.textOffset;
134// s.count = t.textCount;
135// return t.offset + Utilities.getBreakLocation(s, fm, x0, x, e, t.offset);
136//}
137
138 /**
139 * Gives notification from the document that attributes were changed
140 * in a location that this view is responsible for.
141 *
142 * @param e the change information from the associated document
143 * @param a the current allocation of the view
144 * @param f the factory to use to rebuild if the view has children
145 * @see View#changedUpdate
146 */
147 public void changedUpdate(DocumentEvent e, Shape a, ViewFactory f) {
148 updateChildren(e, a);
149 }
150
151
152 /**
153 * Sets the allocation rectangle for a given line's view, but sets the
154 * y value to the passed-in value. This should be used instead of
155 * {@link #childAllocation(int, Rectangle)} since it allows you to account
156 * for hidden lines in collapsed fold regions.
157 *
158 * @param line
159 * @param y
160 * @param alloc
161 */
162 private void childAllocation2(int line, int y, Rectangle alloc) {
163 alloc.x += getOffset(X_AXIS, line);
164 alloc.y += y;
165 alloc.width = getSpan(X_AXIS, line);
166 alloc.height = getSpan(Y_AXIS, line);
167 }
168
169
170 /**
171 * Draws a single view (i.e., a line of text for a wrapped view),
172 * wrapping the text onto multiple lines if necessary.
173 *
174 * @param g The graphics context in which to paint.
175 * @param r The rectangle in which to paint.
176 * @param view The <code>View</code> to paint.
177 * @param fontHeight The height of the font being used.
178 * @param y The y-coordinate at which to begin painting.
179 */
180 protected void drawView(Graphics2D g, Rectangle r, View view,
181 int fontHeight, int y) {
182
183 float x = r.x;
184
185 LayeredHighlighter h = (LayeredHighlighter)host.getHighlighter();
186
187 RSyntaxDocument document = (RSyntaxDocument)getDocument();
188 Element map = getElement();
189
190 int p0 = view.getStartOffset();
191 int lineNumber = map.getElementIndex(p0);
192 int p1 = view.getEndOffset();// - 1;
193
194 setSegment(p0,p1-1, document, drawSeg);
195 //System.err.println("drawSeg=='" + drawSeg + "' (p0/p1==" + p0 + "/" + p1 + ")");
196 int start = p0 - drawSeg.offset;
197 Token token = document.getTokenListForLine(lineNumber);
198
199 // If this line is an empty line, then the token list is simply a
200 // null token. In this case, the line highlight will be skipped in
201 // the loop below, so unfortunately we must manually do it here.
202 if (token!=null && token.type==Token.NULL) {
203 h.paintLayeredHighlights(g, p0,p1, r, host, this);
204 return;
205 }
206
207 // Loop through all tokens in this view and paint them!
208 while (token!=null && token.isPaintable()) {
209
210 int p = calculateBreakPosition(p0, token, x);
211 x = r.x;
212
213 h.paintLayeredHighlights(g, p0,p, r, host, this);
214
215 while (token!=null && token.isPaintable() && token.offset+token.textCount-1<p) {//<=p) {
216 x = token.paint(g, x,y, host, this);
217 token = token.getNextToken();
218 }
219
220 if (token!=null && token.isPaintable() && token.offset<p) {
221 int tokenOffset = token.offset;
222 Token temp = new DefaultToken(drawSeg, tokenOffset-start,
223 p-1-start, tokenOffset,
224 token.type);
225 temp.paint(g, x,y, host, this);
226 temp = null;
227 token.makeStartAt(p);
228 }
229
230 p0 = (p==p0) ? p1 : p;
231 y += fontHeight;
232
233 } // End of while (token!=null && token.isPaintable()).
234
235 // NOTE: We should re-use code from Token (paintBackground()) here,
236 // but don't because I'm just too lazy.
237 if (host.getEOLMarkersVisible()) {
238 g.setColor(host.getForegroundForTokenType(Token.WHITESPACE));
239 g.setFont(host.getFontForTokenType(Token.WHITESPACE));
240 g.drawString("\u00B6", x, y-fontHeight);
241 }
242
243 }
244
245
246 /**
247 * Fetches the allocation for the given child view.<p>
248 * Overridden to account for code folding.
249 *
250 * @param index The index of the child, >= 0 && < getViewCount().
251 * @param a The allocation to this view
252 * @return The allocation to the child; or <code>null</code> if
253 * <code>a</code> is <code>null</code>; or <code>null</code> if the
254 * layout is invalid
255 */
256 public Shape getChildAllocation(int index, Shape a) {
257 if (a != null) {
258 Shape ca = getChildAllocationImpl(index, a);
259 if ((ca != null) && (!isAllocationValid())) {
260 // The child allocation may not have been set yet.
261 Rectangle r = (ca instanceof Rectangle) ? (Rectangle) ca : ca
262 .getBounds();
263 if ((r.width == 0) && (r.height == 0)) {
264 return null;
265 }
266 }
267 return ca;
268 }
269 return null;
270 }
271
272 /**
273 * Fetches the allocation for the given child view to render into.<p>
274 * Overridden to account for lines hidden by collapsed folded regions.
275 *
276 * @param line The index of the child, >= 0 && < getViewCount()
277 * @param a The allocation to this view
278 * @return The allocation to the child
279 */
280 public Shape getChildAllocationImpl(int line, Shape a) {
281
282 Rectangle alloc = getInsideAllocation(a);
283 host = (RSyntaxTextArea)getContainer();
284 FoldManager fm = host.getFoldManager();
285 int y = alloc.y;
286
287 // TODO: Make cached getOffset() calls for Y_AXIS valid even for
288 // folding, to speed this up!
289 for (int i=0; i<line; i++) {
290 y += getSpan(Y_AXIS, i);
291 Fold fold = fm.getFoldForLine(i);
292 if (fold!=null && fold.isCollapsed()) {
293 i += fold.getCollapsedLineCount();
294 }
295 }
296
297 childAllocation2(line, y, alloc);
298 return alloc;
299
300 }
301
302
303 /**
304 * Determines the maximum span for this view along an
305 * axis. This is implemented to provide the superclass
306 * behavior after first making sure that the current font
307 * metrics are cached (for the nested lines which use
308 * the metrics to determine the height of the potentially
309 * wrapped lines).
310 *
311 * @param axis may be either View.X_AXIS or View.Y_AXIS
312 * @return the span the view would like to be rendered into.
313 * Typically the view is told to render into the span
314 * that is returned, although there is no guarantee.
315 * The parent may choose to resize or break the view.
316 * @see View#getMaximumSpan
317 */
318 public float getMaximumSpan(int axis) {
319 updateMetrics();
320 float span = super.getPreferredSpan(axis);
321 if (axis==View.X_AXIS) { // EOL marker
322 span += metrics.charWidth('\u00b6'); // metrics set in updateMetrics
323 }
324 return span;
325 }
326
327
328 /**
329 * Determines the minimum span for this view along an
330 * axis. This is implemented to provide the superclass
331 * behavior after first making sure that the current font
332 * metrics are cached (for the nested lines which use
333 * the metrics to determine the height of the potentially
334 * wrapped lines).
335 *
336 * @param axis may be either View.X_AXIS or View.Y_AXIS
337 * @return the span the view would like to be rendered into.
338 * Typically the view is told to render into the span
339 * that is returned, although there is no guarantee.
340 * The parent may choose to resize or break the view.
341 * @see View#getMinimumSpan
342 */
343 public float getMinimumSpan(int axis) {
344 updateMetrics();
345 float span = super.getPreferredSpan(axis);
346 if (axis==View.X_AXIS) { // EOL marker
347 span += metrics.charWidth('\u00b6'); // metrics set in updateMetrics
348 }
349 return span;
350 }
351
352
353 /**
354 * Determines the preferred span for this view along an
355 * axis. This is implemented to provide the superclass
356 * behavior after first making sure that the current font
357 * metrics are cached (for the nested lines which use
358 * the metrics to determine the height of the potentially
359 * wrapped lines).
360 *
361 * @param axis may be either View.X_AXIS or View.Y_AXIS
362 * @return the span the view would like to be rendered into.
363 * Typically the view is told to render into the span
364 * that is returned, although there is no guarantee.
365 * The parent may choose to resize or break the view.
366 * @see View#getPreferredSpan
367 */
368 public float getPreferredSpan(int axis) {
369 updateMetrics();
370 float span = 0;
371 if (axis==View.X_AXIS) { // Add EOL marker
372 span = super.getPreferredSpan(axis);
373 span += metrics.charWidth('\u00b6'); // metrics set in updateMetrics
374 }
375 else {
376 span = super.getPreferredSpan(axis);
377 host = (RSyntaxTextArea)getContainer();
378 if (host.isCodeFoldingEnabled()) {
379 // TODO: Cache y-offsets again to speed this up
380 //System.out.println("y-axis baby");
381 int lineCount = host.getLineCount();
382 FoldManager fm = host.getFoldManager();
383 for (int i=0; i<lineCount; i++) {
384 if (fm.isLineHidden(i)) {
385 span -= getSpan(View.Y_AXIS, i);
386 }
387 }
388 }
389 }
390 return span;
391 }
392
393
394 /**
395 * Returns the tab size set for the document, defaulting to 5.
396 *
397 * @return the tab size
398 */
399 protected int getTabSize() {
400 Integer i = (Integer) getDocument().
401 getProperty(PlainDocument.tabSizeAttribute);
402 int size = (i != null) ? i.intValue() : 5;
403 return size;
404 }
405
406
407 /**
408 * Overridden to allow for folded regions.
409 */
410 protected View getViewAtPoint(int x, int y, Rectangle alloc) {
411
412 int lineCount = getViewCount();
413 int curY = alloc.y + getOffset(Y_AXIS, 0); // Always at least 1 line
414 host = (RSyntaxTextArea)getContainer();
415 FoldManager fm = host.getFoldManager();
416
417 for (int line=1; line<lineCount; line++) {
418 int span = getSpan(Y_AXIS, line-1);
419 if (y<curY+span) {
420 childAllocation2(line-1, curY, alloc);
421 return getView(line-1);
422 }
423 curY += span;
424 Fold fold = fm.getFoldForLine(line-1);
425 if (fold!=null && fold.isCollapsed()) {
426 line += fold.getCollapsedLineCount();
427 }
428 }
429
430 // Not found - return last line's view.
431 childAllocation2(lineCount - 1, curY, alloc);
432 return getView(lineCount - 1);
433
434 }
435
436
437 /**
438 * Gives notification that something was inserted into the
439 * document in a location that this view is responsible for.
440 * This is implemented to simply update the children.
441 *
442 * @param changes The change information from the associated document.
443 * @param a the current allocation of the view
444 * @param f the factory to use to rebuild if the view has children
445 * @see View#insertUpdate
446 */
447 public void insertUpdate(DocumentEvent changes, Shape a, ViewFactory f) {
448 updateChildren(changes, a);
449 Rectangle alloc = ((a != null) && isAllocationValid()) ?
450 getInsideAllocation(a) : null;
451 int pos = changes.getOffset();
452 View v = getViewAtPosition(pos, alloc);
453 if (v != null)
454 v.insertUpdate(changes, alloc, f);
455 }
456
457
458 /**
459 * Loads all of the children to initialize the view.
460 * This is called by the <code>setParent</code> method.
461 * Subclasses can re-implement this to initialize their
462 * child views in a different manner. The default
463 * implementation creates a child view for each
464 * child element.
465 *
466 * @param f the view factory
467 */
468 protected void loadChildren(ViewFactory f) {
469 Element e = getElement();
470 int n = e.getElementCount();
471 if (n > 0) {
472 View[] added = new View[n];
473 for (int i = 0; i < n; i++)
474 added[i] = new WrappedLine(e.getElement(i));
475 replace(0, 0, added);
476 }
477 }
478
479
480 public Shape modelToView(int pos, Shape a, Position.Bias b)
481 throws BadLocationException {
482
483 if (! isAllocationValid()) {
484 Rectangle alloc = a.getBounds();
485 setSize(alloc.width, alloc.height);
486 }
487
488 boolean isBackward = (b == Position.Bias.Backward);
489 int testPos = (isBackward) ? Math.max(0, pos - 1) : pos;
490 if(isBackward && testPos < getStartOffset()) {
491 return null;
492 }
493
494 int vIndex = getViewIndexAtPosition(testPos);
495 if ((vIndex != -1) && (vIndex < getViewCount())) {
496 View v = getView(vIndex);
497 if(v != null && testPos >= v.getStartOffset() &&
498 testPos < v.getEndOffset()) {
499 Shape childShape = getChildAllocation(vIndex, a);
500 if (childShape == null) {
501 // We are likely invalid, fail.
502 return null;
503 }
504 Shape retShape = v.modelToView(pos, childShape, b);
505 if(retShape == null && v.getEndOffset() == pos) {
506 if(++vIndex < getViewCount()) {
507 v = getView(vIndex);
508 retShape = v.modelToView(pos, getChildAllocation(vIndex, a), b);
509 }
510 }
511 return retShape;
512 }
513 }
514
515 throw new BadLocationException("Position not represented by view", pos);
516
517 }
518
519
520 /**
521 * Provides a mapping, for a given region, from the document model
522 * coordinate space to the view coordinate space. The specified region is
523 * created as a union of the first and last character positions.<p>
524 *
525 * This is implemented to subtract the width of the second character, as
526 * this view's <code>modelToView</code> actually returns the width of the
527 * character instead of "1" or "0" like the View implementations in
528 * <code>javax.swing.text</code>. Thus, if we don't override this method,
529 * the <code>View</code> implementation will return one character's width
530 * too much for its consumers (implementations of
531 * <code>javax.swing.text.Highlighter</code>).
532 *
533 * @param p0 the position of the first character (>=0)
534 * @param b0 The bias of the first character position, toward the previous
535 * character or the next character represented by the offset, in
536 * case the position is a boundary of two views; <code>b0</code>
537 * will have one of these values:
538 * <ul>
539 * <li> <code>Position.Bias.Forward</code>
540 * <li> <code>Position.Bias.Backward</code>
541 * </ul>
542 * @param p1 the position of the last character (>=0)
543 * @param b1 the bias for the second character position, defined
544 * one of the legal values shown above
545 * @param a the area of the view, which encompasses the requested region
546 * @return the bounding box which is a union of the region specified
547 * by the first and last character positions
548 * @exception BadLocationException if the given position does
549 * not represent a valid location in the associated document
550 * @exception IllegalArgumentException if <code>b0</code> or
551 * <code>b1</code> are not one of the
552 * legal <code>Position.Bias</code> values listed above
553 * @see View#viewToModel
554 */
555 public Shape modelToView(int p0, Position.Bias b0,
556 int p1, Position.Bias b1,
557 Shape a) throws BadLocationException {
558
559 Shape s0 = modelToView(p0, a, b0);
560 Shape s1;
561 if (p1 ==getEndOffset()) {
562 try {
563 s1 = modelToView(p1, a, b1);
564 } catch (BadLocationException ble) {
565 s1 = null;
566 }
567 if (s1 == null) {
568 // Assume extends left to right.
569 Rectangle alloc = (a instanceof Rectangle) ? (Rectangle)a :
570 a.getBounds();
571 s1 = new Rectangle(alloc.x + alloc.width - 1, alloc.y,
572 1, alloc.height);
573 }
574 }
575 else {
576 s1 = modelToView(p1, a, b1);
577 }
578 Rectangle r0 = s0.getBounds();
579 Rectangle r1 = (s1 instanceof Rectangle) ? (Rectangle) s1 :
580 s1.getBounds();
581 if (r0.y != r1.y) {
582 // If it spans lines, force it to be the width of the view.
583 Rectangle alloc = (a instanceof Rectangle) ? (Rectangle)a :
584 a.getBounds();
585 r0.x = alloc.x;
586 r0.width = alloc.width;
587 }
588
589 r0.add(r1);
590 // The next line is the only difference between this method and
591 // View's implementation. We're subtracting the width of the second
592 // character. This is because this method is used by Highlighter
593 // implementations to get the area to "highlight", and if we don't do
594 // this, one character too many is highlighted thanks to our
595 // modelToView() implementation returning the actual width of the
596 // character requested!
597 if (p1>p0) r0.width -= r1.width;
598 return r0;
599
600 }
601
602
603 /**
604 * Returns the next tab stop position after a given reference position.
605 * This implementation does not support things like centering so it
606 * ignores the tabOffset argument.
607 *
608 * @param x the current position >= 0
609 * @param tabOffset the position within the text stream
610 * that the tab occurred at >= 0.
611 * @return the tab stop, measured in points >= 0
612 */
613 public float nextTabStop(float x, int tabOffset) {
614 if (tabSize == 0)
615 return x;
616 int ntabs = ((int) x - tabBase) / tabSize;
617 return tabBase + ((ntabs + 1) * tabSize);
618 }
619
620
621 /**
622 * Paints the word-wrapped text.
623 *
624 * @param g The graphics context in which to paint.
625 * @param a The shape (usually a rectangle) in which to paint.
626 */
627 public void paint(Graphics g, Shape a) {
628
629 Rectangle alloc = (a instanceof Rectangle) ?
630 (Rectangle)a : a.getBounds();
631 tabBase = alloc.x;
632
633 Graphics2D g2d = (Graphics2D)g;
634 host = (RSyntaxTextArea)getContainer();
635 int ascent = host.getMaxAscent();
636 int fontHeight = host.getLineHeight();
637 FoldManager fm = host.getFoldManager();
638
639 int n = getViewCount(); // Number of lines.
640 int x = alloc.x + getLeftInset();
641 tempRect.y = alloc.y + getTopInset();
642 Rectangle clip = g.getClipBounds();
643 for (int i = 0; i < n; i++) {
644 tempRect.x = x + getOffset(X_AXIS, i);
645 //tempRect.y = y + getOffset(Y_AXIS, i);
646 tempRect.width = getSpan(X_AXIS, i);
647 tempRect.height = getSpan(Y_AXIS, i);
648 //System.err.println("For line " + i + ": tempRect==" + tempRect);
649 if (tempRect.intersects(clip)) {
650 View view = getView(i);
651 drawView(g2d, alloc, view, fontHeight, tempRect.y+ascent);
652 }
653 tempRect.y += tempRect.height;
654 Fold possibleFold = fm.getFoldForLine(i);
655 if (possibleFold!=null && possibleFold.isCollapsed()) {
656 i += possibleFold.getCollapsedLineCount();
657 // Visible indicator of collapsed lines
658 Color c = RSyntaxUtilities.getFoldedLineBottomColor(host);
659 if (c!=null) {
660 g.setColor(c);
661 g.drawLine(x,tempRect.y-1, alloc.width,tempRect.y-1);
662 }
663 }
664 }
665
666 }
667
668
669 /**
670 * Gives notification that something was removed from the
671 * document in a location that this view is responsible for.
672 * This is implemented to simply update the children.
673 *
674 * @param changes The change information from the associated document.
675 * @param a the current allocation of the view
676 * @param f the factory to use to rebuild if the view has children
677 * @see View#removeUpdate
678 */
679 public void removeUpdate(DocumentEvent changes, Shape a, ViewFactory f) {
680
681 updateChildren(changes, a);
682
683 Rectangle alloc = ((a != null) && isAllocationValid()) ?
684 getInsideAllocation(a) : null;
685 int pos = changes.getOffset();
686 View v = getViewAtPosition(pos, alloc);
687 if (v != null)
688 v.removeUpdate(changes, alloc, f);
689
690 }
691
692
693 /**
694 * Makes a <code>Segment</code> point to the text in our
695 * document between the given positions. Note that the positions MUST be
696 * valid positions in the document.
697 *
698 * @param p0 The first position in the document.
699 * @param p1 The second position in the document.
700 * @param document The document from which you want to get the text.
701 * @param seg The segment in which to load the text.
702 */
703 private void setSegment(int p0, int p1, Document document,
704 Segment seg) {
705 try {
706//System.err.println("... in setSharedSegment, p0/p1==" + p0 + "/" + p1);
707 document.getText(p0, p1-p0, seg);
708 //System.err.println("... in setSharedSegment: s=='" + s + "'; line/numLines==" + line + "/" + numLines);
709 } catch (BadLocationException ble) { // Never happens
710 ble.printStackTrace();
711 }
712 }
713
714
715 /**
716 * Sets the size of the view. This should cause layout of the view along
717 * the given axis, if it has any layout duties.
718 *
719 * @param width the width >= 0
720 * @param height the height >= 0
721 */
722 public void setSize(float width, float height) {
723 updateMetrics();
724 if ((int) width != getWidth()) {
725 // invalidate the view itself since the childrens
726 // desired widths will be based upon this views width.
727 preferenceChanged(null, true, true);
728 widthChanging = true;
729 }
730 super.setSize(width, height);
731 widthChanging = false;
732 }
733
734
735 /**
736 * Update the child views in response to a
737 * document event.
738 */
739 void updateChildren(DocumentEvent e, Shape a) {
740
741 Element elem = getElement();
742 DocumentEvent.ElementChange ec = e.getChange(elem);
743
744 // This occurs when syntax highlighting only changes on lines
745 // (i.e. beginning a multiline comment).
746 if (e.getType()==DocumentEvent.EventType.CHANGE) {
747 //System.err.println("Updating the damage due to a CHANGE event...");
748 // FIXME: Make me repaint more intelligently.
749 getContainer().repaint();
750 //damageLineRange(startLine,endLine, a, host);
751 }
752
753 else if (ec != null) {
754
755 // the structure of this element changed.
756 Element[] removedElems = ec.getChildrenRemoved();
757 Element[] addedElems = ec.getChildrenAdded();
758 View[] added = new View[addedElems.length];
759
760 for (int i = 0; i < addedElems.length; i++)
761 added[i] = new WrappedLine(addedElems[i]);
762 //System.err.println("Replacing " + removedElems.length +
763 // " children with " + addedElems.length);
764 replace(ec.getIndex(), removedElems.length, added);
765
766 // should damge a little more intelligently.
767 if (a != null) {
768 preferenceChanged(null, true, true);
769 getContainer().repaint();
770 }
771
772 }
773
774 // update font metrics which may be used by the child views
775 updateMetrics();
776
777 }
778
779
780 final void updateMetrics() {
781 Component host = getContainer();
782 Font f = host.getFont();
783 metrics = host.getFontMetrics(f); // Metrics for the default font.
784 tabSize = getTabSize() * metrics.charWidth('m');
785 }
786
787
788 public int viewToModel(float x, float y, Shape a, Position.Bias[] bias) {
789
790 int offs = -1;
791
792 if (! isAllocationValid()) {
793 Rectangle alloc = a.getBounds();
794 setSize(alloc.width, alloc.height);
795 }
796
797 // Get the child view for the line at (x,y), and ask it for the
798 // specific offset.
799 Rectangle alloc = getInsideAllocation(a);
800 View v = getViewAtPoint((int) x, (int) y, alloc);
801 if (v != null) {
802 offs = v.viewToModel(x, y, alloc, bias);
803 }
804
805 // Code folding may have hidden the last line. If so, return the last
806 // visible offset instead of the last offset.
807 if (host.isCodeFoldingEnabled() && v==getView(getViewCount()-1) &&
808 offs==v.getEndOffset()-1) {
809 offs = host.getLastVisibleOffset();
810 }
811
812 return offs;
813
814 }
815
816
817 /**
818 * {@inheritDoc}
819 */
820 public int yForLine(Rectangle alloc, int line) throws BadLocationException {
821 return yForLineContaining(alloc,
822 getElement().getElement(line).getStartOffset());
823//return alloc.y + getOffset(Y_AXIS, line);
824 }
825
826
827 /**
828 * {@inheritDoc}
829 */
830 public int yForLineContaining(Rectangle alloc, int offs)
831 throws BadLocationException {
832 if (isAllocationValid()) {
833 // TODO: make cached Y_AXIS offsets valid even with folding enabled
834 // to speed this back up!
835 Rectangle r = (Rectangle)modelToView(offs, alloc, Bias.Forward);
836 if (r!=null) {
837 if (host.isCodeFoldingEnabled()) {
838 int line = host.getLineOfOffset(offs);
839 FoldManager fm = host.getFoldManager();
840 if (fm.isLineHidden(line)) {
841 return -1;
842 }
843 }
844 return r.y;
845 }
846 }
847 return -1;
848 }
849
850
851 /**
852 * Simple view of a line that wraps if it doesn't
853 * fit withing the horizontal space allocated.
854 * This class tries to be lightweight by carrying little
855 * state of it's own and sharing the state of the outer class
856 * with it's siblings.
857 */
858 class WrappedLine extends View {
859
860 int nlines;
861
862 WrappedLine(Element elem) {
863 super(elem);
864 }
865
866 /**
867 * Calculate the number of lines that will be rendered
868 * by logical line when it is wrapped.
869 */
870 final int calculateLineCount() {
871
872 int nlines = 0;
873 int startOffset = getStartOffset();
874 int p1 = getEndOffset();
875
876 // Get the token list for this line so we don't have to keep
877 // recomputing it if this logical line spans multiple physical
878 // lines.
879 RSyntaxTextArea textArea = (RSyntaxTextArea)getContainer();
880 RSyntaxDocument doc = (RSyntaxDocument)getDocument();
881 Element map = doc.getDefaultRootElement();
882 int line = map.getElementIndex(startOffset);
883 Token tokenList = doc.getTokenListForLine(line);
884 float x0 = 0;// FIXME: should be alloc.x!! alloc.x;//0;
885
886
887//System.err.println(">>> calculateLineCount: " + startOffset + "-" + p1);
888 for (int p0=startOffset; p0<p1; ) {
889//System.err.println("... ... " + p0 + ", " + p1);
890 nlines += 1;
891 x0 = RSyntaxUtilities.makeTokenListStartAt(tokenList, p0,
892 WrappedSyntaxView.this, textArea, x0);
893 int p = calculateBreakPosition(p0, tokenList, x0);
894
895//System.err.println("... ... ... break position p==" + p);
896 p0 = (p == p0) ? ++p : p; // this is the fix of #4410243
897 // we check on situation when
898 // width is too small and
899 // break position is calculated
900 // incorrectly.
901//System.err.println("... ... ... new p0==" + p0);
902 }
903/*
904int numLines = 0;
905try {
906 numLines = textArea.getLineCount();
907} catch (BadLocationException ble) {
908 ble.printStackTrace();
909}
910System.err.println(">>> >>> calculated number of lines for this view (line " + line + "/" + numLines + ": " + nlines);
911*/
912 return nlines;
913 }
914
915 /**
916 * Determines the preferred span for this view along an
917 * axis.
918 *
919 * @param axis may be either X_AXIS or Y_AXIS
920 * @return the span the view would like to be rendered into.
921 * Typically the view is told to render into the span
922 * that is returned, although there is no guarantee.
923 * The parent may choose to resize or break the view.
924 * @see View#getPreferredSpan
925 */
926 public float getPreferredSpan(int axis) {
927 switch (axis) {
928 case View.X_AXIS:
929 float width = getWidth();
930 if (width == Integer.MAX_VALUE) {
931 // We have been initially set to MAX_VALUE, but we don't
932 // want this as our preferred.
933 return 100f;
934 }
935 return width;
936 case View.Y_AXIS:
937 if (nlines == 0 || widthChanging)
938 nlines = calculateLineCount();
939 int h = nlines * ((RSyntaxTextArea)getContainer()).getLineHeight();
940 return h;
941 default:
942 throw new IllegalArgumentException("Invalid axis: " + axis);
943 }
944 }
945
946 /**
947 * Renders using the given rendering surface and area on that
948 * surface. The view may need to do layout and create child
949 * views to enable itself to render into the given allocation.
950 *
951 * @param g the rendering surface to use
952 * @param a the allocated region to render into
953 * @see View#paint
954 */
955 public void paint(Graphics g, Shape a) {
956 // This is done by drawView() above.
957 }
958
959 /**
960 * Provides a mapping from the document model coordinate space
961 * to the coordinate space of the view mapped to it.
962 *
963 * @param pos the position to convert
964 * @param a the allocated region to render into
965 * @return the bounding box of the given position is returned
966 * @exception BadLocationException if the given position does not
967 * represent a valid location in the associated document.
968 */
969 public Shape modelToView(int pos, Shape a, Position.Bias b)
970 throws BadLocationException {
971
972 //System.err.println("--- begin modelToView ---");
973 Rectangle alloc = a.getBounds();
974 RSyntaxTextArea textArea = (RSyntaxTextArea)getContainer();
975 alloc.height = textArea.getLineHeight();//metrics.getHeight();
976 alloc.width = 1;
977 int p0 = getStartOffset();
978 int p1 = getEndOffset();
979 int testP = (b == Position.Bias.Forward) ? pos :
980 Math.max(p0, pos - 1);
981
982 // Get the token list for this line so we don't have to keep
983 // recomputing it if this logical line spans multiple physical
984 // lines.
985 RSyntaxDocument doc = (RSyntaxDocument)getDocument();
986 Element map = doc.getDefaultRootElement();
987 int line = map.getElementIndex(p0);
988 Token tokenList = doc.getTokenListForLine(line);
989 float x0 = alloc.x;//0;
990
991 while (p0 < p1) {
992 x0 = RSyntaxUtilities.makeTokenListStartAt(tokenList, p0,
993 WrappedSyntaxView.this, textArea, x0);
994 int p = calculateBreakPosition(p0, tokenList, x0);
995 if ((pos >= p0) && (testP<p)) {//pos < p)) {
996 // it's in this line
997 alloc = RSyntaxUtilities.getLineWidthUpTo(
998 textArea, s, p0, pos,
999 WrappedSyntaxView.this,
1000 alloc, alloc.x);
1001 //System.err.println("--- end modelToView ---");
1002 return alloc;
1003 }
1004 //if (p == p1 && pos == p1) {
1005 if (p==p1-1 && pos==p1-1) {
1006 // Wants end.
1007 if (pos > p0) {
1008 alloc = RSyntaxUtilities.getLineWidthUpTo(
1009 textArea, s, p0, pos,
1010 WrappedSyntaxView.this,
1011 alloc, alloc.x);
1012 }
1013 //System.err.println("--- end modelToView ---");
1014 return alloc;
1015 }
1016
1017 p0 = (p == p0) ? p1 : p;
1018 //System.err.println("... ... Incrementing y");
1019 alloc.y += alloc.height;
1020
1021 }
1022
1023 throw new BadLocationException(null, pos);
1024
1025 }
1026
1027 /**
1028 * Provides a mapping from the view coordinate space to the logical
1029 * coordinate space of the model.
1030 *
1031 * @param fx the X coordinate
1032 * @param fy the Y coordinate
1033 * @param a the allocated region to render into
1034 * @return the location within the model that best represents the
1035 * given point in the view
1036 * @see View#viewToModel
1037 */
1038 public int viewToModel(float fx, float fy, Shape a, Position.Bias[] bias) {
1039
1040 // PENDING(prinz) implement bias properly
1041 bias[0] = Position.Bias.Forward;
1042
1043 Rectangle alloc = (Rectangle) a;
1044 RSyntaxDocument doc = (RSyntaxDocument)getDocument();
1045 int x = (int) fx;
1046 int y = (int) fy;
1047 if (y < alloc.y) {
1048 // above the area covered by this icon, so the the position
1049 // is assumed to be the start of the coverage for this view.
1050 return getStartOffset();
1051 }
1052 else if (y > alloc.y + alloc.height) {
1053 // below the area covered by this icon, so the the position
1054 // is assumed to be the end of the coverage for this view.
1055 return getEndOffset() - 1;
1056 }
1057 else {
1058
1059 // positioned within the coverage of this view vertically,
1060 // so we figure out which line the point corresponds to.
1061 // if the line is greater than the number of lines
1062 // contained, then simply use the last line as it represents
1063 // the last possible place we can position to.
1064
1065 RSyntaxTextArea textArea = (RSyntaxTextArea)getContainer();
1066 alloc.height = textArea.getLineHeight();
1067 int p1 = getEndOffset();
1068
1069 // Get the token list for this line so we don't have to keep
1070 // recomputing it if this logical line spans multiple
1071 // physical lines.
1072 Element map = doc.getDefaultRootElement();
1073 int p0 = getStartOffset();
1074 int line = map.getElementIndex(p0);
1075 Token tlist = doc.getTokenListForLine(line);
1076
1077 // Look at each physical line-chunk of this logical line.
1078 while (p0<p1) {
1079
1080 // We can always use alloc.x since we always break
1081 // lines so they start at the beginning of a physical
1082 // line.
1083 RSyntaxUtilities.makeTokenListStartAt(tlist, p0,
1084 WrappedSyntaxView.this, textArea, alloc.x);
1085 int p = calculateBreakPosition(p0, tlist, alloc.x);
1086
1087 // If desired view position is in this physical chunk.
1088 if ((y>=alloc.y) && (y<(alloc.y+alloc.height))) {
1089
1090 // Point is to the left of the line
1091 if (x < alloc.x) {
1092 return p0;
1093 }
1094
1095 // Point is to the right of the line
1096 else if (x > alloc.x + alloc.width) {
1097 return p - 1;
1098 }
1099
1100 // Point is in this physical line!
1101 else {
1102
1103 // Start at alloc.x since this chunk starts
1104 // at the beginning of a physical line.
1105 int n = tlist.getListOffset(textArea,
1106 WrappedSyntaxView.this,
1107 alloc.x, x);
1108
1109 // NOTE: We needed to add the max() with
1110 // p0 as getTokenListForLine returns -1
1111 // for empty lines (just a null token).
1112 // How did this work before?
1113 // FIXME: Have null tokens have their
1114 // offset but a -1 length.
1115 return Math.max(Math.min(n, p1-1), p0);
1116
1117 } // End of else.
1118
1119 } // End of if ((y>=alloc.y) && ...
1120
1121 p0 = (p == p0) ? p1 : p;
1122 alloc.y += alloc.height;
1123
1124 } // End of while (p0<p1).
1125
1126 return getEndOffset() - 1;
1127
1128 } // End of else.
1129
1130 }
1131
1132 private void handleDocumentEvent(DocumentEvent e, Shape a,
1133 ViewFactory f) {
1134 int n = calculateLineCount();
1135 if (this.nlines != n) {
1136 this.nlines = n;
1137 WrappedSyntaxView.this.preferenceChanged(this, false, true);
1138 // have to repaint any views after the receiver.
1139 RSyntaxTextArea textArea = (RSyntaxTextArea)getContainer();
1140 textArea.repaint();
1141 // Must also revalidate container so gutter components, such
1142 // as line numbers, get updated for this line's new height
1143 Gutter gutter = RSyntaxUtilities.getGutter(textArea);
1144 if (gutter!=null) {
1145 gutter.revalidate();
1146 gutter.repaint();
1147 }
1148 }
1149 else if (a != null) {
1150 Component c = getContainer();
1151 Rectangle alloc = (Rectangle) a;
1152 c.repaint(alloc.x, alloc.y, alloc.width, alloc.height);
1153 }
1154 }
1155
1156 public void insertUpdate(DocumentEvent e, Shape a, ViewFactory f) {
1157 handleDocumentEvent(e, a, f);
1158 }
1159
1160 public void removeUpdate(DocumentEvent e, Shape a, ViewFactory f) {
1161 handleDocumentEvent(e, a, f);
1162 }
1163
1164 }
1165
1166
1167}
Note: See TracBrowser for help on using the repository browser.