853 lines · 31.9 KB · Java
RawDownload
/* * Copyright (c) 1998, 2011, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation.  Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */package javax.swing.text; import java.awt.*;import java.lang.ref.SoftReference;import javax.swing.event.*; /** * View of plain text (text with only one font and color) * that does line-wrapping.  This view expects that its * associated element has child elements that represent * the lines it should be wrapping.  It is implemented * as a vertical box that contains logical line views. * The logical line views are nested classes that render * the logical line as multiple physical line if the logical * line is too wide to fit within the allocation.  The * line views draw upon the outer class for its state * to reduce their memory requirements. * <p> * The line views do all of their rendering through the * <code>drawLine</code> method which in turn does all of * its rendering through the <code>drawSelectedText</code> * and <code>drawUnselectedText</code> methods.  This * enables subclasses to easily specialize the rendering * without concern for the layout aspects. * * @author  Timothy Prinzing * @see     View */public class WrappedPlainView extends BoxView implements TabExpander {     /**     * Creates a new WrappedPlainView.  Lines will be wrapped     * on character boundaries.     *     * @param elem the element underlying the view     */    public WrappedPlainView(Element elem) {        this(elem, false);    }     /**     * Creates a new WrappedPlainView.  Lines can be wrapped on     * either character or word boundaries depending upon the     * setting of the wordWrap parameter.     *     * @param elem the element underlying the view     * @param wordWrap should lines be wrapped on word boundaries?     */    public WrappedPlainView(Element elem, boolean wordWrap) {        super(elem, Y_AXIS);        this.wordWrap = wordWrap;    }     /**     * Returns the tab size set for the document, defaulting to 8.     *     * @return the tab size     */    protected int getTabSize() {        Integer i = (Integer) getDocument().getProperty(PlainDocument.tabSizeAttribute);        int size = (i != null) ? i.intValue() : 8;        return size;    }     /**     * Renders a line of text, suppressing whitespace at the end     * and expanding any tabs.  This is implemented to make calls     * to the methods <code>drawUnselectedText</code> and     * <code>drawSelectedText</code> so that the way selected and     * unselected text are rendered can be customized.     *     * @param p0 the starting document location to use >= 0     * @param p1 the ending document location to use >= p1     * @param g the graphics context     * @param x the starting X position >= 0     * @param y the starting Y position >= 0     * @see #drawUnselectedText     * @see #drawSelectedText     */    protected void drawLine(int p0, int p1, Graphics g, int x, int y) {        Element lineMap = getElement();        Element line = lineMap.getElement(lineMap.getElementIndex(p0));        Element elem;         try {            if (line.isLeaf()) {                 drawText(line, p0, p1, g, x, y);            } else {                // this line contains the composed text.                int idx = line.getElementIndex(p0);                int lastIdx = line.getElementIndex(p1);                for(; idx <= lastIdx; idx++) {                    elem = line.getElement(idx);                    int start = Math.max(elem.getStartOffset(), p0);                    int end = Math.min(elem.getEndOffset(), p1);                    x = drawText(elem, start, end, g, x, y);                }            }        } catch (BadLocationException e) {            throw new StateInvariantError("Can't render: " + p0 + "," + p1);        }    }     private int drawText(Element elem, int p0, int p1, Graphics g, int x, int y) throws BadLocationException {        p1 = Math.min(getDocument().getLength(), p1);        AttributeSet attr = elem.getAttributes();         if (Utilities.isComposedTextAttributeDefined(attr)) {            g.setColor(unselected);            x = Utilities.drawComposedText(this, attr, g, x, y,                                        p0-elem.getStartOffset(),                                        p1-elem.getStartOffset());        } else {            if (sel0 == sel1 || selected == unselected) {                // no selection, or it is invisible                x = drawUnselectedText(g, x, y, p0, p1);            } else if ((p0 >= sel0 && p0 <= sel1) && (p1 >= sel0 && p1 <= sel1)) {                x = drawSelectedText(g, x, y, p0, p1);            } else if (sel0 >= p0 && sel0 <= p1) {                if (sel1 >= p0 && sel1 <= p1) {                    x = drawUnselectedText(g, x, y, p0, sel0);                    x = drawSelectedText(g, x, y, sel0, sel1);                    x = drawUnselectedText(g, x, y, sel1, p1);                } else {                    x = drawUnselectedText(g, x, y, p0, sel0);                    x = drawSelectedText(g, x, y, sel0, p1);                }            } else if (sel1 >= p0 && sel1 <= p1) {                x = drawSelectedText(g, x, y, p0, sel1);                x = drawUnselectedText(g, x, y, sel1, p1);            } else {                x = drawUnselectedText(g, x, y, p0, p1);            }        }         return x;    }     /**     * Renders the given range in the model as normal unselected     * text.     *     * @param g the graphics context     * @param x the starting X coordinate >= 0     * @param y the starting Y coordinate >= 0     * @param p0 the beginning position in the model >= 0     * @param p1 the ending position in the model >= p0     * @return the X location of the end of the range >= 0     * @exception BadLocationException if the range is invalid     */    protected int drawUnselectedText(Graphics g, int x, int y,                                     int p0, int p1) throws BadLocationException {        g.setColor(unselected);        Document doc = getDocument();        Segment segment = SegmentCache.getSharedSegment();        doc.getText(p0, p1 - p0, segment);        int ret = Utilities.drawTabbedText(this, segment, x, y, g, this, p0);        SegmentCache.releaseSharedSegment(segment);        return ret;    }     /**     * Renders the given range in the model as selected text.  This     * is implemented to render the text in the color specified in     * the hosting component.  It assumes the highlighter will render     * the selected background.     *     * @param g the graphics context     * @param x the starting X coordinate >= 0     * @param y the starting Y coordinate >= 0     * @param p0 the beginning position in the model >= 0     * @param p1 the ending position in the model >= p0     * @return the location of the end of the range.     * @exception BadLocationException if the range is invalid     */    protected int drawSelectedText(Graphics g, int x,                                   int y, int p0, int p1) throws BadLocationException {        g.setColor(selected);        Document doc = getDocument();        Segment segment = SegmentCache.getSharedSegment();        doc.getText(p0, p1 - p0, segment);        int ret = Utilities.drawTabbedText(this, segment, x, y, g, this, p0);        SegmentCache.releaseSharedSegment(segment);        return ret;    }     /**     * Gives access to a buffer that can be used to fetch     * text from the associated document.     *     * @return the buffer     */    protected final Segment getLineBuffer() {        if (lineBuffer == null) {            lineBuffer = new Segment();        }        return lineBuffer;    }     /**     * This is called by the nested wrapped line     * views to determine the break location.  This can     * be reimplemented to alter the breaking behavior.     * It will either break at word or character boundaries     * depending upon the break argument given at     * construction.     */    protected int calculateBreakPosition(int p0, int p1) {        int p;        Segment segment = SegmentCache.getSharedSegment();        loadText(segment, p0, p1);        int currentWidth = getWidth();        if (wordWrap) {            p = p0 + Utilities.getBreakLocation(segment, metrics,                                                tabBase, tabBase + currentWidth,                                                this, p0);        } else {            p = p0 + Utilities.getTabbedTextOffset(segment, metrics,                                                   tabBase, tabBase + currentWidth,                                                   this, p0, false);        }        SegmentCache.releaseSharedSegment(segment);        return p;    }     /**     * Loads all of the children to initialize the view.     * This is called by the <code>setParent</code> method.     * Subclasses can reimplement this to initialize their     * child views in a different manner.  The default     * implementation creates a child view for each     * child element.     *     * @param f the view factory     */    protected void loadChildren(ViewFactory f) {        Element e = getElement();        int n = e.getElementCount();        if (n > 0) {            View[] added = new View[n];            for (int i = 0; i < n; i++) {                added[i] = new WrappedLine(e.getElement(i));            }            replace(0, 0, added);        }    }     /**     * Update the child views in response to a     * document event.     */    void updateChildren(DocumentEvent e, Shape a) {        Element elem = getElement();        DocumentEvent.ElementChange ec = e.getChange(elem);        if (ec != null) {            // the structure of this element changed.            Element[] removedElems = ec.getChildrenRemoved();            Element[] addedElems = ec.getChildrenAdded();            View[] added = new View[addedElems.length];            for (int i = 0; i < addedElems.length; i++) {                added[i] = new WrappedLine(addedElems[i]);            }            replace(ec.getIndex(), removedElems.length, added);             // should damge a little more intelligently.            if (a != null) {                preferenceChanged(null, true, true);                getContainer().repaint();            }        }         // update font metrics which may be used by the child views        updateMetrics();    }     /**     * Load the text buffer with the given range     * of text.  This is used by the fragments     * broken off of this view as well as this     * view itself.     */    final void loadText(Segment segment, int p0, int p1) {        try {            Document doc = getDocument();            doc.getText(p0, p1 - p0, segment);        } catch (BadLocationException bl) {            throw new StateInvariantError("Can't get line text");        }    }     final void updateMetrics() {        Component host = getContainer();        Font f = host.getFont();        metrics = host.getFontMetrics(f);        tabSize = getTabSize() * metrics.charWidth('m');    }     // --- TabExpander methods ------------------------------------------     /**     * Returns the next tab stop position after a given reference position.     * This implementation does not support things like centering so it     * ignores the tabOffset argument.     *     * @param x the current position >= 0     * @param tabOffset the position within the text stream     *   that the tab occurred at >= 0.     * @return the tab stop, measured in points >= 0     */    public float nextTabStop(float x, int tabOffset) {        if (tabSize == 0)            return x;        int ntabs = ((int) x - tabBase) / tabSize;        return tabBase + ((ntabs + 1) * tabSize);    }      // --- View methods -------------------------------------     /**     * Renders using the given rendering surface and area     * on that surface.  This is implemented to stash the     * selection positions, selection colors, and font     * metrics for the nested lines to use.     *     * @param g the rendering surface to use     * @param a the allocated region to render into     *     * @see View#paint     */    public void paint(Graphics g, Shape a) {        Rectangle alloc = (Rectangle) a;        tabBase = alloc.x;        JTextComponent host = (JTextComponent) getContainer();        sel0 = host.getSelectionStart();        sel1 = host.getSelectionEnd();        unselected = (host.isEnabled()) ?            host.getForeground() : host.getDisabledTextColor();        Caret c = host.getCaret();        selected = c.isSelectionVisible() && host.getHighlighter() != null ?                        host.getSelectedTextColor() : unselected;        g.setFont(host.getFont());         // superclass paints the children        super.paint(g, a);    }     /**     * Sets the size of the view.  This should cause     * layout of the view along the given axis, if it     * has any layout duties.     *     * @param width the width >= 0     * @param height the height >= 0     */    public void setSize(float width, float height) {        updateMetrics();        if ((int) width != getWidth()) {            // invalidate the view itself since the childrens            // desired widths will be based upon this views width.            preferenceChanged(null, true, true);            widthChanging = true;        }        super.setSize(width, height);        widthChanging = false;    }     /**     * Determines the preferred span for this view along an     * axis.  This is implemented to provide the superclass     * behavior after first making sure that the current font     * metrics are cached (for the nested lines which use     * the metrics to determine the height of the potentially     * wrapped lines).     *     * @param axis may be either View.X_AXIS or View.Y_AXIS     * @return  the span the view would like to be rendered into.     *           Typically the view is told to render into the span     *           that is returned, although there is no guarantee.     *           The parent may choose to resize or break the view.     * @see View#getPreferredSpan     */    public float getPreferredSpan(int axis) {        updateMetrics();        return super.getPreferredSpan(axis);    }     /**     * Determines the minimum span for this view along an     * axis.  This is implemented to provide the superclass     * behavior after first making sure that the current font     * metrics are cached (for the nested lines which use     * the metrics to determine the height of the potentially     * wrapped lines).     *     * @param axis may be either View.X_AXIS or View.Y_AXIS     * @return  the span the view would like to be rendered into.     *           Typically the view is told to render into the span     *           that is returned, although there is no guarantee.     *           The parent may choose to resize or break the view.     * @see View#getMinimumSpan     */    public float getMinimumSpan(int axis) {        updateMetrics();        return super.getMinimumSpan(axis);    }     /**     * Determines the maximum span for this view along an     * axis.  This is implemented to provide the superclass     * behavior after first making sure that the current font     * metrics are cached (for the nested lines which use     * the metrics to determine the height of the potentially     * wrapped lines).     *     * @param axis may be either View.X_AXIS or View.Y_AXIS     * @return  the span the view would like to be rendered into.     *           Typically the view is told to render into the span     *           that is returned, although there is no guarantee.     *           The parent may choose to resize or break the view.     * @see View#getMaximumSpan     */    public float getMaximumSpan(int axis) {        updateMetrics();        return super.getMaximumSpan(axis);    }     /**     * Gives notification that something was inserted into the     * document in a location that this view is responsible for.     * This is implemented to simply update the children.     *     * @param e the change information from the associated document     * @param a the current allocation of the view     * @param f the factory to use to rebuild if the view has children     * @see View#insertUpdate     */    public void insertUpdate(DocumentEvent e, Shape a, ViewFactory f) {        updateChildren(e, a);         Rectangle alloc = ((a != null) && isAllocationValid()) ?            getInsideAllocation(a) : null;        int pos = e.getOffset();        View v = getViewAtPosition(pos, alloc);        if (v != null) {            v.insertUpdate(e, alloc, f);        }    }     /**     * Gives notification that something was removed from the     * document in a location that this view is responsible for.     * This is implemented to simply update the children.     *     * @param e the change information from the associated document     * @param a the current allocation of the view     * @param f the factory to use to rebuild if the view has children     * @see View#removeUpdate     */    public void removeUpdate(DocumentEvent e, Shape a, ViewFactory f) {        updateChildren(e, a);         Rectangle alloc = ((a != null) && isAllocationValid()) ?            getInsideAllocation(a) : null;        int pos = e.getOffset();        View v = getViewAtPosition(pos, alloc);        if (v != null) {            v.removeUpdate(e, alloc, f);        }    }     /**     * Gives notification from the document that attributes were changed     * in a location that this view is responsible for.     *     * @param e the change information from the associated document

Lines 1–500 of 853.