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