blob: fb7aede73748731833974d6593167329e460235f [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 1998-2006 Sun Microsystems, Inc. All Rights Reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25package javax.swing.text;
26
27import java.util.Vector;
28import java.util.Properties;
29import java.awt.*;
30import java.lang.ref.SoftReference;
31import javax.swing.event.*;
32
33/**
34 * View of plain text (text with only one font and color)
35 * that does line-wrapping. This view expects that its
36 * associated element has child elements that represent
37 * the lines it should be wrapping. It is implemented
38 * as a vertical box that contains logical line views.
39 * The logical line views are nested classes that render
40 * the logical line as multiple physical line if the logical
41 * line is too wide to fit within the allocation. The
42 * line views draw upon the outer class for its state
43 * to reduce their memory requirements.
44 * <p>
45 * The line views do all of their rendering through the
46 * <code>drawLine</code> method which in turn does all of
47 * its rendering through the <code>drawSelectedText</code>
48 * and <code>drawUnselectedText</code> methods. This
49 * enables subclasses to easily specialize the rendering
50 * without concern for the layout aspects.
51 *
52 * @author Timothy Prinzing
53 * @see View
54 */
55public class WrappedPlainView extends BoxView implements TabExpander {
56
57 /**
58 * Creates a new WrappedPlainView. Lines will be wrapped
59 * on character boundaries.
60 *
61 * @param elem the element underlying the view
62 */
63 public WrappedPlainView(Element elem) {
64 this(elem, false);
65 }
66
67 /**
68 * Creates a new WrappedPlainView. Lines can be wrapped on
69 * either character or word boundaries depending upon the
70 * setting of the wordWrap parameter.
71 *
72 * @param elem the element underlying the view
73 * @param wordWrap should lines be wrapped on word boundaries?
74 */
75 public WrappedPlainView(Element elem, boolean wordWrap) {
76 super(elem, Y_AXIS);
77 this.wordWrap = wordWrap;
78 }
79
80 /**
81 * Returns the tab size set for the document, defaulting to 8.
82 *
83 * @return the tab size
84 */
85 protected int getTabSize() {
86 Integer i = (Integer) getDocument().getProperty(PlainDocument.tabSizeAttribute);
87 int size = (i != null) ? i.intValue() : 8;
88 return size;
89 }
90
91 /**
92 * Renders a line of text, suppressing whitespace at the end
93 * and expanding any tabs. This is implemented to make calls
94 * to the methods <code>drawUnselectedText</code> and
95 * <code>drawSelectedText</code> so that the way selected and
96 * unselected text are rendered can be customized.
97 *
98 * @param p0 the starting document location to use >= 0
99 * @param p1 the ending document location to use >= p1
100 * @param g the graphics context
101 * @param x the starting X position >= 0
102 * @param y the starting Y position >= 0
103 * @see #drawUnselectedText
104 * @see #drawSelectedText
105 */
106 protected void drawLine(int p0, int p1, Graphics g, int x, int y) {
107 Element lineMap = getElement();
108 Element line = lineMap.getElement(lineMap.getElementIndex(p0));
109 Element elem;
110
111 try {
112 if (line.isLeaf()) {
113 drawText(line, p0, p1, g, x, y);
114 } else {
115 // this line contains the composed text.
116 int idx = line.getElementIndex(p0);
117 int lastIdx = line.getElementIndex(p1);
118 for(; idx <= lastIdx; idx++) {
119 elem = line.getElement(idx);
120 int start = Math.max(elem.getStartOffset(), p0);
121 int end = Math.min(elem.getEndOffset(), p1);
122 x = drawText(elem, start, end, g, x, y);
123 }
124 }
125 } catch (BadLocationException e) {
126 throw new StateInvariantError("Can't render: " + p0 + "," + p1);
127 }
128 }
129
130 private int drawText(Element elem, int p0, int p1, Graphics g, int x, int y) throws BadLocationException {
131 p1 = Math.min(getDocument().getLength(), p1);
132 AttributeSet attr = elem.getAttributes();
133
134 if (Utilities.isComposedTextAttributeDefined(attr)) {
135 g.setColor(unselected);
136 x = Utilities.drawComposedText(this, attr, g, x, y,
137 p0-elem.getStartOffset(),
138 p1-elem.getStartOffset());
139 } else {
140 if (sel0 == sel1 || selected == unselected) {
141 // no selection, or it is invisible
142 x = drawUnselectedText(g, x, y, p0, p1);
143 } else if ((p0 >= sel0 && p0 <= sel1) && (p1 >= sel0 && p1 <= sel1)) {
144 x = drawSelectedText(g, x, y, p0, p1);
145 } else if (sel0 >= p0 && sel0 <= p1) {
146 if (sel1 >= p0 && sel1 <= p1) {
147 x = drawUnselectedText(g, x, y, p0, sel0);
148 x = drawSelectedText(g, x, y, sel0, sel1);
149 x = drawUnselectedText(g, x, y, sel1, p1);
150 } else {
151 x = drawUnselectedText(g, x, y, p0, sel0);
152 x = drawSelectedText(g, x, y, sel0, p1);
153 }
154 } else if (sel1 >= p0 && sel1 <= p1) {
155 x = drawSelectedText(g, x, y, p0, sel1);
156 x = drawUnselectedText(g, x, y, sel1, p1);
157 } else {
158 x = drawUnselectedText(g, x, y, p0, p1);
159 }
160 }
161
162 return x;
163 }
164
165 /**
166 * Renders the given range in the model as normal unselected
167 * text.
168 *
169 * @param g the graphics context
170 * @param x the starting X coordinate >= 0
171 * @param y the starting Y coordinate >= 0
172 * @param p0 the beginning position in the model >= 0
173 * @param p1 the ending position in the model >= p0
174 * @return the X location of the end of the range >= 0
175 * @exception BadLocationException if the range is invalid
176 */
177 protected int drawUnselectedText(Graphics g, int x, int y,
178 int p0, int p1) throws BadLocationException {
179 g.setColor(unselected);
180 Document doc = getDocument();
181 Segment segment = SegmentCache.getSharedSegment();
182 doc.getText(p0, p1 - p0, segment);
183 int ret = Utilities.drawTabbedText(this, segment, x, y, g, this, p0);
184 SegmentCache.releaseSharedSegment(segment);
185 return ret;
186 }
187
188 /**
189 * Renders the given range in the model as selected text. This
190 * is implemented to render the text in the color specified in
191 * the hosting component. It assumes the highlighter will render
192 * the selected background.
193 *
194 * @param g the graphics context
195 * @param x the starting X coordinate >= 0
196 * @param y the starting Y coordinate >= 0
197 * @param p0 the beginning position in the model >= 0
198 * @param p1 the ending position in the model >= p0
199 * @return the location of the end of the range.
200 * @exception BadLocationException if the range is invalid
201 */
202 protected int drawSelectedText(Graphics g, int x,
203 int y, int p0, int p1) throws BadLocationException {
204 g.setColor(selected);
205 Document doc = getDocument();
206 Segment segment = SegmentCache.getSharedSegment();
207 doc.getText(p0, p1 - p0, segment);
208 int ret = Utilities.drawTabbedText(this, segment, x, y, g, this, p0);
209 SegmentCache.releaseSharedSegment(segment);
210 return ret;
211 }
212
213 /**
214 * Gives access to a buffer that can be used to fetch
215 * text from the associated document.
216 *
217 * @return the buffer
218 */
219 protected final Segment getLineBuffer() {
220 if (lineBuffer == null) {
221 lineBuffer = new Segment();
222 }
223 return lineBuffer;
224 }
225
226 /**
227 * This is called by the nested wrapped line
228 * views to determine the break location. This can
229 * be reimplemented to alter the breaking behavior.
230 * It will either break at word or character boundaries
231 * depending upon the break argument given at
232 * construction.
233 */
234 protected int calculateBreakPosition(int p0, int p1) {
235 int p;
236 Segment segment = SegmentCache.getSharedSegment();
237 loadText(segment, p0, p1);
238 int currentWidth = getWidth();
239 if (currentWidth == Integer.MAX_VALUE) {
240 currentWidth = (int) getDefaultSpan(View.X_AXIS);
241 }
242 if (wordWrap) {
243 p = p0 + Utilities.getBreakLocation(segment, metrics,
244 tabBase, tabBase + currentWidth,
245 this, p0);
246 } else {
247 p = p0 + Utilities.getTabbedTextOffset(segment, metrics,
248 tabBase, tabBase + currentWidth,
249 this, p0, false);
250 }
251 SegmentCache.releaseSharedSegment(segment);
252 return p;
253 }
254
255 /**
256 * Loads all of the children to initialize the view.
257 * This is called by the <code>setParent</code> method.
258 * Subclasses can reimplement this to initialize their
259 * child views in a different manner. The default
260 * implementation creates a child view for each
261 * child element.
262 *
263 * @param f the view factory
264 */
265 protected void loadChildren(ViewFactory f) {
266 Element e = getElement();
267 int n = e.getElementCount();
268 if (n > 0) {
269 View[] added = new View[n];
270 for (int i = 0; i < n; i++) {
271 added[i] = new WrappedLine(e.getElement(i));
272 }
273 replace(0, 0, added);
274 }
275 }
276
277 /**
278 * Update the child views in response to a
279 * document event.
280 */
281 void updateChildren(DocumentEvent e, Shape a) {
282 Element elem = getElement();
283 DocumentEvent.ElementChange ec = e.getChange(elem);
284 if (ec != null) {
285 // the structure of this element changed.
286 Element[] removedElems = ec.getChildrenRemoved();
287 Element[] addedElems = ec.getChildrenAdded();
288 View[] added = new View[addedElems.length];
289 for (int i = 0; i < addedElems.length; i++) {
290 added[i] = new WrappedLine(addedElems[i]);
291 }
292 replace(ec.getIndex(), removedElems.length, added);
293
294 // should damge a little more intelligently.
295 if (a != null) {
296 preferenceChanged(null, true, true);
297 getContainer().repaint();
298 }
299 }
300
301 // update font metrics which may be used by the child views
302 updateMetrics();
303 }
304
305 /**
306 * Load the text buffer with the given range
307 * of text. This is used by the fragments
308 * broken off of this view as well as this
309 * view itself.
310 */
311 final void loadText(Segment segment, int p0, int p1) {
312 try {
313 Document doc = getDocument();
314 doc.getText(p0, p1 - p0, segment);
315 } catch (BadLocationException bl) {
316 throw new StateInvariantError("Can't get line text");
317 }
318 }
319
320 final void updateMetrics() {
321 Component host = getContainer();
322 Font f = host.getFont();
323 metrics = host.getFontMetrics(f);
324 tabSize = getTabSize() * metrics.charWidth('m');
325 }
326
327 /**
328 * Return reasonable default values for the view dimensions. The standard
329 * text terminal size 80x24 is pretty suitable for the wrapped plain view.
330 */
331 private float getDefaultSpan(int axis) {
332 switch (axis) {
333 case View.X_AXIS:
334 return 80 * metrics.getWidths()['M'];
335 case View.Y_AXIS:
336 return 24 * metrics.getHeight();
337 default:
338 throw new IllegalArgumentException("Invalid axis: " + axis);
339 }
340 }
341
342 // --- TabExpander methods ------------------------------------------
343
344 /**
345 * Returns the next tab stop position after a given reference position.
346 * This implementation does not support things like centering so it
347 * ignores the tabOffset argument.
348 *
349 * @param x the current position >= 0
350 * @param tabOffset the position within the text stream
351 * that the tab occurred at >= 0.
352 * @return the tab stop, measured in points >= 0
353 */
354 public float nextTabStop(float x, int tabOffset) {
355 if (tabSize == 0)
356 return x;
357 int ntabs = ((int) x - tabBase) / tabSize;
358 return tabBase + ((ntabs + 1) * tabSize);
359 }
360
361
362 // --- View methods -------------------------------------
363
364 /**
365 * Renders using the given rendering surface and area
366 * on that surface. This is implemented to stash the
367 * selection positions, selection colors, and font
368 * metrics for the nested lines to use.
369 *
370 * @param g the rendering surface to use
371 * @param a the allocated region to render into
372 *
373 * @see View#paint
374 */
375 public void paint(Graphics g, Shape a) {
376 Rectangle alloc = (Rectangle) a;
377 tabBase = alloc.x;
378 JTextComponent host = (JTextComponent) getContainer();
379 sel0 = host.getSelectionStart();
380 sel1 = host.getSelectionEnd();
381 unselected = (host.isEnabled()) ?
382 host.getForeground() : host.getDisabledTextColor();
383 Caret c = host.getCaret();
384 selected = c.isSelectionVisible() && host.getHighlighter() != null ?
385 host.getSelectedTextColor() : unselected;
386 g.setFont(host.getFont());
387
388 // superclass paints the children
389 super.paint(g, a);
390 }
391
392 /**
393 * Sets the size of the view. This should cause
394 * layout of the view along the given axis, if it
395 * has any layout duties.
396 *
397 * @param width the width >= 0
398 * @param height the height >= 0
399 */
400 public void setSize(float width, float height) {
401 updateMetrics();
402 if ((int) width != getWidth()) {
403 // invalidate the view itself since the childrens
404 // desired widths will be based upon this views width.
405 preferenceChanged(null, true, true);
406 widthChanging = true;
407 }
408 super.setSize(width, height);
409 widthChanging = false;
410 }
411
412 /**
413 * Determines the preferred span for this view along an
414 * axis. This is implemented to provide the superclass
415 * behavior after first making sure that the current font
416 * metrics are cached (for the nested lines which use
417 * the metrics to determine the height of the potentially
418 * wrapped lines).
419 *
420 * @param axis may be either View.X_AXIS or View.Y_AXIS
421 * @return the span the view would like to be rendered into.
422 * Typically the view is told to render into the span
423 * that is returned, although there is no guarantee.
424 * The parent may choose to resize or break the view.
425 * @see View#getPreferredSpan
426 */
427 public float getPreferredSpan(int axis) {
428 updateMetrics();
429 return super.getPreferredSpan(axis);
430 }
431
432 /**
433 * Determines the minimum span for this view along an
434 * axis. This is implemented to provide the superclass
435 * behavior after first making sure that the current font
436 * metrics are cached (for the nested lines which use
437 * the metrics to determine the height of the potentially
438 * wrapped lines).
439 *
440 * @param axis may be either View.X_AXIS or View.Y_AXIS
441 * @return the span the view would like to be rendered into.
442 * Typically the view is told to render into the span
443 * that is returned, although there is no guarantee.
444 * The parent may choose to resize or break the view.
445 * @see View#getMinimumSpan
446 */
447 public float getMinimumSpan(int axis) {
448 updateMetrics();
449 return super.getMinimumSpan(axis);
450 }
451
452 /**
453 * Determines the maximum span for this view along an
454 * axis. This is implemented to provide the superclass
455 * behavior after first making sure that the current font
456 * metrics are cached (for the nested lines which use
457 * the metrics to determine the height of the potentially
458 * wrapped lines).
459 *
460 * @param axis may be either View.X_AXIS or View.Y_AXIS
461 * @return the span the view would like to be rendered into.
462 * Typically the view is told to render into the span
463 * that is returned, although there is no guarantee.
464 * The parent may choose to resize or break the view.
465 * @see View#getMaximumSpan
466 */
467 public float getMaximumSpan(int axis) {
468 updateMetrics();
469 return super.getMaximumSpan(axis);
470 }
471
472 /**
473 * Gives notification that something was inserted into the
474 * document in a location that this view is responsible for.
475 * This is implemented to simply update the children.
476 *
477 * @param e the change information from the associated document
478 * @param a the current allocation of the view
479 * @param f the factory to use to rebuild if the view has children
480 * @see View#insertUpdate
481 */
482 public void insertUpdate(DocumentEvent e, Shape a, ViewFactory f) {
483 updateChildren(e, a);
484
485 Rectangle alloc = ((a != null) && isAllocationValid()) ?
486 getInsideAllocation(a) : null;
487 int pos = e.getOffset();
488 View v = getViewAtPosition(pos, alloc);
489 if (v != null) {
490 v.insertUpdate(e, alloc, f);
491 }
492 }
493
494 /**
495 * Gives notification that something was removed from the
496 * document in a location that this view is responsible for.
497 * This is implemented to simply update the children.
498 *
499 * @param e the change information from the associated document
500 * @param a the current allocation of the view
501 * @param f the factory to use to rebuild if the view has children
502 * @see View#removeUpdate
503 */
504 public void removeUpdate(DocumentEvent e, Shape a, ViewFactory f) {
505 updateChildren(e, a);
506
507 Rectangle alloc = ((a != null) && isAllocationValid()) ?
508 getInsideAllocation(a) : null;
509 int pos = e.getOffset();
510 View v = getViewAtPosition(pos, alloc);
511 if (v != null) {
512 v.removeUpdate(e, alloc, f);
513 }
514 }
515
516 /**
517 * Gives notification from the document that attributes were changed
518 * in a location that this view is responsible for.
519 *
520 * @param e the change information from the associated document
521 * @param a the current allocation of the view
522 * @param f the factory to use to rebuild if the view has children
523 * @see View#changedUpdate
524 */
525 public void changedUpdate(DocumentEvent e, Shape a, ViewFactory f) {
526 updateChildren(e, a);
527 }
528
529 // --- variables -------------------------------------------
530
531 FontMetrics metrics;
532 Segment lineBuffer;
533 boolean widthChanging;
534 int tabBase;
535 int tabSize;
536 boolean wordWrap;
537
538 int sel0;
539 int sel1;
540 Color unselected;
541 Color selected;
542
543
544 /**
545 * Simple view of a line that wraps if it doesn't
546 * fit withing the horizontal space allocated.
547 * This class tries to be lightweight by carrying little
548 * state of it's own and sharing the state of the outer class
549 * with it's sibblings.
550 */
551 class WrappedLine extends View {
552
553 WrappedLine(Element elem) {
554 super(elem);
555 lineCount = -1;
556 }
557
558 /**
559 * Determines the preferred span for this view along an
560 * axis.
561 *
562 * @param axis may be either X_AXIS or Y_AXIS
563 * @return the span the view would like to be rendered into.
564 * Typically the view is told to render into the span
565 * that is returned, although there is no guarantee.
566 * The parent may choose to resize or break the view.
567 * @see View#getPreferredSpan
568 */
569 public float getPreferredSpan(int axis) {
570 switch (axis) {
571 case View.X_AXIS:
572 float width = getWidth();
573 if (width == Integer.MAX_VALUE) {
574 // We have been initially set to MAX_VALUE, but we don't
575 // want this as our preferred.
576 width = getDefaultSpan(axis);
577 }
578 return width;
579 case View.Y_AXIS:
580 if (getDocument().getLength() > 0) {
581 if ((lineCount < 0) || widthChanging) {
582 breakLines(getStartOffset());
583 }
584 return lineCount * metrics.getHeight();
585 } else {
586 return getDefaultSpan(axis);
587 }
588 default:
589 throw new IllegalArgumentException("Invalid axis: " + axis);
590 }
591 }
592
593 /**
594 * Renders using the given rendering surface and area on that
595 * surface. The view may need to do layout and create child
596 * views to enable itself to render into the given allocation.
597 *
598 * @param g the rendering surface to use
599 * @param a the allocated region to render into
600 * @see View#paint
601 */
602 public void paint(Graphics g, Shape a) {
603 Rectangle alloc = (Rectangle) a;
604 int y = alloc.y + metrics.getAscent();
605 int x = alloc.x;
606
607 JTextComponent host = (JTextComponent)getContainer();
608 Highlighter h = host.getHighlighter();
609 LayeredHighlighter dh = (h instanceof LayeredHighlighter) ?
610 (LayeredHighlighter)h : null;
611
612 int start = getStartOffset();
613 int end = getEndOffset();
614 int p0 = start;
615 int[] lineEnds = getLineEnds();
616 for (int i = 0; i < lineCount; i++) {
617 int p1 = (lineEnds == null) ? end :
618 start + lineEnds[i];
619 if (dh != null) {
620 int hOffset = (p1 == end)
621 ? (p1 - 1)
622 : p1;
623 dh.paintLayeredHighlights(g, p0, hOffset, a, host, this);
624 }
625 drawLine(p0, p1, g, x, y);
626
627 p0 = p1;
628 y += metrics.getHeight();
629 }
630 }
631
632 /**
633 * Provides a mapping from the document model coordinate space
634 * to the coordinate space of the view mapped to it.
635 *
636 * @param pos the position to convert
637 * @param a the allocated region to render into
638 * @return the bounding box of the given position is returned
639 * @exception BadLocationException if the given position does not represent a
640 * valid location in the associated document
641 * @see View#modelToView
642 */
643 public Shape modelToView(int pos, Shape a, Position.Bias b)
644 throws BadLocationException {
645 Rectangle alloc = a.getBounds();
646 alloc.height = metrics.getHeight();
647 alloc.width = 1;
648
649 int p0 = getStartOffset();
650 if (pos < p0 || pos > getEndOffset()) {
651 throw new BadLocationException("Position out of range", pos);
652 }
653
654 int testP = (b == Position.Bias.Forward) ? pos :
655 Math.max(p0, pos - 1);
656 int line = 0;
657 int[] lineEnds = getLineEnds();
658 if (lineEnds != null) {
659 line = findLine(testP - p0);
660 if (line > 0) {
661 p0 += lineEnds[line - 1];
662 }
663 alloc.y += alloc.height * line;
664 }
665
666 if (pos > p0) {
667 Segment segment = SegmentCache.getSharedSegment();
668 loadText(segment, p0, pos);
669 alloc.x += Utilities.getTabbedTextWidth(segment, metrics,
670 alloc.x, WrappedPlainView.this, p0);
671 SegmentCache.releaseSharedSegment(segment);
672 }
673 return alloc;
674 }
675
676 /**
677 * Provides a mapping from the view coordinate space to the logical
678 * coordinate space of the model.
679 *
680 * @param fx the X coordinate
681 * @param fy the Y coordinate
682 * @param a the allocated region to render into
683 * @return the location within the model that best represents the
684 * given point in the view
685 * @see View#viewToModel
686 */
687 public int viewToModel(float fx, float fy, Shape a, Position.Bias[] bias) {
688 // PENDING(prinz) implement bias properly
689 bias[0] = Position.Bias.Forward;
690
691 Rectangle alloc = (Rectangle) a;
692 int x = (int) fx;
693 int y = (int) fy;
694 if (y < alloc.y) {
695 // above the area covered by this icon, so the the position
696 // is assumed to be the start of the coverage for this view.
697 return getStartOffset();
698 } else if (y > alloc.y + alloc.height) {
699 // below the area covered by this icon, so the the position
700 // is assumed to be the end of the coverage for this view.
701 return getEndOffset() - 1;
702 } else {
703 // positioned within the coverage of this view vertically,
704 // so we figure out which line the point corresponds to.
705 // if the line is greater than the number of lines contained, then
706 // simply use the last line as it represents the last possible place
707 // we can position to.
708 alloc.height = metrics.getHeight();
709 int line = (alloc.height > 0 ?
710 (y - alloc.y) / alloc.height : lineCount - 1);
711 if (line >= lineCount) {
712 return getEndOffset() - 1;
713 } else {
714 int p0 = getStartOffset();
715 int p1;
716 if (lineCount == 1) {
717 p1 = getEndOffset();
718 } else {
719 int[] lineEnds = getLineEnds();
720 p1 = p0 + lineEnds[line];
721 if (line > 0) {
722 p0 += lineEnds[line - 1];
723 }
724 }
725
726 if (x < alloc.x) {
727 // point is to the left of the line
728 return p0;
729 } else if (x > alloc.x + alloc.width) {
730 // point is to the right of the line
731 return p1 - 1;
732 } else {
733 // Determine the offset into the text
734 Segment segment = SegmentCache.getSharedSegment();
735 loadText(segment, p0, p1);
736 int n = Utilities.getTabbedTextOffset(segment, metrics,
737 alloc.x, x,
738 WrappedPlainView.this, p0);
739 SegmentCache.releaseSharedSegment(segment);
740 return Math.min(p0 + n, p1 - 1);
741 }
742 }
743 }
744 }
745
746 public void insertUpdate(DocumentEvent e, Shape a, ViewFactory f) {
747 update(e, a);
748 }
749
750 public void removeUpdate(DocumentEvent e, Shape a, ViewFactory f) {
751 update(e, a);
752 }
753
754 private void update(DocumentEvent ev, Shape a) {
755 int oldCount = lineCount;
756 breakLines(ev.getOffset());
757 if (oldCount != lineCount) {
758 WrappedPlainView.this.preferenceChanged(this, false, true);
759 // have to repaint any views after the receiver.
760 getContainer().repaint();
761 } else if (a != null) {
762 Component c = getContainer();
763 Rectangle alloc = (Rectangle) a;
764 c.repaint(alloc.x, alloc.y, alloc.width, alloc.height);
765 }
766 }
767
768 /**
769 * Returns line cache. If the cache was GC'ed, recreates it.
770 * If there's no cache, returns null
771 */
772 final int[] getLineEnds() {
773 if (lineCache == null) {
774 return null;
775 } else {
776 int[] lineEnds = lineCache.get();
777 if (lineEnds == null) {
778 // Cache was GC'ed, so rebuild it
779 return breakLines(getStartOffset());
780 } else {
781 return lineEnds;
782 }
783 }
784 }
785
786 /**
787 * Creates line cache if text breaks into more than one physical line.
788 * @param startPos position to start breaking from
789 * @return the cache created, ot null if text breaks into one line
790 */
791 final int[] breakLines(int startPos) {
792 int[] lineEnds = (lineCache == null) ? null : lineCache.get();
793 int[] oldLineEnds = lineEnds;
794 int start = getStartOffset();
795 int lineIndex = 0;
796 if (lineEnds != null) {
797 lineIndex = findLine(startPos - start);
798 if (lineIndex > 0) {
799 lineIndex--;
800 }
801 }
802
803 int p0 = (lineIndex == 0) ? start : start + lineEnds[lineIndex - 1];
804 int p1 = getEndOffset();
805 while (p0 < p1) {
806 int p = calculateBreakPosition(p0, p1);
807 p0 = (p == p0) ? ++p : p; // 4410243
808
809 if (lineIndex == 0 && p0 >= p1) {
810 // do not use cache if there's only one line
811 lineCache = null;
812 lineEnds = null;
813 lineIndex = 1;
814 break;
815 } else if (lineEnds == null || lineIndex >= lineEnds.length) {
816 // we have 2+ lines, and the cache is not big enough
817 // we try to estimate total number of lines
818 double growFactor = ((double)(p1 - start) / (p0 - start));
819 int newSize = (int)Math.ceil((lineIndex + 1) * growFactor);
820 newSize = Math.max(newSize, lineIndex + 2);
821 int[] tmp = new int[newSize];
822 if (lineEnds != null) {
823 System.arraycopy(lineEnds, 0, tmp, 0, lineIndex);
824 }
825 lineEnds = tmp;
826 }
827 lineEnds[lineIndex++] = p0 - start;
828 }
829
830 lineCount = lineIndex;
831 if (lineCount > 1) {
832 // check if the cache is too big
833 int maxCapacity = lineCount + lineCount / 3;
834 if (lineEnds.length > maxCapacity) {
835 int[] tmp = new int[maxCapacity];
836 System.arraycopy(lineEnds, 0, tmp, 0, lineCount);
837 lineEnds = tmp;
838 }
839 }
840
841 if (lineEnds != null && lineEnds != oldLineEnds) {
842 lineCache = new SoftReference<int[]>(lineEnds);
843 }
844 return lineEnds;
845 }
846
847 /**
848 * Binary search in the cache for line containing specified offset
849 * (which is relative to the beginning of the view). This method
850 * assumes that cache exists.
851 */
852 private int findLine(int offset) {
853 int[] lineEnds = lineCache.get();
854 if (offset < lineEnds[0]) {
855 return 0;
856 } else if (offset > lineEnds[lineCount - 1]) {
857 return lineCount;
858 } else {
859 return findLine(lineEnds, offset, 0, lineCount - 1);
860 }
861 }
862
863 private int findLine(int[] array, int offset, int min, int max) {
864 if (max - min <= 1) {
865 return max;
866 } else {
867 int mid = (max + min) / 2;
868 return (offset < array[mid]) ?
869 findLine(array, offset, min, mid) :
870 findLine(array, offset, mid, max);
871 }
872 }
873
874 int lineCount;
875 SoftReference<int[]> lineCache = null;
876 }
877}