1335 lines · 49.6 KB · Java
RawDownload
/* * Copyright (c) 1999, 2010, 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.text.BreakIterator;import javax.swing.event.*;import java.util.BitSet;import java.util.Locale; import javax.swing.UIManager;import sun.swing.SwingUtilities2;import static sun.swing.SwingUtilities2.IMPLIED_CR; /** * A GlyphView is a styled chunk of text that represents a view * mapped over an element in the text model. This view is generally * responsible for displaying text glyphs using character level * attributes in some way. * An implementation of the GlyphPainter class is used to do the * actual rendering and model/view translations.  This separates * rendering from layout and management of the association with * the model. * <p> * The view supports breaking for the purpose of formatting. * The fragments produced by breaking share the view that has * primary responsibility for the element (i.e. they are nested * classes and carry only a small amount of state of their own) * so they can share its resources. * <p> * Since this view * represents text that may have tabs embedded in it, it implements the * <code>TabableView</code> interface.  Tabs will only be * expanded if this view is embedded in a container that does * tab expansion.  ParagraphView is an example of a container * that does tab expansion. * <p> * * @since 1.3 * * @author  Timothy Prinzing */public class GlyphView extends View implements TabableView, Cloneable {     /**     * Constructs a new view wrapped on an element.     *     * @param elem the element     */    public GlyphView(Element elem) {        super(elem);        offset = 0;        length = 0;        Element parent = elem.getParentElement();        AttributeSet attr = elem.getAttributes();         //         if there was an implied CR        impliedCR = (attr != null && attr.getAttribute(IMPLIED_CR) != null &&        //         if this is non-empty paragraph                   parent != null && parent.getElementCount() > 1);        skipWidth = elem.getName().equals("br");    }     /**     * Creates a shallow copy.  This is used by the     * createFragment and breakView methods.     *     * @return the copy     */    protected final Object clone() {        Object o;        try {            o = super.clone();        } catch (CloneNotSupportedException cnse) {            o = null;        }        return o;    }     /**     * Fetch the currently installed glyph painter.     * If a painter has not yet been installed, and     * a default was not yet needed, null is returned.     */    public GlyphPainter getGlyphPainter() {        return painter;    }     /**     * Sets the painter to use for rendering glyphs.     */    public void setGlyphPainter(GlyphPainter p) {        painter = p;    }     /**     * Fetch a reference to the text that occupies     * the given range.  This is normally used by     * the GlyphPainter to determine what characters     * it should render glyphs for.     *     * @param p0  the starting document offset >= 0     * @param p1  the ending document offset >= p0     * @return    the <code>Segment</code> containing the text     */     public Segment getText(int p0, int p1) {         // When done with the returned Segment it should be released by         // invoking:         //    SegmentCache.releaseSharedSegment(segment);         Segment text = SegmentCache.getSharedSegment();         try {             Document doc = getDocument();             doc.getText(p0, p1 - p0, text);         } catch (BadLocationException bl) {             throw new StateInvariantError("GlyphView: Stale view: " + bl);         }         return text;     }     /**     * Fetch the background color to use to render the     * glyphs.  If there is no background color, null should     * be returned.  This is implemented to call     * <code>StyledDocument.getBackground</code> if the associated     * document is a styled document, otherwise it returns null.     */    public Color getBackground() {        Document doc = getDocument();        if (doc instanceof StyledDocument) {            AttributeSet attr = getAttributes();            if (attr.isDefined(StyleConstants.Background)) {                return ((StyledDocument)doc).getBackground(attr);            }        }        return null;    }     /**     * Fetch the foreground color to use to render the     * glyphs.  If there is no foreground color, null should     * be returned.  This is implemented to call     * <code>StyledDocument.getBackground</code> if the associated     * document is a StyledDocument.  If the associated document     * is not a StyledDocument, the associated components foreground     * color is used.  If there is no associated component, null     * is returned.     */    public Color getForeground() {        Document doc = getDocument();        if (doc instanceof StyledDocument) {            AttributeSet attr = getAttributes();            return ((StyledDocument)doc).getForeground(attr);        }        Component c = getContainer();        if (c != null) {            return c.getForeground();        }        return null;    }     /**     * Fetch the font that the glyphs should be based     * upon.  This is implemented to call     * <code>StyledDocument.getFont</code> if the associated     * document is a StyledDocument.  If the associated document     * is not a StyledDocument, the associated components font     * is used.  If there is no associated component, null     * is returned.     */    public Font getFont() {        Document doc = getDocument();        if (doc instanceof StyledDocument) {            AttributeSet attr = getAttributes();            return ((StyledDocument)doc).getFont(attr);        }        Component c = getContainer();        if (c != null) {            return c.getFont();        }        return null;    }     /**     * Determine if the glyphs should be underlined.  If true,     * an underline should be drawn through the baseline.     */    public boolean isUnderline() {        AttributeSet attr = getAttributes();        return StyleConstants.isUnderline(attr);    }     /**     * Determine if the glyphs should have a strikethrough     * line.  If true, a line should be drawn through the center     * of the glyphs.     */    public boolean isStrikeThrough() {        AttributeSet attr = getAttributes();        return StyleConstants.isStrikeThrough(attr);    }     /**     * Determine if the glyphs should be rendered as superscript.     */    public boolean isSubscript() {        AttributeSet attr = getAttributes();        return StyleConstants.isSubscript(attr);    }     /**     * Determine if the glyphs should be rendered as subscript.     */    public boolean isSuperscript() {        AttributeSet attr = getAttributes();        return StyleConstants.isSuperscript(attr);    }     /**     * Fetch the TabExpander to use if tabs are present in this view.     */    public TabExpander getTabExpander() {        return expander;    }     /**     * Check to see that a glyph painter exists.  If a painter     * doesn't exist, a default glyph painter will be installed.     */    protected void checkPainter() {        if (painter == null) {            if (defaultPainter == null) {                // the classname should probably come from a property file.                String classname = "javax.swing.text.GlyphPainter1";                try {                    Class c;                    ClassLoader loader = getClass().getClassLoader();                    if (loader != null) {                        c = loader.loadClass(classname);                    } else {                        c = Class.forName(classname);                    }                    Object o = c.newInstance();                    if (o instanceof GlyphPainter) {                        defaultPainter = (GlyphPainter) o;                    }                } catch (Throwable e) {                    throw new StateInvariantError("GlyphView: Can't load glyph painter: "                                                  + classname);                }            }            setGlyphPainter(defaultPainter.getPainter(this, getStartOffset(),                                                      getEndOffset()));        }    }     // --- TabableView methods --------------------------------------     /**     * Determines the desired span when using the given     * tab expansion implementation.     *     * @param x the position the view would be located     *  at for the purpose of tab expansion >= 0.     * @param e how to expand the tabs when encountered.     * @return the desired span >= 0     * @see TabableView#getTabbedSpan     */    public float getTabbedSpan(float x, TabExpander e) {        checkPainter();         TabExpander old = expander;        expander = e;         if (expander != old) {            // setting expander can change horizontal span of the view,            // so we have to call preferenceChanged()            preferenceChanged(null, true, false);        }         this.x = (int) x;        int p0 = getStartOffset();        int p1 = getEndOffset();        float width = painter.getSpan(this, p0, p1, expander, x);        return width;    }     /**     * Determines the span along the same axis as tab     * expansion for a portion of the view.  This is     * intended for use by the TabExpander for cases     * where the tab expansion involves aligning the     * portion of text that doesn't have whitespace     * relative to the tab stop.  There is therefore     * an assumption that the range given does not     * contain tabs.     * <p>     * This method can be called while servicing the     * getTabbedSpan or getPreferredSize.  It has to     * arrange for its own text buffer to make the     * measurements.     *     * @param p0 the starting document offset >= 0     * @param p1 the ending document offset >= p0     * @return the span >= 0     */    public float getPartialSpan(int p0, int p1) {        checkPainter();        float width = painter.getSpan(this, p0, p1, expander, x);        return width;    }     // --- View methods ---------------------------------------------     /**     * Fetches the portion of the model that this view is responsible for.     *     * @return the starting offset into the model     * @see View#getStartOffset     */    public int getStartOffset() {        Element e = getElement();        return (length > 0) ? e.getStartOffset() + offset : e.getStartOffset();    }     /**     * Fetches the portion of the model that this view is responsible for.     *     * @return the ending offset into the model     * @see View#getEndOffset     */    public int getEndOffset() {        Element e = getElement();        return (length > 0) ? e.getStartOffset() + offset + length : e.getEndOffset();    }     /**     * Lazily initializes the selections field     */    private void initSelections(int p0, int p1) {        int viewPosCount = p1 - p0 + 1;        if (selections == null || viewPosCount > selections.length) {            selections = new byte[viewPosCount];            return;        }        for (int i = 0; i < viewPosCount; selections[i++] = 0);    }     /**     * Renders a portion of a text style run.     *     * @param g the rendering surface to use     * @param a the allocated region to render into     */    public void paint(Graphics g, Shape a) {        checkPainter();         boolean paintedText = false;        Component c = getContainer();        int p0 = getStartOffset();        int p1 = getEndOffset();        Rectangle alloc = (a instanceof Rectangle) ? (Rectangle)a : a.getBounds();        Color bg = getBackground();        Color fg = getForeground();         if (c != null && ! c.isEnabled()) {            fg = (c instanceof JTextComponent ?                ((JTextComponent)c).getDisabledTextColor() :                UIManager.getColor("textInactiveText"));        }        if (bg != null) {            g.setColor(bg);            g.fillRect(alloc.x, alloc.y, alloc.width, alloc.height);        }        if (c instanceof JTextComponent) {            JTextComponent tc = (JTextComponent) c;            Highlighter h = tc.getHighlighter();            if (h instanceof LayeredHighlighter) {                ((LayeredHighlighter)h).paintLayeredHighlights                    (g, p0, p1, a, tc, this);            }        }         if (Utilities.isComposedTextElement(getElement())) {            Utilities.paintComposedText(g, a.getBounds(), this);            paintedText = true;        } else if(c instanceof JTextComponent) {            JTextComponent tc = (JTextComponent) c;            Color selFG = tc.getSelectedTextColor();             if (// there's a highlighter (bug 4532590), and                (tc.getHighlighter() != null) &&                // selected text color is different from regular foreground                (selFG != null) && !selFG.equals(fg)) {                 Highlighter.Highlight[] h = tc.getHighlighter().getHighlights();                if(h.length != 0) {                    boolean initialized = false;                    int viewSelectionCount = 0;                    for (int i = 0; i < h.length; i++) {                        Highlighter.Highlight highlight = h[i];                        int hStart = highlight.getStartOffset();                        int hEnd = highlight.getEndOffset();                        if (hStart > p1 || hEnd < p0) {                            // the selection is out of this view                            continue;                        }                        if (!SwingUtilities2.useSelectedTextColor(highlight, tc)) {                            continue;                        }                        if (hStart <= p0 && hEnd >= p1){                            // the whole view is selected                            paintTextUsingColor(g, a, selFG, p0, p1);                            paintedText = true;                            break;                        }                        // the array is lazily created only when the view                        // is partially selected                        if (!initialized) {                            initSelections(p0, p1);                            initialized = true;                        }                        hStart = Math.max(p0, hStart);                        hEnd = Math.min(p1, hEnd);                        paintTextUsingColor(g, a, selFG, hStart, hEnd);                        // the array represents view positions [0, p1-p0+1]                        // later will iterate this array and sum its                        // elements. Positions with sum == 0 are not selected.                        selections[hStart-p0]++;                        selections[hEnd-p0]--;                         viewSelectionCount++;                    }                     if (!paintedText && viewSelectionCount > 0) {                        // the view is partially selected                        int curPos = -1;                        int startPos = 0;                        int viewLen = p1 - p0;                        while (curPos++ < viewLen) {                            // searching for the next selection start                            while(curPos < viewLen &&                                    selections[curPos] == 0) curPos++;                            if (startPos != curPos) {                                // paint unselected text                                paintTextUsingColor(g, a, fg,                                        p0 + startPos, p0 + curPos);                            }                            int checkSum = 0;                            // searching for next start position of unselected text                            while (curPos < viewLen &&                                    (checkSum += selections[curPos]) != 0) curPos++;                            startPos = curPos;                        }                        paintedText = true;                    }                }            }        }        if(!paintedText)            paintTextUsingColor(g, a, fg, p0, p1);    }     /**     * Paints the specified region of text in the specified color.     */    final void paintTextUsingColor(Graphics g, Shape a, Color c, int p0, int p1) {        // render the glyphs        g.setColor(c);        painter.paint(this, g, a, p0, p1);         // render underline or strikethrough if set.        boolean underline = isUnderline();        boolean strike = isStrikeThrough();        if (underline || strike) {            // calculate x coordinates            Rectangle alloc = (a instanceof Rectangle) ? (Rectangle)a : a.getBounds();            View parent = getParent();            if ((parent != null) && (parent.getEndOffset() == p1)) {

Lines 1–500 of 1335.