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