0001 /*
0002 * Copyright 1999-2006 Sun Microsystems, Inc. All Rights Reserved.
0003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0004 *
0005 * This code is free software; you can redistribute it and/or modify it
0006 * under the terms of the GNU General Public License version 2 only, as
0007 * published by the Free Software Foundation. Sun designates this
0008 * particular file as subject to the "Classpath" exception as provided
0009 * by Sun in the LICENSE file that accompanied this code.
0010 *
0011 * This code is distributed in the hope that it will be useful, but WITHOUT
0012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0014 * version 2 for more details (a copy is included in the LICENSE file that
0015 * accompanied this code).
0016 *
0017 * You should have received a copy of the GNU General Public License version
0018 * 2 along with this work; if not, write to the Free Software Foundation,
0019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0020 *
0021 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
0022 * CA 95054 USA or visit www.sun.com if you need additional information or
0023 * have any questions.
0024 */
0025 package javax.swing.text;
0026
0027 import java.awt.*;
0028 import java.text.BreakIterator;
0029 import javax.swing.event.*;
0030 import java.util.BitSet;
0031
0032 import sun.swing.SwingUtilities2;
0033
0034 /**
0035 * A GlyphView is a styled chunk of text that represents a view
0036 * mapped over an element in the text model. This view is generally
0037 * responsible for displaying text glyphs using character level
0038 * attributes in some way.
0039 * An implementation of the GlyphPainter class is used to do the
0040 * actual rendering and model/view translations. This separates
0041 * rendering from layout and management of the association with
0042 * the model.
0043 * <p>
0044 * The view supports breaking for the purpose of formatting.
0045 * The fragments produced by breaking share the view that has
0046 * primary responsibility for the element (i.e. they are nested
0047 * classes and carry only a small amount of state of their own)
0048 * so they can share its resources.
0049 * <p>
0050 * Since this view
0051 * represents text that may have tabs embedded in it, it implements the
0052 * <code>TabableView</code> interface. Tabs will only be
0053 * expanded if this view is embedded in a container that does
0054 * tab expansion. ParagraphView is an example of a container
0055 * that does tab expansion.
0056 * <p>
0057 *
0058 * @since 1.3
0059 *
0060 * @author Timothy Prinzing
0061 * @version 1.49 05/05/07
0062 */
0063 public class GlyphView extends View implements TabableView, Cloneable {
0064
0065 /**
0066 * Constructs a new view wrapped on an element.
0067 *
0068 * @param elem the element
0069 */
0070 public GlyphView(Element elem) {
0071 super (elem);
0072 offset = 0;
0073 length = 0;
0074 Element parent = elem.getParentElement();
0075 AttributeSet attr = elem.getAttributes();
0076
0077 // if there was an implied CR
0078 impliedCR = (attr != null
0079 && attr.getAttribute(IMPLIED_CR) != null &&
0080 // if this is non-empty paragraph
0081 parent != null && parent.getElementCount() > 1);
0082 skipWidth = elem.getName().equals("br");
0083 }
0084
0085 /**
0086 * Creates a shallow copy. This is used by the
0087 * createFragment and breakView methods.
0088 *
0089 * @return the copy
0090 */
0091 protected final Object clone() {
0092 Object o;
0093 try {
0094 o = super .clone();
0095 } catch (CloneNotSupportedException cnse) {
0096 o = null;
0097 }
0098 return o;
0099 }
0100
0101 /**
0102 * Fetch the currently installed glyph painter.
0103 * If a painter has not yet been installed, and
0104 * a default was not yet needed, null is returned.
0105 */
0106 public GlyphPainter getGlyphPainter() {
0107 return painter;
0108 }
0109
0110 /**
0111 * Sets the painter to use for rendering glyphs.
0112 */
0113 public void setGlyphPainter(GlyphPainter p) {
0114 painter = p;
0115 }
0116
0117 /**
0118 * Fetch a reference to the text that occupies
0119 * the given range. This is normally used by
0120 * the GlyphPainter to determine what characters
0121 * it should render glyphs for.
0122 *
0123 * @param p0 the starting document offset >= 0
0124 * @param p1 the ending document offset >= p0
0125 * @return the <code>Segment</code> containing the text
0126 */
0127 public Segment getText(int p0, int p1) {
0128 // When done with the returned Segment it should be released by
0129 // invoking:
0130 // SegmentCache.releaseSharedSegment(segment);
0131 Segment text = SegmentCache.getSharedSegment();
0132 try {
0133 Document doc = getDocument();
0134 doc.getText(p0, p1 - p0, text);
0135 } catch (BadLocationException bl) {
0136 throw new StateInvariantError("GlyphView: Stale view: "
0137 + bl);
0138 }
0139 return text;
0140 }
0141
0142 /**
0143 * Fetch the background color to use to render the
0144 * glyphs. If there is no background color, null should
0145 * be returned. This is implemented to call
0146 * <code>StyledDocument.getBackground</code> if the associated
0147 * document is a styled document, otherwise it returns null.
0148 */
0149 public Color getBackground() {
0150 Document doc = getDocument();
0151 if (doc instanceof StyledDocument) {
0152 AttributeSet attr = getAttributes();
0153 if (attr.isDefined(StyleConstants.Background)) {
0154 return ((StyledDocument) doc).getBackground(attr);
0155 }
0156 }
0157 return null;
0158 }
0159
0160 /**
0161 * Fetch the foreground color to use to render the
0162 * glyphs. If there is no foreground color, null should
0163 * be returned. This is implemented to call
0164 * <code>StyledDocument.getBackground</code> if the associated
0165 * document is a StyledDocument. If the associated document
0166 * is not a StyledDocument, the associated components foreground
0167 * color is used. If there is no associated component, null
0168 * is returned.
0169 */
0170 public Color getForeground() {
0171 Document doc = getDocument();
0172 if (doc instanceof StyledDocument) {
0173 AttributeSet attr = getAttributes();
0174 return ((StyledDocument) doc).getForeground(attr);
0175 }
0176 Component c = getContainer();
0177 if (c != null) {
0178 return c.getForeground();
0179 }
0180 return null;
0181 }
0182
0183 /**
0184 * Fetch the font that the glyphs should be based
0185 * upon. This is implemented to call
0186 * <code>StyledDocument.getFont</code> if the associated
0187 * document is a StyledDocument. If the associated document
0188 * is not a StyledDocument, the associated components font
0189 * is used. If there is no associated component, null
0190 * is returned.
0191 */
0192 public Font getFont() {
0193 Document doc = getDocument();
0194 if (doc instanceof StyledDocument) {
0195 AttributeSet attr = getAttributes();
0196 return ((StyledDocument) doc).getFont(attr);
0197 }
0198 Component c = getContainer();
0199 if (c != null) {
0200 return c.getFont();
0201 }
0202 return null;
0203 }
0204
0205 /**
0206 * Determine if the glyphs should be underlined. If true,
0207 * an underline should be drawn through the baseline.
0208 */
0209 public boolean isUnderline() {
0210 AttributeSet attr = getAttributes();
0211 return StyleConstants.isUnderline(attr);
0212 }
0213
0214 /**
0215 * Determine if the glyphs should have a strikethrough
0216 * line. If true, a line should be drawn through the center
0217 * of the glyphs.
0218 */
0219 public boolean isStrikeThrough() {
0220 AttributeSet attr = getAttributes();
0221 return StyleConstants.isStrikeThrough(attr);
0222 }
0223
0224 /**
0225 * Determine if the glyphs should be rendered as superscript.
0226 */
0227 public boolean isSubscript() {
0228 AttributeSet attr = getAttributes();
0229 return StyleConstants.isSubscript(attr);
0230 }
0231
0232 /**
0233 * Determine if the glyphs should be rendered as subscript.
0234 */
0235 public boolean isSuperscript() {
0236 AttributeSet attr = getAttributes();
0237 return StyleConstants.isSuperscript(attr);
0238 }
0239
0240 /**
0241 * Fetch the TabExpander to use if tabs are present in this view.
0242 */
0243 public TabExpander getTabExpander() {
0244 return expander;
0245 }
0246
0247 /**
0248 * Check to see that a glyph painter exists. If a painter
0249 * doesn't exist, a default glyph painter will be installed.
0250 */
0251 protected void checkPainter() {
0252 if (painter == null) {
0253 if (defaultPainter == null) {
0254 // the classname should probably come from a property file.
0255 String classname = "javax.swing.text.GlyphPainter1";
0256 try {
0257 Class c;
0258 ClassLoader loader = getClass().getClassLoader();
0259 if (loader != null) {
0260 c = loader.loadClass(classname);
0261 } else {
0262 c = Class.forName(classname);
0263 }
0264 Object o = c.newInstance();
0265 if (o instanceof GlyphPainter) {
0266 defaultPainter = (GlyphPainter) o;
0267 }
0268 } catch (Throwable e) {
0269 throw new StateInvariantError(
0270 "GlyphView: Can't load glyph painter: "
0271 + classname);
0272 }
0273 }
0274 setGlyphPainter(defaultPainter.getPainter(this ,
0275 getStartOffset(), getEndOffset()));
0276 }
0277 }
0278
0279 // --- TabableView methods --------------------------------------
0280
0281 /**
0282 * Determines the desired span when using the given
0283 * tab expansion implementation.
0284 *
0285 * @param x the position the view would be located
0286 * at for the purpose of tab expansion >= 0.
0287 * @param e how to expand the tabs when encountered.
0288 * @return the desired span >= 0
0289 * @see TabableView#getTabbedSpan
0290 */
0291 public float getTabbedSpan(float x, TabExpander e) {
0292 checkPainter();
0293
0294 TabExpander old = expander;
0295 expander = e;
0296
0297 if (expander != old) {
0298 // setting expander can change horizontal span of the view,
0299 // so we have to call preferenceChanged()
0300 preferenceChanged(null, true, false);
0301 }
0302
0303 this .x = (int) x;
0304 int p0 = getStartOffset();
0305 int p1 = getEndOffset();
0306 float width = painter.getSpan(this , p0, p1, expander, x);
0307 return width;
0308 }
0309
0310 /**
0311 * Determines the span along the same axis as tab
0312 * expansion for a portion of the view. This is
0313 * intended for use by the TabExpander for cases
0314 * where the tab expansion involves aligning the
0315 * portion of text that doesn't have whitespace
0316 * relative to the tab stop. There is therefore
0317 * an assumption that the range given does not
0318 * contain tabs.
0319 * <p>
0320 * This method can be called while servicing the
0321 * getTabbedSpan or getPreferredSize. It has to
0322 * arrange for its own text buffer to make the
0323 * measurements.
0324 *
0325 * @param p0 the starting document offset >= 0
0326 * @param p1 the ending document offset >= p0
0327 * @return the span >= 0
0328 */
0329 public float getPartialSpan(int p0, int p1) {
0330 checkPainter();
0331 float width = painter.getSpan(this , p0, p1, expander, x);
0332 return width;
0333 }
0334
0335 // --- View methods ---------------------------------------------
0336
0337 /**
0338 * Fetches the portion of the model that this view is responsible for.
0339 *
0340 * @return the starting offset into the model
0341 * @see View#getStartOffset
0342 */
0343 public int getStartOffset() {
0344 Element e = getElement();
0345 return (length > 0) ? e.getStartOffset() + offset : e
0346 .getStartOffset();
0347 }
0348
0349 /**
0350 * Fetches the portion of the model that this view is responsible for.
0351 *
0352 * @return the ending offset into the model
0353 * @see View#getEndOffset
0354 */
0355 public int getEndOffset() {
0356 Element e = getElement();
0357 return (length > 0) ? e.getStartOffset() + offset + length : e
0358 .getEndOffset();
0359 }
0360
0361 /**
0362 * Lazily initializes the selections field
0363 */
0364 private void initSelections(int p0, int p1) {
0365 int viewPosCount = p1 - p0 + 1;
0366 if (selections == null || viewPosCount > selections.length) {
0367 selections = new byte[viewPosCount];
0368 return;
0369 }
0370 for (int i = 0; i < viewPosCount; selections[i++] = 0)
0371 ;
0372 }
0373
0374 /**
0375 * Renders a portion of a text style run.
0376 *
0377 * @param g the rendering surface to use
0378 * @param a the allocated region to render into
0379 */
0380 public void paint(Graphics g, Shape a) {
0381 checkPainter();
0382
0383 boolean paintedText = false;
0384 Component c = getContainer();
0385 int p0 = getStartOffset();
0386 int p1 = getEndOffset();
0387 Rectangle alloc = (a instanceof Rectangle) ? (Rectangle) a : a
0388 .getBounds();
0389 Color bg = getBackground();
0390 Color fg = getForeground();
0391
0392 if (c instanceof JTextComponent) {
0393 JTextComponent tc = (JTextComponent) c;
0394 if (!tc.isEnabled()) {
0395 fg = tc.getDisabledTextColor();
0396 }
0397 }
0398 if (bg != null) {
0399 g.setColor(bg);
0400 g.fillRect(alloc.x, alloc.y, alloc.width, alloc.height);
0401 }
0402 if (c instanceof JTextComponent) {
0403 JTextComponent tc = (JTextComponent) c;
0404 Highlighter h = tc.getHighlighter();
0405 if (h instanceof LayeredHighlighter) {
0406 ((LayeredHighlighter) h).paintLayeredHighlights(g, p0,
0407 p1, a, tc, this );
0408 }
0409 }
0410
0411 if (Utilities.isComposedTextElement(getElement())) {
0412 Utilities.paintComposedText(g, a.getBounds(), this );
0413 paintedText = true;
0414 } else if (c instanceof JTextComponent) {
0415 JTextComponent tc = (JTextComponent) c;
0416 Color selFG = tc.getSelectedTextColor();
0417
0418 if (// there's a highlighter (bug 4532590), and
0419 (tc.getHighlighter() != null) &&
0420 // selected text color is different from regular foreground
0421 (selFG != null) && !selFG.equals(fg)) {
0422
0423 Highlighter.Highlight[] h = tc.getHighlighter()
0424 .getHighlights();
0425 if (h.length != 0) {
0426 boolean initialized = false;
0427 int viewSelectionCount = 0;
0428 for (int i = 0; i < h.length; i++) {
0429 Highlighter.Highlight highlight = h[i];
0430 int hStart = highlight.getStartOffset();
0431 int hEnd = highlight.getEndOffset();
0432 if (hStart > p1 || hEnd < p0) {
0433 // the selection is out of this view
0434 continue;
0435 }
0436 if (!SwingUtilities2.useSelectedTextColor(
0437 highlight, tc)) {
0438 continue;
0439 }
0440 if (hStart <= p0 && hEnd >= p1) {
0441 // the whole view is selected
0442 paintTextUsingColor(g, a, selFG, p0, p1);
0443 paintedText = true;
0444 break;
0445 }
0446 // the array is lazily created only when the view
0447 // is partially selected
0448 if (!initialized) {
0449 initSelections(p0, p1);
0450 initialized = true;
0451 }
0452 hStart = Math.max(p0, hStart);
0453 hEnd = Math.min(p1, hEnd);
0454 paintTextUsingColor(g, a, selFG, hStart, hEnd);
0455 // the array represents view positions [0, p1-p0+1]
0456 // later will iterate this array and sum its
0457 // elements. Positions with sum == 0 are not selected.
0458 selections[hStart - p0]++;
0459 selections[hEnd - p0]--;
0460
0461 viewSelectionCount++;
0462 }
0463
0464 if (!paintedText && viewSelectionCount > 0) {
0465 // the view is partially selected
0466 int curPos = -1;
0467 int startPos = 0;
0468 int viewLen = p1 - p0;
0469 while (curPos++ < viewLen) {
0470 // searching for the next selection start
0471 while (curPos < viewLen
0472 && selections[curPos] == 0)
0473 curPos++;
0474 if (startPos != curPos) {
0475 // paint unselected text
0476 paintTextUsingColor(g, a, fg, p0
0477 + startPos, p0 + curPos);
0478 }
0479 int checkSum = 0;
0480 // searching for next start position of unselected text
0481 while (curPos < viewLen
0482 && (checkSum += selections[curPos]) != 0)
0483 curPos++;
0484 startPos = curPos;
0485 }
0486 paintedText = true;
0487 }
0488 }
0489 }
0490 }
0491 if (!paintedText)
0492 paintTextUsingColor(g, a, fg, p0, p1);
0493 }
0494
0495 /**
0496 * Paints the specified region of text in the specified color.
0497 */
0498 final void paintTextUsingColor(Graphics g, Shape a, Color c,
0499 int p0, int p1) {
0500 // render the glyphs
0501 g.setColor(c);
0502 painter.paint(this , g, a, p0, p1);
0503
0504 // render underline or strikethrough if set.
0505 boolean underline = isUnderline();
0506 boolean strike = isStrikeThrough();
0507 if (underline || strike) {
0508 // calculate x coordinates
0509 Rectangle alloc = (a instanceof Rectangle) ? (Rectangle) a
0510 : a.getBounds();
0511 View parent = getParent();
0512 if ((parent != null) && (parent.getEndOffset() == p1)) {
0513 // strip whitespace on end
0514 Segment s = getText(p0, p1);
0515 while (Character.isWhitespace(s.last())) {
0516 p1 -= 1;
0517 s.count -= 1;
0518 }
0519 SegmentCache.releaseSharedSegment(s);
0520 }
0521 int x0 = alloc.x;
0522 int p = getStartOffset();
0523 if (p != p0) {
0524 x0 += (int) painter.getSpan(this , p, p0,
0525 getTabExpander(), x0);
0526 }
0527 int x1 = x0
0528 + (int) painter.getSpan(this , p0, p1,
0529 getTabExpander(), x0);
0530
0531 // calculate y coordinate
0532 int y = alloc.y + alloc.height
0533 - (int) painter.getDescent(this );
0534 if (underline) {
0535 int yTmp = y + 1;
0536 g.drawLine(x0, yTmp, x1, yTmp);
0537 }
0538 if (strike) {
0539 // move y coordinate above baseline
0540 int yTmp = y - (int) (painter.getAscent(this ) * 0.3f);
0541 g.drawLine(x0, yTmp, x1, yTmp);
0542 }
0543
0544 }
0545 }
0546
0547 /**
0548 * Determines the preferred span for this view along an
0549 * axis.
0550 *
0551 * @param axis may be either View.X_AXIS or View.Y_AXIS
0552 * @return the span the view would like to be rendered into >= 0.
0553 * Typically the view is told to render into the span
0554 * that is returned, although there is no guarantee.
0555 * The parent may choose to resize or break the view.
0556 */
0557 public float getPreferredSpan(int axis) {
0558 if (impliedCR) {
0559 return 0;
0560 }
0561 checkPainter();
0562 int p0 = getStartOffset();
0563 int p1 = getEndOffset();
0564 switch (axis) {
0565 case View.X_AXIS:
0566 if (skipWidth) {
0567 return 0;
0568 }
0569 return painter.getSpan(this , p0, p1, expander, this .x);
0570 case View.Y_AXIS:
0571 float h = painter.getHeight(this );
0572 if (isSuperscript()) {
0573 h += h / 3;
0574 }
0575 return h;
0576 default:
0577 throw new IllegalArgumentException("Invalid axis: " + axis);
0578 }
0579 }
0580
0581 /**
0582 * Determines the desired alignment for this view along an
0583 * axis. For the label, the alignment is along the font
0584 * baseline for the y axis, and the superclasses alignment
0585 * along the x axis.
0586 *
0587 * @param axis may be either View.X_AXIS or View.Y_AXIS
0588 * @return the desired alignment. This should be a value
0589 * between 0.0 and 1.0 inclusive, where 0 indicates alignment at the
0590 * origin and 1.0 indicates alignment to the full span
0591 * away from the origin. An alignment of 0.5 would be the
0592 * center of the view.
0593 */
0594 public float getAlignment(int axis) {
0595 checkPainter();
0596 if (axis == View.Y_AXIS) {
0597 boolean sup = isSuperscript();
0598 boolean sub = isSubscript();
0599 float h = painter.getHeight(this );
0600 float d = painter.getDescent(this );
0601 float a = painter.getAscent(this );
0602 float align;
0603 if (sup) {
0604 align = 1.0f;
0605 } else if (sub) {
0606 align = (h > 0) ? (h - (d + (a / 2))) / h : 0;
0607 } else {
0608 align = (h > 0) ? (h - d) / h : 0;
0609 }
0610 return align;
0611 }
0612 return super .getAlignment(axis);
0613 }
0614
0615 /**
0616 * Provides a mapping from the document model coordinate space
0617 * to the coordinate space of the view mapped to it.
0618 *
0619 * @param pos the position to convert >= 0
0620 * @param a the allocated region to render into
0621 * @param b either <code>Position.Bias.Forward</code>
0622 * or <code>Position.Bias.Backward</code>
0623 * @return the bounding box of the given position
0624 * @exception BadLocationException if the given position does not represent a
0625 * valid location in the associated document
0626 * @see View#modelToView
0627 */
0628 public Shape modelToView(int pos, Shape a, Position.Bias b)
0629 throws BadLocationException {
0630 checkPainter();
0631 return painter.modelToView(this , pos, b, a);
0632 }
0633
0634 /**
0635 * Provides a mapping from the view coordinate space to the logical
0636 * coordinate space of the model.
0637 *
0638 * @param x the X coordinate >= 0
0639 * @param y the Y coordinate >= 0
0640 * @param a the allocated region to render into
0641 * @param biasReturn either <code>Position.Bias.Forward</code>
0642 * or <code>Position.Bias.Backward</code> is returned as the
0643 * zero-th element of this array
0644 * @return the location within the model that best represents the
0645 * given point of view >= 0
0646 * @see View#viewToModel
0647 */
0648 public int viewToModel(float x, float y, Shape a,
0649 Position.Bias[] biasReturn) {
0650 checkPainter();
0651 return painter.viewToModel(this , x, y, a, biasReturn);
0652 }
0653
0654 /**
0655 * Determines how attractive a break opportunity in
0656 * this view is. This can be used for determining which
0657 * view is the most attractive to call <code>breakView</code>
0658 * on in the process of formatting. The
0659 * higher the weight, the more attractive the break. A
0660 * value equal to or lower than <code>View.BadBreakWeight</code>
0661 * should not be considered for a break. A value greater
0662 * than or equal to <code>View.ForcedBreakWeight</code> should
0663 * be broken.
0664 * <p>
0665 * This is implemented to forward to the superclass for
0666 * the Y_AXIS. Along the X_AXIS the following values
0667 * may be returned.
0668 * <dl>
0669 * <dt><b>View.ExcellentBreakWeight</b>
0670 * <dd>if there is whitespace proceeding the desired break
0671 * location.
0672 * <dt><b>View.BadBreakWeight</b>
0673 * <dd>if the desired break location results in a break
0674 * location of the starting offset.
0675 * <dt><b>View.GoodBreakWeight</b>
0676 * <dd>if the other conditions don't occur.
0677 * </dl>
0678 * This will normally result in the behavior of breaking
0679 * on a whitespace location if one can be found, otherwise
0680 * breaking between characters.
0681 *
0682 * @param axis may be either View.X_AXIS or View.Y_AXIS
0683 * @param pos the potential location of the start of the
0684 * broken view >= 0. This may be useful for calculating tab
0685 * positions.
0686 * @param len specifies the relative length from <em>pos</em>
0687 * where a potential break is desired >= 0.
0688 * @return the weight, which should be a value between
0689 * View.ForcedBreakWeight and View.BadBreakWeight.
0690 * @see LabelView
0691 * @see ParagraphView
0692 * @see View#BadBreakWeight
0693 * @see View#GoodBreakWeight
0694 * @see View#ExcellentBreakWeight
0695 * @see View#ForcedBreakWeight
0696 */
0697 public int getBreakWeight(int axis, float pos, float len) {
0698 if (axis == View.X_AXIS) {
0699 checkPainter();
0700 int p0 = getStartOffset();
0701 int p1 = painter.getBoundedPosition(this , p0, pos, len);
0702 if (p1 == p0) {
0703 // can't even fit a single character
0704 return View.BadBreakWeight;
0705 }
0706 if (getBreakSpot(p0, p1) != -1) {
0707 return View.ExcellentBreakWeight;
0708 }
0709 // Nothing good to break on.
0710 // breaking on the View boundary is better than splitting it
0711 if (p1 == getEndOffset()) {
0712 return View.GoodBreakWeight;
0713 } else {
0714 return View.GoodBreakWeight - 1;
0715 }
0716 }
0717 return super .getBreakWeight(axis, pos, len);
0718 }
0719
0720 /**
0721 * Breaks this view on the given axis at the given length.
0722 * This is implemented to attempt to break on a whitespace
0723 * location, and returns a fragment with the whitespace at
0724 * the end. If a whitespace location can't be found, the
0725 * nearest character is used.
0726 *
0727 * @param axis may be either View.X_AXIS or View.Y_AXIS
0728 * @param p0 the location in the model where the
0729 * fragment should start it's representation >= 0.
0730 * @param pos the position along the axis that the
0731 * broken view would occupy >= 0. This may be useful for
0732 * things like tab calculations.
0733 * @param len specifies the distance along the axis
0734 * where a potential break is desired >= 0.
0735 * @return the fragment of the view that represents the
0736 * given span, if the view can be broken. If the view
0737 * doesn't support breaking behavior, the view itself is
0738 * returned.
0739 * @see View#breakView
0740 */
0741 public View breakView(int axis, int p0, float pos, float len) {
0742 if (axis == View.X_AXIS) {
0743 checkPainter();
0744 int p1 = painter.getBoundedPosition(this , p0, pos, len);
0745 int breakSpot = getBreakSpot(p0, p1);
0746
0747 if (breakSpot != -1) {
0748 p1 = breakSpot;
0749 }
0750 // else, no break in the region, return a fragment of the
0751 // bounded region.
0752 if (p0 == getStartOffset() && p1 == getEndOffset()) {
0753 return this ;
0754 }
0755 GlyphView v = (GlyphView) createFragment(p0, p1);
0756 v.x = (int) pos;
0757 return v;
0758 }
0759 return this ;
0760 }
0761
0762 /**
0763 * Returns a location to break at in the passed in region, or -1 if
0764 * there isn't a good location to break at in the specified region.
0765 */
0766 private int getBreakSpot(int p0, int p1) {
0767 Document doc = getDocument();
0768
0769 if (doc != null
0770 && Boolean.TRUE
0771 .equals(doc
0772 .getProperty(AbstractDocument.MultiByteProperty))) {
0773 return getBreakSpotUseBreakIterator(p0, p1);
0774 }
0775 return getBreakSpotUseWhitespace(p0, p1);
0776 }
0777
0778 /**
0779 * Returns the appropriate place to break based on the last whitespace
0780 * character encountered.
0781 */
0782 private int getBreakSpotUseWhitespace(int p0, int p1) {
0783 Segment s = getText(p0, p1);
0784
0785 for (char ch = s.last(); ch != Segment.DONE; ch = s.previous()) {
0786 if (Character.isWhitespace(ch)) {
0787 // found whitespace
0788 SegmentCache.releaseSharedSegment(s);
0789 return s.getIndex() - s.getBeginIndex() + 1 + p0;
0790 }
0791 }
0792 SegmentCache.releaseSharedSegment(s);
0793 return -1;
0794 }
0795
0796 /**
0797 * Returns the appropriate place to break based on BreakIterator.
0798 */
0799 private int getBreakSpotUseBreakIterator(int p0, int p1) {
0800 // Certain regions require context for BreakIterator, start from
0801 // our parents start offset.
0802 Element parent = getElement().getParentElement();
0803 int parent0;
0804 int parent1;
0805 Container c = getContainer();
0806 BreakIterator breaker;
0807
0808 if (parent == null) {
0809 parent0 = p0;
0810 parent1 = p1;
0811 } else {
0812 parent0 = parent.getStartOffset();
0813 parent1 = parent.getEndOffset();
0814 }
0815 if (c != null) {
0816 breaker = BreakIterator.getLineInstance(c.getLocale());
0817 } else {
0818 breaker = BreakIterator.getLineInstance();
0819 }
0820
0821 Segment s = getText(parent0, parent1);
0822 int breakPoint;
0823
0824 // Needed to initialize the Segment.
0825 s.first();
0826 breaker.setText(s);
0827
0828 if (p1 == parent1) {
0829 // This will most likely return the end, the assumption is
0830 // that if parent1 == p1, then we are the last portion of
0831 // a paragraph
0832 breakPoint = breaker.last();
0833 } else if (p1 + 1 == parent1) {
0834 // assert(s.count > 1)
0835 breakPoint = breaker.following(s.offset + s.count - 2);
0836 if (breakPoint >= s.count + s.offset) {
0837 breakPoint = breaker.preceding(s.offset + s.count - 1);
0838 }
0839 } else {
0840 breakPoint = breaker.preceding(p1 - parent0 + s.offset + 1);
0841 }
0842
0843 int retValue = -1;
0844
0845 if (breakPoint != BreakIterator.DONE) {
0846 breakPoint = breakPoint - s.offset + parent0;
0847 if (breakPoint > p0) {
0848 if (p0 == parent0 && breakPoint == p0) {
0849 retValue = -1;
0850 } else if (breakPoint <= p1) {
0851 retValue = breakPoint;
0852 }
0853 }
0854 }
0855 SegmentCache.releaseSharedSegment(s);
0856 return retValue;
0857 }
0858
0859 /**
0860 * Creates a view that represents a portion of the element.
0861 * This is potentially useful during formatting operations
0862 * for taking measurements of fragments of the view. If
0863 * the view doesn't support fragmenting (the default), it
0864 * should return itself.
0865 * <p>
0866 * This view does support fragmenting. It is implemented
0867 * to return a nested class that shares state in this view
0868 * representing only a portion of the view.
0869 *
0870 * @param p0 the starting offset >= 0. This should be a value
0871 * greater or equal to the element starting offset and
0872 * less than the element ending offset.
0873 * @param p1 the ending offset > p0. This should be a value
0874 * less than or equal to the elements end offset and
0875 * greater than the elements starting offset.
0876 * @return the view fragment, or itself if the view doesn't
0877 * support breaking into fragments
0878 * @see LabelView
0879 */
0880 public View createFragment(int p0, int p1) {
0881 checkPainter();
0882 Element elem = getElement();
0883 GlyphView v = (GlyphView) clone();
0884 v.offset = p0 - elem.getStartOffset();
0885 v.length = p1 - p0;
0886 v.painter = painter.getPainter(v, p0, p1);
0887 v.justificationInfo = null;
0888 return v;
0889 }
0890
0891 /**
0892 * Provides a way to determine the next visually represented model
0893 * location that one might place a caret. Some views may not be
0894 * visible, they might not be in the same order found in the model, or
0895 * they just might not allow access to some of the locations in the
0896 * model.
0897 *
0898 * @param pos the position to convert >= 0
0899 * @param a the allocated region to render into
0900 * @param direction the direction from the current position that can
0901 * be thought of as the arrow keys typically found on a keyboard.
0902 * This may be SwingConstants.WEST, SwingConstants.EAST,
0903 * SwingConstants.NORTH, or SwingConstants.SOUTH.
0904 * @return the location within the model that best represents the next
0905 * location visual position.
0906 * @exception BadLocationException
0907 * @exception IllegalArgumentException for an invalid direction
0908 */
0909 public int getNextVisualPositionFrom(int pos, Position.Bias b,
0910 Shape a, int direction, Position.Bias[] biasRet)
0911 throws BadLocationException {
0912
0913 return painter.getNextVisualPositionFrom(this , pos, b, a,
0914 direction, biasRet);
0915 }
0916
0917 /**
0918 * Gives notification that something was inserted into
0919 * the document in a location that this view is responsible for.
0920 * This is implemented to call preferenceChanged along the
0921 * axis the glyphs are rendered.
0922 *
0923 * @param e the change information from the associated document
0924 * @param a the current allocation of the view
0925 * @param f the factory to use to rebuild if the view has children
0926 * @see View#insertUpdate
0927 */
0928 public void insertUpdate(DocumentEvent e, Shape a, ViewFactory f) {
0929 justificationInfo = null;
0930 syncCR();
0931 preferenceChanged(null, true, false);
0932 }
0933
0934 /**
0935 * Gives notification that something was removed from the document
0936 * in a location that this view is responsible for.
0937 * This is implemented to call preferenceChanged along the
0938 * axis the glyphs are rendered.
0939 *
0940 * @param e the change information from the associated document
0941 * @param a the current allocation of the view
0942 * @param f the factory to use to rebuild if the view has children
0943 * @see View#removeUpdate
0944 */
0945 public void removeUpdate(DocumentEvent e, Shape a, ViewFactory f) {
0946 justificationInfo = null;
0947 syncCR();
0948 preferenceChanged(null, true, false);
0949 }
0950
0951 /**
0952 * Gives notification from the document that attributes were changed
0953 * in a location that this view is responsible for.
0954 * This is implemented to call preferenceChanged along both the
0955 * horizontal and vertical axis.
0956 *
0957 * @param e the change information from the associated document
0958 * @param a the current allocation of the view
0959 * @param f the factory to use to rebuild if the view has children
0960 * @see View#changedUpdate
0961 */
0962 public void changedUpdate(DocumentEvent e, Shape a, ViewFactory f) {
0963 syncCR();
0964 preferenceChanged(null, true, true);
0965 }
0966
0967 // checks if the paragraph is empty and updates impliedCR flag
0968 // accordingly
0969 private void syncCR() {
0970 if (impliedCR) {
0971 Element parent = getElement().getParentElement();
0972 impliedCR = (parent != null && parent.getElementCount() > 1);
0973 }
0974 }
0975
0976 /**
0977 * Class to hold data needed to justify this GlyphView in a PargraphView.Row
0978 */
0979 static class JustificationInfo {
0980 //justifiable content start
0981 final int start;
0982 //justifiable content end
0983 final int end;
0984 final int leadingSpaces;
0985 final int contentSpaces;
0986 final int trailingSpaces;
0987 final boolean hasTab;
0988 final BitSet spaceMap;
0989
0990 JustificationInfo(int start, int end, int leadingSpaces,
0991 int contentSpaces, int trailingSpaces, boolean hasTab,
0992 BitSet spaceMap) {
0993 this .start = start;
0994 this .end = end;
0995 this .leadingSpaces = leadingSpaces;
0996 this .contentSpaces = contentSpaces;
0997 this .trailingSpaces = trailingSpaces;
0998 this .hasTab = hasTab;
0999 this .spaceMap = spaceMap;
1000 }
1001 }
1002
1003 JustificationInfo getJustificationInfo(int rowStartOffset) {
1004 if (justificationInfo != null) {
1005 return justificationInfo;
1006 }
1007 //states for the parsing
1008 final int TRAILING = 0;
1009 final int CONTENT = 1;
1010 final int SPACES = 2;
1011 int startOffset = getStartOffset();
1012 int endOffset = getEndOffset();
1013 Segment segment = getText(startOffset, endOffset);
1014 int txtOffset = segment.offset;
1015 int txtEnd = segment.offset + segment.count - 1;
1016 int startContentPosition = txtEnd + 1;
1017 int endContentPosition = txtOffset - 1;
1018 int lastTabPosition = txtOffset - 1;
1019 int trailingSpaces = 0;
1020 int contentSpaces = 0;
1021 int leadingSpaces = 0;
1022 boolean hasTab = false;
1023 BitSet spaceMap = new BitSet(endOffset - startOffset + 1);
1024
1025 //we parse conent to the right of the rightmost TAB only.
1026 //we are looking for the trailing and leading spaces.
1027 //position after the leading spaces (startContentPosition)
1028 //position before the trailing spaces (endContentPosition)
1029 for (int i = txtEnd, state = TRAILING; i >= txtOffset; i--) {
1030 if (' ' == segment.array[i]) {
1031 spaceMap.set(i - txtOffset);
1032 if (state == TRAILING) {
1033 trailingSpaces++;
1034 } else if (state == CONTENT) {
1035 state = SPACES;
1036 leadingSpaces = 1;
1037 } else if (state == SPACES) {
1038 leadingSpaces++;
1039 }
1040 } else if ('\t' == segment.array[i]) {
1041 hasTab = true;
1042 break;
1043 } else {
1044 if (state == TRAILING) {
1045 if ('\n' != segment.array[i]
1046 && '\r' != segment.array[i]) {
1047 state = CONTENT;
1048 endContentPosition = i;
1049 }
1050 } else if (state == CONTENT) {
1051 //do nothing
1052 } else if (state == SPACES) {
1053 contentSpaces += leadingSpaces;
1054 leadingSpaces = 0;
1055 }
1056 startContentPosition = i;
1057 }
1058 }
1059
1060 SegmentCache.releaseSharedSegment(segment);
1061
1062 int startJustifiableContent = -1;
1063 if (startContentPosition < txtEnd) {
1064 startJustifiableContent = startContentPosition - txtOffset;
1065 }
1066 int endJustifiableContent = -1;
1067 if (endContentPosition > txtOffset) {
1068 endJustifiableContent = endContentPosition - txtOffset;
1069 }
1070 justificationInfo = new JustificationInfo(
1071 startJustifiableContent, endJustifiableContent,
1072 leadingSpaces, contentSpaces, trailingSpaces, hasTab,
1073 spaceMap);
1074 return justificationInfo;
1075 }
1076
1077 // --- variables ------------------------------------------------
1078
1079 /**
1080 * Used by paint() to store highlighted view positions
1081 */
1082 private byte[] selections = null;
1083
1084 int offset;
1085 int length;
1086 // if it is an implied newline character
1087 boolean impliedCR;
1088 private static final String IMPLIED_CR = "CR";
1089 boolean skipWidth;
1090
1091 /**
1092 * how to expand tabs
1093 */
1094 TabExpander expander;
1095
1096 /**
1097 * location for determining tab expansion against.
1098 */
1099 int x;
1100
1101 /**
1102 * Glyph rendering functionality.
1103 */
1104 GlyphPainter painter;
1105
1106 /**
1107 * The prototype painter used by default.
1108 */
1109 static GlyphPainter defaultPainter;
1110
1111 private JustificationInfo justificationInfo = null;
1112
1113 /**
1114 * A class to perform rendering of the glyphs.
1115 * This can be implemented to be stateless, or
1116 * to hold some information as a cache to
1117 * facilitate faster rendering and model/view
1118 * translation. At a minimum, the GlyphPainter
1119 * allows a View implementation to perform its
1120 * duties independant of a particular version
1121 * of JVM and selection of capabilities (i.e.
1122 * shaping for i18n, etc).
1123 *
1124 * @since 1.3
1125 */
1126 public static abstract class GlyphPainter {
1127
1128 /**
1129 * Determine the span the glyphs given a start location
1130 * (for tab expansion).
1131 */
1132 public abstract float getSpan(GlyphView v, int p0, int p1,
1133 TabExpander e, float x);
1134
1135 public abstract float getHeight(GlyphView v);
1136
1137 public abstract float getAscent(GlyphView v);
1138
1139 public abstract float getDescent(GlyphView v);
1140
1141 /**
1142 * Paint the glyphs representing the given range.
1143 */
1144 public abstract void paint(GlyphView v, Graphics g, Shape a,
1145 int p0, int p1);
1146
1147 /**
1148 * Provides a mapping from the document model coordinate space
1149 * to the coordinate space of the view mapped to it.
1150 * This is shared by the broken views.
1151 *
1152 * @param v the <code>GlyphView</code> containing the
1153 * destination coordinate space
1154 * @param pos the position to convert
1155 * @param bias either <code>Position.Bias.Forward</code>
1156 * or <code>Position.Bias.Backward</code>
1157 * @param a Bounds of the View
1158 * @return the bounding box of the given position
1159 * @exception BadLocationException if the given position does not represent a
1160 * valid location in the associated document
1161 * @see View#modelToView
1162 */
1163 public abstract Shape modelToView(GlyphView v, int pos,
1164 Position.Bias bias, Shape a)
1165 throws BadLocationException;
1166
1167 /**
1168 * Provides a mapping from the view coordinate space to the logical
1169 * coordinate space of the model.
1170 *
1171 * @param v the <code>GlyphView</code> to provide a mapping for
1172 * @param x the X coordinate
1173 * @param y the Y coordinate
1174 * @param a the allocated region to render into
1175 * @param biasReturn either <code>Position.Bias.Forward</code>
1176 * or <code>Position.Bias.Backward</code>
1177 * is returned as the zero-th element of this array
1178 * @return the location within the model that best represents the
1179 * given point of view
1180 * @see View#viewToModel
1181 */
1182 public abstract int viewToModel(GlyphView v, float x, float y,
1183 Shape a, Position.Bias[] biasReturn);
1184
1185 /**
1186 * Determines the model location that represents the
1187 * maximum advance that fits within the given span.
1188 * This could be used to break the given view. The result
1189 * should be a location just shy of the given advance. This
1190 * differs from viewToModel which returns the closest
1191 * position which might be proud of the maximum advance.
1192 *
1193 * @param v the view to find the model location to break at.
1194 * @param p0 the location in the model where the
1195 * fragment should start it's representation >= 0.
1196 * @param x the graphic location along the axis that the
1197 * broken view would occupy >= 0. This may be useful for
1198 * things like tab calculations.
1199 * @param len specifies the distance into the view
1200 * where a potential break is desired >= 0.
1201 * @return the maximum model location possible for a break.
1202 * @see View#breakView
1203 */
1204 public abstract int getBoundedPosition(GlyphView v, int p0,
1205 float x, float len);
1206
1207 /**
1208 * Create a painter to use for the given GlyphView. If
1209 * the painter carries state it can create another painter
1210 * to represent a new GlyphView that is being created. If
1211 * the painter doesn't hold any significant state, it can
1212 * return itself. The default behavior is to return itself.
1213 * @param v the <code>GlyphView</code> to provide a painter for
1214 * @param p0 the starting document offset >= 0
1215 * @param p1 the ending document offset >= p0
1216 */
1217 public GlyphPainter getPainter(GlyphView v, int p0, int p1) {
1218 return this ;
1219 }
1220
1221 /**
1222 * Provides a way to determine the next visually represented model
1223 * location that one might place a caret. Some views may not be
1224 * visible, they might not be in the same order found in the model, or
1225 * they just might not allow access to some of the locations in the
1226 * model.
1227 *
1228 * @param v the view to use
1229 * @param pos the position to convert >= 0
1230 * @param b either <code>Position.Bias.Forward</code>
1231 * or <code>Position.Bias.Backward</code>
1232 * @param a the allocated region to render into
1233 * @param direction the direction from the current position that can
1234 * be thought of as the arrow keys typically found on a keyboard.
1235 * This may be SwingConstants.WEST, SwingConstants.EAST,
1236 * SwingConstants.NORTH, or SwingConstants.SOUTH.
1237 * @param biasRet either <code>Position.Bias.Forward</code>
1238 * or <code>Position.Bias.Backward</code>
1239 * is returned as the zero-th element of this array
1240 * @return the location within the model that best represents the next
1241 * location visual position.
1242 * @exception BadLocationException
1243 * @exception IllegalArgumentException for an invalid direction
1244 */
1245 public int getNextVisualPositionFrom(GlyphView v, int pos,
1246 Position.Bias b, Shape a, int direction,
1247 Position.Bias[] biasRet) throws BadLocationException {
1248
1249 int startOffset = v.getStartOffset();
1250 int endOffset = v.getEndOffset();
1251 Segment text;
1252
1253 switch (direction) {
1254 case View.NORTH:
1255 case View.SOUTH:
1256 if (pos != -1) {
1257 // Presumably pos is between startOffset and endOffset,
1258 // since GlyphView is only one line, we won't contain
1259 // the position to the nort/south, therefore return -1.
1260 return -1;
1261 }
1262 Container container = v.getContainer();
1263
1264 if (container instanceof JTextComponent) {
1265 Caret c = ((JTextComponent) container).getCaret();
1266 Point magicPoint;
1267 magicPoint = (c != null) ? c
1268 .getMagicCaretPosition() : null;
1269
1270 if (magicPoint == null) {
1271 biasRet[0] = Position.Bias.Forward;
1272 return startOffset;
1273 }
1274 int value = v.viewToModel(magicPoint.x, 0f, a,
1275 biasRet);
1276 return value;
1277 }
1278 break;
1279 case View.EAST:
1280 if (startOffset == v.getDocument().getLength()) {
1281 if (pos == -1) {
1282 biasRet[0] = Position.Bias.Forward;
1283 return startOffset;
1284 }
1285 // End case for bidi text where newline is at beginning
1286 // of line.
1287 return -1;
1288 }
1289 if (pos == -1) {
1290 biasRet[0] = Position.Bias.Forward;
1291 return startOffset;
1292 }
1293 if (pos == endOffset) {
1294 return -1;
1295 }
1296 if (++pos == endOffset) {
1297 // Assumed not used in bidi text, GlyphPainter2 will
1298 // override as necessary, therefore return -1.
1299 return -1;
1300 } else {
1301 biasRet[0] = Position.Bias.Forward;
1302 }
1303 return pos;
1304 case View.WEST:
1305 if (startOffset == v.getDocument().getLength()) {
1306 if (pos == -1) {
1307 biasRet[0] = Position.Bias.Forward;
1308 return startOffset;
1309 }
1310 // End case for bidi text where newline is at beginning
1311 // of line.
1312 return -1;
1313 }
1314 if (pos == -1) {
1315 // Assumed not used in bidi text, GlyphPainter2 will
1316 // override as necessary, therefore return -1.
1317 biasRet[0] = Position.Bias.Forward;
1318 return endOffset - 1;
1319 }
1320 if (pos == startOffset) {
1321 return -1;
1322 }
1323 biasRet[0] = Position.Bias.Forward;
1324 return (pos - 1);
1325 default:
1326 throw new IllegalArgumentException("Bad direction: "
1327 + direction);
1328 }
1329 return pos;
1330
1331 }
1332 }
1333 }
|