001 /* GlyphView.java -- A view to render styled text
002 Copyright (C) 2005 Free Software Foundation, Inc.
003
004 This file is part of GNU Classpath.
005
006 GNU Classpath is free software; you can redistribute it and/or modify
007 it under the terms of the GNU General Public License as published by
008 the Free Software Foundation; either version 2, or (at your option)
009 any later version.
010
011 GNU Classpath is distributed in the hope that it will be useful, but
012 WITHOUT ANY WARRANTY; without even the implied warranty of
013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014 General Public License for more details.
015
016 You should have received a copy of the GNU General Public License
017 along with GNU Classpath; see the file COPYING. If not, write to the
018 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019 02110-1301 USA.
020
021 Linking this library statically or dynamically with other modules is
022 making a combined work based on this library. Thus, the terms and
023 conditions of the GNU General Public License cover the whole
024 combination.
025
026 As a special exception, the copyright holders of this library give you
027 permission to link this library with independent modules to produce an
028 executable, regardless of the license terms of these independent
029 modules, and to copy and distribute the resulting executable under
030 terms of your choice, provided that you also meet, for each linked
031 independent module, the terms and conditions of the license of that
032 module. An independent module is a module which is not derived from
033 or based on this library. If you modify this library, you may extend
034 this exception to your version of the library, but you are not
035 obligated to do so. If you do not wish to do so, delete this
036 exception statement from your version. */
037
038
039 package javax.swing.text;
040
041 import gnu.classpath.SystemProperties;
042
043 import java.awt.Color;
044 import java.awt.Container;
045 import java.awt.Font;
046 import java.awt.FontMetrics;
047 import java.awt.Graphics;
048 import java.awt.Graphics2D;
049 import java.awt.Rectangle;
050 import java.awt.Shape;
051 import java.awt.Toolkit;
052 import java.awt.font.FontRenderContext;
053 import java.awt.font.TextHitInfo;
054 import java.awt.font.TextLayout;
055 import java.awt.geom.Rectangle2D;
056
057 import javax.swing.SwingConstants;
058 import javax.swing.event.DocumentEvent;
059 import javax.swing.text.Position.Bias;
060
061 /**
062 * Renders a run of styled text. This {@link View} subclass paints the
063 * characters of the <code>Element</code> it is responsible for using
064 * the style information from that <code>Element</code>.
065 *
066 * @author Roman Kennke (roman@kennke.org)
067 */
068 public class GlyphView extends View implements TabableView, Cloneable
069 {
070
071 /**
072 * An abstract base implementation for a glyph painter for
073 * <code>GlyphView</code>.
074 */
075 public abstract static class GlyphPainter
076 {
077 /**
078 * Creates a new <code>GlyphPainer</code>.
079 */
080 public GlyphPainter()
081 {
082 // Nothing to do here.
083 }
084
085 /**
086 * Returns the ascent of the font that is used by this glyph painter.
087 *
088 * @param v the glyph view
089 *
090 * @return the ascent of the font that is used by this glyph painter
091 */
092 public abstract float getAscent(GlyphView v);
093
094 /**
095 * Returns the descent of the font that is used by this glyph painter.
096 *
097 * @param v the glyph view
098 *
099 * @return the descent of the font that is used by this glyph painter
100 */
101 public abstract float getDescent(GlyphView v);
102
103 /**
104 * Returns the full height of the rendered text.
105 *
106 * @return the full height of the rendered text
107 */
108 public abstract float getHeight(GlyphView view);
109
110 /**
111 * Determines the model offset, so that the text between <code>p0</code>
112 * and this offset fits within the span starting at <code>x</code> with
113 * the length of <code>len</code>.
114 *
115 * @param v the glyph view
116 * @param p0 the starting offset in the model
117 * @param x the start location in the view
118 * @param len the length of the span in the view
119 */
120 public abstract int getBoundedPosition(GlyphView v, int p0, float x,
121 float len);
122
123 /**
124 * Paints the glyphs.
125 *
126 * @param view the glyph view to paint
127 * @param g the graphics context to use for painting
128 * @param a the allocation of the glyph view
129 * @param p0 the start position (in the model) from which to paint
130 * @param p1 the end position (in the model) to which to paint
131 */
132 public abstract void paint(GlyphView view, Graphics g, Shape a, int p0,
133 int p1);
134
135 /**
136 * Maps a position in the document into the coordinate space of the View.
137 * The output rectangle usually reflects the font height but has a width
138 * of zero.
139 *
140 * @param view the glyph view
141 * @param pos the position of the character in the model
142 * @param a the area that is occupied by the view
143 * @param b either {@link Position.Bias#Forward} or
144 * {@link Position.Bias#Backward} depending on the preferred
145 * direction bias. If <code>null</code> this defaults to
146 * <code>Position.Bias.Forward</code>
147 *
148 * @return a rectangle that gives the location of the document position
149 * inside the view coordinate space
150 *
151 * @throws BadLocationException if <code>pos</code> is invalid
152 * @throws IllegalArgumentException if b is not one of the above listed
153 * valid values
154 */
155 public abstract Shape modelToView(GlyphView view, int pos, Position.Bias b,
156 Shape a)
157 throws BadLocationException;
158
159 /**
160 * Maps a visual position into a document location.
161 *
162 * @param v the glyph view
163 * @param x the X coordinate of the visual position
164 * @param y the Y coordinate of the visual position
165 * @param a the allocated region
166 * @param biasRet filled with the bias of the model location on method exit
167 *
168 * @return the model location that represents the specified view location
169 */
170 public abstract int viewToModel(GlyphView v, float x, float y, Shape a,
171 Position.Bias[] biasRet);
172
173 /**
174 * Determine the span of the glyphs from location <code>p0</code> to
175 * location <code>p1</code>. If <code>te</code> is not <code>null</code>,
176 * then TABs are expanded using this <code>TabExpander</code>.
177 * The parameter <code>x</code> is the location at which the view is
178 * located (this is important when using TAB expansion).
179 *
180 * @param view the glyph view
181 * @param p0 the starting location in the document model
182 * @param p1 the end location in the document model
183 * @param te the tab expander to use
184 * @param x the location at which the view is located
185 *
186 * @return the span of the glyphs from location <code>p0</code> to
187 * location <code>p1</code>, possibly using TAB expansion
188 */
189 public abstract float getSpan(GlyphView view, int p0, int p1,
190 TabExpander te, float x);
191
192
193 /**
194 * Returns the model location that should be used to place a caret when
195 * moving the caret through the document.
196 *
197 * @param v the glyph view
198 * @param pos the current model location
199 * @param b the bias for <code>p</code>
200 * @param a the allocated region for the glyph view
201 * @param direction the direction from the current position; Must be one of
202 * {@link SwingConstants#EAST}, {@link SwingConstants#WEST},
203 * {@link SwingConstants#NORTH} or {@link SwingConstants#SOUTH}
204 * @param biasRet filled with the bias of the resulting location when method
205 * returns
206 *
207 * @return the location within the document that should be used to place the
208 * caret when moving the caret around the document
209 *
210 * @throws BadLocationException if <code>pos</code> is an invalid model
211 * location
212 * @throws IllegalArgumentException if <code>d</code> is invalid
213 */
214 public int getNextVisualPositionFrom(GlyphView v, int pos, Position.Bias b,
215 Shape a, int direction,
216 Position.Bias[] biasRet)
217 throws BadLocationException
218
219 {
220 int result = pos;
221 switch (direction)
222 {
223 case SwingConstants.EAST:
224 result = pos + 1;
225 break;
226 case SwingConstants.WEST:
227 result = pos - 1;
228 break;
229 case SwingConstants.NORTH:
230 case SwingConstants.SOUTH:
231 default:
232 // This should be handled in enclosing view, since the glyph view
233 // does not layout vertically.
234 break;
235 }
236 return result;
237 }
238
239 /**
240 * Returns a painter that can be used to render the specified glyph view.
241 * If this glyph painter is stateful, then it should return a new instance.
242 * However, if this painter is stateless it should return itself. The
243 * default behaviour is to return itself.
244 *
245 * @param v the glyph view for which to create a painter
246 * @param p0 the start offset of the rendered area
247 * @param p1 the end offset of the rendered area
248 *
249 * @return a painter that can be used to render the specified glyph view
250 */
251 public GlyphPainter getPainter(GlyphView v, int p0, int p1)
252 {
253 return this;
254 }
255 }
256
257 /**
258 * A GlyphPainter implementation based on TextLayout. This should give
259 * better performance in Java2D environments.
260 */
261 private static class J2DGlyphPainter
262 extends GlyphPainter
263 {
264
265 /**
266 * The text layout.
267 */
268 TextLayout textLayout;
269
270 /**
271 * Creates a new J2DGlyphPainter.
272 *
273 * @param str the string
274 * @param font the font
275 * @param frc the font render context
276 */
277 J2DGlyphPainter(String str, Font font, FontRenderContext frc)
278 {
279 textLayout = new TextLayout(str, font, frc);
280 }
281
282 /**
283 * Returns null so that GlyphView.checkPainter() creates a new instance.
284 */
285 public GlyphPainter getPainter(GlyphView v, int p0, int p1)
286 {
287 return null;
288 }
289
290 /**
291 * Delegates to the text layout.
292 */
293 public float getAscent(GlyphView v)
294 {
295 return textLayout.getAscent();
296 }
297
298 /**
299 * Delegates to the text layout.
300 */
301 public int getBoundedPosition(GlyphView v, int p0, float x, float len)
302 {
303 int pos;
304 TextHitInfo hit = textLayout.hitTestChar(len, 0);
305 if (hit.getCharIndex() == -1 && ! textLayout.isLeftToRight())
306 pos = v.getEndOffset();
307 else
308 {
309 pos = hit.isLeadingEdge() ? hit.getInsertionIndex()
310 : hit.getInsertionIndex() - 1;
311 pos += v.getStartOffset();
312 }
313 return pos;
314 }
315
316 /**
317 * Delegates to the text layout.
318 */
319 public float getDescent(GlyphView v)
320 {
321 return textLayout.getDescent();
322 }
323
324 /**
325 * Delegates to the text layout.
326 */
327 public float getHeight(GlyphView view)
328 {
329 return textLayout.getAscent() + textLayout.getDescent()
330 + textLayout.getLeading();
331 }
332
333 /**
334 * Delegates to the text layout.
335 */
336 public float getSpan(GlyphView v, int p0, int p1, TabExpander te, float x)
337 {
338 float span;
339 if (p0 == v.getStartOffset() && p1 == v.getEndOffset())
340 span = textLayout.getAdvance();
341 else
342 {
343 int start = v.getStartOffset();
344 int i0 = p0 - start;
345 int i1 = p1 - start;
346 TextHitInfo hit0 = TextHitInfo.afterOffset(i0);
347 TextHitInfo hit1 = TextHitInfo.afterOffset(i1);
348 float x0 = textLayout.getCaretInfo(hit0)[0];
349 float x1 = textLayout.getCaretInfo(hit1)[0];
350 span = Math.abs(x1 - x0);
351 }
352 return span;
353 }
354
355 /**
356 * Delegates to the text layout.
357 */
358 public Shape modelToView(GlyphView v, int pos, Bias b, Shape a)
359 throws BadLocationException
360 {
361 int offs = pos - v.getStartOffset();
362 // Create copy here to protect original shape.
363 Rectangle2D bounds = a.getBounds2D();
364 TextHitInfo hit =
365 b == Position.Bias.Forward ? TextHitInfo.afterOffset(offs)
366 : TextHitInfo.beforeOffset(offs);
367 float[] loc = textLayout.getCaretInfo(hit);
368 bounds.setRect(bounds.getX() + loc[0], bounds.getY(), 1,
369 bounds.getHeight());
370 return bounds;
371 }
372
373 /**
374 * Delegates to the text layout.
375 */
376 public void paint(GlyphView view, Graphics g, Shape a, int p0, int p1)
377 {
378 // Can't paint this with plain graphics.
379 if (g instanceof Graphics2D)
380 {
381 Graphics2D g2d = (Graphics2D) g;
382 Rectangle2D b = a instanceof Rectangle2D ? (Rectangle2D) a
383 : a.getBounds2D();
384 float x = (float) b.getX();
385 float y = (float) b.getY() + textLayout.getAscent()
386 + textLayout.getLeading();
387 // TODO: Try if clipping makes things faster for narrow views.
388 textLayout.draw(g2d, x, y);
389 }
390 }
391
392 /**
393 * Delegates to the text layout.
394 */
395 public int viewToModel(GlyphView v, float x, float y, Shape a,
396 Bias[] biasRet)
397 {
398 Rectangle2D bounds = a instanceof Rectangle2D ? (Rectangle2D) a
399 : a.getBounds2D();
400 TextHitInfo hit = textLayout.hitTestChar(x - (float) bounds.getX(), 0);
401 int pos = hit.getInsertionIndex();
402 biasRet[0] = hit.isLeadingEdge() ? Position.Bias.Forward
403 : Position.Bias.Backward;
404 return pos + v.getStartOffset();
405 }
406
407 }
408
409 /**
410 * The default <code>GlyphPainter</code> used in <code>GlyphView</code>.
411 */
412 static class DefaultGlyphPainter extends GlyphPainter
413 {
414 FontMetrics fontMetrics;
415
416 /**
417 * Returns the full height of the rendered text.
418 *
419 * @return the full height of the rendered text
420 */
421 public float getHeight(GlyphView view)
422 {
423 updateFontMetrics(view);
424 float height = fontMetrics.getHeight();
425 return height;
426 }
427
428 /**
429 * Paints the glyphs.
430 *
431 * @param view the glyph view to paint
432 * @param g the graphics context to use for painting
433 * @param a the allocation of the glyph view
434 * @param p0 the start position (in the model) from which to paint
435 * @param p1 the end position (in the model) to which to paint
436 */
437 public void paint(GlyphView view, Graphics g, Shape a, int p0,
438 int p1)
439 {
440 updateFontMetrics(view);
441 Rectangle r = a instanceof Rectangle ? (Rectangle) a : a.getBounds();
442 TabExpander tabEx = view.getTabExpander();
443 Segment txt = view.getText(p0, p1);
444
445 // Find out the X location at which we have to paint.
446 int x = r.x;
447 int p = view.getStartOffset();
448 if (p != p0)
449 {
450 int width = Utilities.getTabbedTextWidth(txt, fontMetrics,x, tabEx,
451 p);
452 x += width;
453 }
454 // Find out Y location.
455 int y = r.y + fontMetrics.getHeight() - fontMetrics.getDescent();
456
457 // Render the thing.
458 g.setFont(fontMetrics.getFont());
459 Utilities.drawTabbedText(txt, x, y, g, tabEx, p0);
460
461 }
462
463 /**
464 * Maps a position in the document into the coordinate space of the View.
465 * The output rectangle usually reflects the font height but has a width
466 * of zero.
467 *
468 * @param view the glyph view
469 * @param pos the position of the character in the model
470 * @param a the area that is occupied by the view
471 * @param b either {@link Position.Bias#Forward} or
472 * {@link Position.Bias#Backward} depending on the preferred
473 * direction bias. If <code>null</code> this defaults to
474 * <code>Position.Bias.Forward</code>
475 *
476 * @return a rectangle that gives the location of the document position
477 * inside the view coordinate space
478 *
479 * @throws BadLocationException if <code>pos</code> is invalid
480 * @throws IllegalArgumentException if b is not one of the above listed
481 * valid values
482 */
483 public Shape modelToView(GlyphView view, int pos, Position.Bias b,
484 Shape a)
485 throws BadLocationException
486 {
487 updateFontMetrics(view);
488 Element el = view.getElement();
489 Segment txt = view.getText(el.getStartOffset(), pos);
490 Rectangle bounds = a instanceof Rectangle ? (Rectangle) a
491 : a.getBounds();
492 TabExpander expander = view.getTabExpander();
493 int width = Utilities.getTabbedTextWidth(txt, fontMetrics, bounds.x,
494 expander,
495 view.getStartOffset());
496 int height = fontMetrics.getHeight();
497 Rectangle result = new Rectangle(bounds.x + width, bounds.y,
498 0, height);
499 return result;
500 }
501
502 /**
503 * Determine the span of the glyphs from location <code>p0</code> to
504 * location <code>p1</code>. If <code>te</code> is not <code>null</code>,
505 * then TABs are expanded using this <code>TabExpander</code>.
506 * The parameter <code>x</code> is the location at which the view is
507 * located (this is important when using TAB expansion).
508 *
509 * @param view the glyph view
510 * @param p0 the starting location in the document model
511 * @param p1 the end location in the document model
512 * @param te the tab expander to use
513 * @param x the location at which the view is located
514 *
515 * @return the span of the glyphs from location <code>p0</code> to
516 * location <code>p1</code>, possibly using TAB expansion
517 */
518 public float getSpan(GlyphView view, int p0, int p1,
519 TabExpander te, float x)
520 {
521 updateFontMetrics(view);
522 Segment txt = view.getText(p0, p1);
523 int span = Utilities.getTabbedTextWidth(txt, fontMetrics, (int) x, te,
524 p0);
525 return span;
526 }
527
528 /**
529 * Returns the ascent of the text run that is rendered by this
530 * <code>GlyphPainter</code>.
531 *
532 * @param v the glyph view
533 *
534 * @return the ascent of the text run that is rendered by this
535 * <code>GlyphPainter</code>
536 *
537 * @see FontMetrics#getAscent()
538 */
539 public float getAscent(GlyphView v)
540 {
541 updateFontMetrics(v);
542 return fontMetrics.getAscent();
543 }
544
545 /**
546 * Returns the descent of the text run that is rendered by this
547 * <code>GlyphPainter</code>.
548 *
549 * @param v the glyph view
550 *
551 * @return the descent of the text run that is rendered by this
552 * <code>GlyphPainter</code>
553 *
554 * @see FontMetrics#getDescent()
555 */
556 public float getDescent(GlyphView v)
557 {
558 updateFontMetrics(v);
559 return fontMetrics.getDescent();
560 }
561
562 /**
563 * Determines the model offset, so that the text between <code>p0</code>
564 * and this offset fits within the span starting at <code>x</code> with
565 * the length of <code>len</code>.
566 *
567 * @param v the glyph view
568 * @param p0 the starting offset in the model
569 * @param x the start location in the view
570 * @param len the length of the span in the view
571 */
572 public int getBoundedPosition(GlyphView v, int p0, float x, float len)
573 {
574 updateFontMetrics(v);
575 TabExpander te = v.getTabExpander();
576 Segment txt = v.getText(p0, v.getEndOffset());
577 int pos = Utilities.getTabbedTextOffset(txt, fontMetrics, (int) x,
578 (int) (x + len), te, p0, false);
579 return pos + p0;
580 }
581
582 /**
583 * Maps a visual position into a document location.
584 *
585 * @param v the glyph view
586 * @param x the X coordinate of the visual position
587 * @param y the Y coordinate of the visual position
588 * @param a the allocated region
589 * @param biasRet filled with the bias of the model location on method exit
590 *
591 * @return the model location that represents the specified view location
592 */
593 public int viewToModel(GlyphView v, float x, float y, Shape a,
594 Bias[] biasRet)
595 {
596 Rectangle r = a instanceof Rectangle ? (Rectangle) a : a.getBounds();
597 int p0 = v.getStartOffset();
598 int p1 = v.getEndOffset();
599 TabExpander te = v.getTabExpander();
600 Segment s = v.getText(p0, p1);
601 int offset = Utilities.getTabbedTextOffset(s, fontMetrics, r.x, (int) x,
602 te, p0);
603 int ret = p0 + offset;
604 if (ret == p1)
605 ret--;
606 biasRet[0] = Position.Bias.Forward;
607 return ret;
608 }
609
610 private void updateFontMetrics(GlyphView v)
611 {
612 Font font = v.getFont();
613 if (fontMetrics == null || ! font.equals(fontMetrics.getFont()))
614 {
615 Container c = v.getContainer();
616 FontMetrics fm;
617 if (c != null)
618 fm = c.getFontMetrics(font);
619 else
620 fm = Toolkit.getDefaultToolkit().getFontMetrics(font);
621 fontMetrics = fm;
622 }
623 }
624 }
625
626 /**
627 * The GlyphPainer used for painting the glyphs.
628 */
629 GlyphPainter glyphPainter;
630
631 /**
632 * The start offset within the document for this view.
633 */
634 private int offset;
635
636 /**
637 * The end offset within the document for this view.
638 */
639 private int length;
640
641 /**
642 * The x location against which the tab expansion is done.
643 */
644 private float tabX;
645
646 /**
647 * The tab expander that is used in this view.
648 */
649 private TabExpander tabExpander;
650
651 /**
652 * Creates a new <code>GlyphView</code> for the given <code>Element</code>.
653 *
654 * @param element the element that is rendered by this GlyphView
655 */
656 public GlyphView(Element element)
657 {
658 super(element);
659 offset = 0;
660 length = 0;
661 }
662
663 /**
664 * Returns the <code>GlyphPainter</code> that is used by this
665 * <code>GlyphView</code>. If no <code>GlyphPainer</code> has been installed
666 * <code>null</code> is returned.
667 *
668 * @return the glyph painter that is used by this
669 * glyph view or <code>null</code> if no glyph painter has been
670 * installed
671 */
672 public GlyphPainter getGlyphPainter()
673 {
674 return glyphPainter;
675 }
676
677 /**
678 * Sets the {@link GlyphPainter} to be used for this <code>GlyphView</code>.
679 *
680 * @param painter the glyph painter to be used for this glyph view
681 */
682 public void setGlyphPainter(GlyphPainter painter)
683 {
684 glyphPainter = painter;
685 }
686
687 /**
688 * Checks if a <code>GlyphPainer</code> is installed. If this is not the
689 * case, a default painter is installed.
690 */
691 protected void checkPainter()
692 {
693 if (glyphPainter == null)
694 {
695 if ("true".equals(
696 SystemProperties.getProperty("gnu.javax.swing.noGraphics2D")))
697 {
698 glyphPainter = new DefaultGlyphPainter();
699 }
700 else
701 {
702 Segment s = getText(getStartOffset(), getEndOffset());
703 glyphPainter = new J2DGlyphPainter(s.toString(), getFont(),
704 new FontRenderContext(null,
705 false,
706 false));
707 }
708 }
709 }
710
711 /**
712 * Renders the <code>Element</code> that is associated with this
713 * <code>View</code>.
714 *
715 * @param g the <code>Graphics</code> context to render to
716 * @param a the allocated region for the <code>Element</code>
717 */
718 public void paint(Graphics g, Shape a)
719 {
720 checkPainter();
721 int p0 = getStartOffset();
722 int p1 = getEndOffset();
723
724 Rectangle r = a instanceof Rectangle ? (Rectangle) a : a.getBounds();
725 Container c = getContainer();
726
727 Color fg = getForeground();
728 JTextComponent tc = null;
729 if (c instanceof JTextComponent)
730 {
731 tc = (JTextComponent) c;
732 if (! tc.isEnabled())
733 fg = tc.getDisabledTextColor();
734 }
735 Color bg = getBackground();
736 if (bg != null)
737 {
738 g.setColor(bg);
739 System.err.println("fill background: " + bg);
740 g.fillRect(r.x, r.y, r.width, r.height);
741 }
742
743
744 // Paint layered highlights if there are any.
745 if (tc != null)
746 {
747 Highlighter h = tc.getHighlighter();
748 if (h instanceof LayeredHighlighter)
749 {
750 LayeredHighlighter lh = (LayeredHighlighter) h;
751 lh.paintLayeredHighlights(g, p0, p1, a, tc, this);
752 }
753 }
754
755 g.setColor(fg);
756 glyphPainter.paint(this, g, a, p0, p1);
757 boolean underline = isUnderline();
758 boolean striked = isStrikeThrough();
759 if (underline || striked)
760 {
761 View parent = getParent();
762 // X coordinate.
763 if (parent != null && parent.getEndOffset() == p1)
764 {
765 // Strip whitespace.
766 Segment s = getText(p0, p1);
767 while (s.count > 0 && Character.isWhitespace(s.array[s.count - 1]))
768 {
769 p1--;
770 s.count--;
771 }
772 }
773 int x0 = r.x;
774 int p = getStartOffset();
775 TabExpander tabEx = getTabExpander();
776 if (p != p0)
777 x0 += (int) glyphPainter.getSpan(this, p, p0, tabEx, x0);
778 int x1 = x0 + (int) glyphPainter.getSpan(this, p0, p1, tabEx, x0);
779 // Y coordinate.
780 int y = r.y + r.height - (int) glyphPainter.getDescent(this);
781 if (underline)
782 {
783 int yTmp = y;
784 yTmp += 1;
785 g.drawLine(x0, yTmp, x1, yTmp);
786 }
787 if (striked)
788 {
789 int yTmp = y;
790 yTmp -= (int) glyphPainter.getAscent(this);
791 g.drawLine(x0, yTmp, x1, yTmp);
792 }
793 }
794 }
795
796
797 /**
798 * Returns the preferred span of the content managed by this
799 * <code>View</code> along the specified <code>axis</code>.
800 *
801 * @param axis the axis
802 *
803 * @return the preferred span of this <code>View</code>.
804 */
805 public float getPreferredSpan(int axis)
806 {
807 float span = 0;
808 checkPainter();
809 GlyphPainter painter = getGlyphPainter();
810 switch (axis)
811 {
812 case X_AXIS:
813 TabExpander tabEx = null;
814 View parent = getParent();
815 if (parent instanceof TabExpander)
816 tabEx = (TabExpander) parent;
817 span = painter.getSpan(this, getStartOffset(), getEndOffset(),
818 tabEx, 0.F);
819 break;
820 case Y_AXIS:
821 span = painter.getHeight(this);
822 if (isSuperscript())
823 span += span / 3;
824 break;
825 default:
826 throw new IllegalArgumentException("Illegal axis");
827 }
828 return span;
829 }
830
831 /**
832 * Maps a position in the document into the coordinate space of the View.
833 * The output rectangle usually reflects the font height but has a width
834 * of zero.
835 *
836 * @param pos the position of the character in the model
837 * @param a the area that is occupied by the view
838 * @param b either {@link Position.Bias#Forward} or
839 * {@link Position.Bias#Backward} depending on the preferred
840 * direction bias. If <code>null</code> this defaults to
841 * <code>Position.Bias.Forward</code>
842 *
843 * @return a rectangle that gives the location of the document position
844 * inside the view coordinate space
845 *
846 * @throws BadLocationException if <code>pos</code> is invalid
847 * @throws IllegalArgumentException if b is not one of the above listed
848 * valid values
849 */
850 public Shape modelToView(int pos, Shape a, Position.Bias b)
851 throws BadLocationException
852 {
853 GlyphPainter p = getGlyphPainter();
854 return p.modelToView(this, pos, b, a);
855 }
856
857 /**
858 * Maps coordinates from the <code>View</code>'s space into a position
859 * in the document model.
860 *
861 * @param x the x coordinate in the view space
862 * @param y the y coordinate in the view space
863 * @param a the allocation of this <code>View</code>
864 * @param b the bias to use
865 *
866 * @return the position in the document that corresponds to the screen
867 * coordinates <code>x, y</code>
868 */
869 public int viewToModel(float x, float y, Shape a, Position.Bias[] b)
870 {
871 checkPainter();
872 GlyphPainter painter = getGlyphPainter();
873 return painter.viewToModel(this, x, y, a, b);
874 }
875
876 /**
877 * Return the {@link TabExpander} to use.
878 *
879 * @return the {@link TabExpander} to use
880 */
881 public TabExpander getTabExpander()
882 {
883 return tabExpander;
884 }
885
886 /**
887 * Returns the preferred span of this view for tab expansion.
888 *
889 * @param x the location of the view
890 * @param te the tab expander to use
891 *
892 * @return the preferred span of this view for tab expansion
893 */
894 public float getTabbedSpan(float x, TabExpander te)
895 {
896 checkPainter();
897 TabExpander old = tabExpander;
898 tabExpander = te;
899 if (tabExpander != old)
900 {
901 // Changing the tab expander will lead to a relayout in the X_AXIS.
902 preferenceChanged(null, true, false);
903 }
904 tabX = x;
905 return getGlyphPainter().getSpan(this, getStartOffset(),
906 getEndOffset(), tabExpander, x);
907 }
908
909 /**
910 * Returns the span of a portion of the view. This is used in TAB expansion
911 * for fragments that don't contain TABs.
912 *
913 * @param p0 the start index
914 * @param p1 the end index
915 *
916 * @return the span of the specified portion of the view
917 */
918 public float getPartialSpan(int p0, int p1)
919 {
920 checkPainter();
921 return glyphPainter.getSpan(this, p0, p1, tabExpander, tabX);
922 }
923
924 /**
925 * Returns the start offset in the document model of the portion
926 * of text that this view is responsible for.
927 *
928 * @return the start offset in the document model of the portion
929 * of text that this view is responsible for
930 */
931 public int getStartOffset()
932 {
933 Element el = getElement();
934 int offs = el.getStartOffset();
935 if (length > 0)
936 offs += offset;
937 return offs;
938 }
939
940 /**
941 * Returns the end offset in the document model of the portion
942 * of text that this view is responsible for.
943 *
944 * @return the end offset in the document model of the portion
945 * of text that this view is responsible for
946 */
947 public int getEndOffset()
948 {
949 Element el = getElement();
950 int offs;
951 if (length > 0)
952 offs = el.getStartOffset() + offset + length;
953 else
954 offs = el.getEndOffset();
955 return offs;
956 }
957
958 private Segment cached = new Segment();
959
960 /**
961 * Returns the text segment that this view is responsible for.
962 *
963 * @param p0 the start index in the document model
964 * @param p1 the end index in the document model
965 *
966 * @return the text segment that this view is responsible for
967 */
968 public Segment getText(int p0, int p1)
969 {
970 try
971 {
972 getDocument().getText(p0, p1 - p0, cached);
973 }
974 catch (BadLocationException ex)
975 {
976 AssertionError ae;
977 ae = new AssertionError("BadLocationException should not be "
978 + "thrown here. p0 = " + p0 + ", p1 = " + p1);
979 ae.initCause(ex);
980 throw ae;
981 }
982
983 return cached;
984 }
985
986 /**
987 * Returns the font for the text run for which this <code>GlyphView</code>
988 * is responsible.
989 *
990 * @return the font for the text run for which this <code>GlyphView</code>
991 * is responsible
992 */
993 public Font getFont()
994 {
995 Document doc = getDocument();
996 Font font = null;
997 if (doc instanceof StyledDocument)
998 {
999 StyledDocument styledDoc = (StyledDocument) doc;
1000 font = styledDoc.getFont(getAttributes());
1001 }
1002 else
1003 {
1004 Container c = getContainer();
1005 if (c != null)
1006 font = c.getFont();
1007 }
1008 return font;
1009 }
1010
1011 /**
1012 * Returns the foreground color which should be used to paint the text.
1013 * This is fetched from the associated element's text attributes using
1014 * {@link StyleConstants#getForeground}.
1015 *
1016 * @return the foreground color which should be used to paint the text
1017 */
1018 public Color getForeground()
1019 {
1020 Element el = getElement();
1021 AttributeSet atts = el.getAttributes();
1022 return StyleConstants.getForeground(atts);
1023 }
1024
1025 /**
1026 * Returns the background color which should be used to paint the text.
1027 * This is fetched from the associated element's text attributes using
1028 * {@link StyleConstants#getBackground}.
1029 *
1030 * @return the background color which should be used to paint the text
1031 */
1032 public Color getBackground()
1033 {
1034 Element el = getElement();
1035 AttributeSet atts = el.getAttributes();
1036 // We cannot use StyleConstants.getBackground() here, because that returns
1037 // BLACK as default (when background == null). What we need is the
1038 // background setting of the text component instead, which is what we get
1039 // when background == null anyway.
1040 return (Color) atts.getAttribute(StyleConstants.Background);
1041 }
1042
1043 /**
1044 * Determines whether the text should be rendered strike-through or not. This
1045 * is determined using the method
1046 * {@link StyleConstants#isStrikeThrough(AttributeSet)} on the element of
1047 * this view.
1048 *
1049 * @return whether the text should be rendered strike-through or not
1050 */
1051 public boolean isStrikeThrough()
1052 {
1053 Element el = getElement();
1054 AttributeSet atts = el.getAttributes();
1055 return StyleConstants.isStrikeThrough(atts);
1056 }
1057
1058 /**
1059 * Determines whether the text should be rendered as subscript or not. This
1060 * is determined using the method
1061 * {@link StyleConstants#isSubscript(AttributeSet)} on the element of
1062 * this view.
1063 *
1064 * @return whether the text should be rendered as subscript or not
1065 */
1066 public boolean isSubscript()
1067 {
1068 Element el = getElement();
1069 AttributeSet atts = el.getAttributes();
1070 return StyleConstants.isSubscript(atts);
1071 }
1072
1073 /**
1074 * Determines whether the text should be rendered as superscript or not. This
1075 * is determined using the method
1076 * {@link StyleConstants#isSuperscript(AttributeSet)} on the element of
1077 * this view.
1078 *
1079 * @return whether the text should be rendered as superscript or not
1080 */
1081 public boolean isSuperscript()
1082 {
1083 Element el = getElement();
1084 AttributeSet atts = el.getAttributes();
1085 return StyleConstants.isSuperscript(atts);
1086 }
1087
1088 /**
1089 * Determines whether the text should be rendered as underlined or not. This
1090 * is determined using the method
1091 * {@link StyleConstants#isUnderline(AttributeSet)} on the element of
1092 * this view.
1093 *
1094 * @return whether the text should be rendered as underlined or not
1095 */
1096 public boolean isUnderline()
1097 {
1098 Element el = getElement();
1099 AttributeSet atts = el.getAttributes();
1100 return StyleConstants.isUnderline(atts);
1101 }
1102
1103 /**
1104 * Creates and returns a shallow clone of this GlyphView. This is used by
1105 * the {@link #createFragment} and {@link #breakView} methods.
1106 *
1107 * @return a shallow clone of this GlyphView
1108 */
1109 protected final Object clone()
1110 {
1111 try
1112 {
1113 return super.clone();
1114 }
1115 catch (CloneNotSupportedException ex)
1116 {
1117 AssertionError err = new AssertionError("CloneNotSupportedException "
1118 + "must not be thrown here");
1119 err.initCause(ex);
1120 throw err;
1121 }
1122 }
1123
1124 /**
1125 * Tries to break the view near the specified view span <code>len</code>.
1126 * The glyph view can only be broken in the X direction. For Y direction it
1127 * returns itself.
1128 *
1129 * @param axis the axis for breaking, may be {@link View#X_AXIS} or
1130 * {@link View#Y_AXIS}
1131 * @param p0 the model location where the fragment should start
1132 * @param pos the view position along the axis where the fragment starts
1133 * @param len the desired length of the fragment view
1134 *
1135 * @return the fragment view, or <code>this</code> if breaking was not
1136 * possible
1137 */
1138 public View breakView(int axis, int p0, float pos, float len)
1139 {
1140 View brokenView = this;
1141 if (axis == X_AXIS)
1142 {
1143 checkPainter();
1144 int end = glyphPainter.getBoundedPosition(this, p0, pos, len);
1145 int breakLoc = getBreakLocation(p0, end);
1146 if (breakLoc != -1)
1147 end = breakLoc;
1148 if (p0 != getStartOffset() || end != getEndOffset())
1149 {
1150 brokenView = createFragment(p0, end);
1151 if (brokenView instanceof GlyphView)
1152 ((GlyphView) brokenView).tabX = pos;
1153 }
1154 }
1155 return brokenView;
1156 }
1157
1158 /**
1159 * Determines how well the specified view location is suitable for inserting
1160 * a line break. If <code>axis</code> is <code>View.Y_AXIS</code>, then
1161 * this method forwards to the superclass, if <code>axis</code> is
1162 * <code>View.X_AXIS</code> then this method returns
1163 * {@link View#ExcellentBreakWeight} if there is a suitable break location
1164 * (usually whitespace) within the specified view span, or
1165 * {@link View#GoodBreakWeight} if not.
1166 *
1167 * @param axis the axis along which the break weight is requested
1168 * @param pos the starting view location
1169 * @param len the length of the span at which the view should be broken
1170 *
1171 * @return the break weight
1172 */
1173 public int getBreakWeight(int axis, float pos, float len)
1174 {
1175 int weight;
1176 if (axis == Y_AXIS)
1177 weight = super.getBreakWeight(axis, pos, len);
1178 else
1179 {
1180 checkPainter();
1181 int start = getStartOffset();
1182 int end = glyphPainter.getBoundedPosition(this, start, pos, len);
1183 if (end == 0)
1184 weight = BadBreakWeight;
1185 else
1186 {
1187 if (getBreakLocation(start, end) != -1)
1188 weight = ExcellentBreakWeight;
1189 else
1190 weight = GoodBreakWeight;
1191 }
1192 }
1193 return weight;
1194 }
1195
1196 private int getBreakLocation(int start, int end)
1197 {
1198 int loc = -1;
1199 Segment s = getText(start, end);
1200 for (char c = s.last(); c != Segment.DONE && loc == -1; c = s.previous())
1201 {
1202 if (Character.isWhitespace(c))
1203 {
1204 loc = s.getIndex() - s.getBeginIndex() + 1 + start;
1205 }
1206 }
1207 return loc;
1208 }
1209
1210 /**
1211 * Receives notification that some text attributes have changed within the
1212 * text fragment that this view is responsible for. This calls
1213 * {@link View#preferenceChanged(View, boolean, boolean)} on the parent for
1214 * both width and height.
1215 *
1216 * @param e the document event describing the change; not used here
1217 * @param a the view allocation on screen; not used here
1218 * @param vf the view factory; not used here
1219 */
1220 public void changedUpdate(DocumentEvent e, Shape a, ViewFactory vf)
1221 {
1222 preferenceChanged(null, true, true);
1223 }
1224
1225 /**
1226 * Receives notification that some text has been inserted within the
1227 * text fragment that this view is responsible for. This calls
1228 * {@link View#preferenceChanged(View, boolean, boolean)} for the
1229 * direction in which the glyphs are rendered.
1230 *
1231 * @param e the document event describing the change; not used here
1232 * @param a the view allocation on screen; not used here
1233 * @param vf the view factory; not used here
1234 */
1235 public void insertUpdate(DocumentEvent e, Shape a, ViewFactory vf)
1236 {
1237 preferenceChanged(null, true, false);
1238 }
1239
1240 /**
1241 * Receives notification that some text has been removed within the
1242 * text fragment that this view is responsible for. This calls
1243 * {@link View#preferenceChanged(View, boolean, boolean)} on the parent for
1244 * width.
1245 *
1246 * @param e the document event describing the change; not used here
1247 * @param a the view allocation on screen; not used here
1248 * @param vf the view factory; not used here
1249 */
1250 public void removeUpdate(DocumentEvent e, Shape a, ViewFactory vf)
1251 {
1252 preferenceChanged(null, true, false);
1253 }
1254
1255 /**
1256 * Creates a fragment view of this view that starts at <code>p0</code> and
1257 * ends at <code>p1</code>.
1258 *
1259 * @param p0 the start location for the fragment view
1260 * @param p1 the end location for the fragment view
1261 *
1262 * @return the fragment view
1263 */
1264 public View createFragment(int p0, int p1)
1265 {
1266 checkPainter();
1267 Element el = getElement();
1268 GlyphView fragment = (GlyphView) clone();
1269 fragment.offset = p0 - el.getStartOffset();
1270 fragment.length = p1 - p0;
1271 fragment.glyphPainter = glyphPainter.getPainter(fragment, p0, p1);
1272 return fragment;
1273 }
1274
1275 /**
1276 * Returns the alignment of this view along the specified axis. For the Y
1277 * axis this is <code>(height - descent) / height</code> for the used font,
1278 * so that it is aligned along the baseline.
1279 * For the X axis the superclass is called.
1280 */
1281 public float getAlignment(int axis)
1282 {
1283 checkPainter();
1284 float align;
1285 if (axis == Y_AXIS)
1286 {
1287 GlyphPainter painter = getGlyphPainter();
1288 float height = painter.getHeight(this);
1289 float descent = painter.getDescent(this);
1290 float ascent = painter.getAscent(this);
1291 if (isSuperscript())
1292 align = 1.0F;
1293 else if (isSubscript())
1294 align = height > 0 ? (height - (descent + (ascent / 2))) / height
1295 : 0;
1296 else
1297 align = height > 0 ? (height - descent) / height : 0;
1298 }
1299 else
1300 align = super.getAlignment(axis);
1301
1302 return align;
1303 }
1304
1305 /**
1306 * Returns the model location that should be used to place a caret when
1307 * moving the caret through the document.
1308 *
1309 * @param pos the current model location
1310 * @param bias the bias for <code>p</code>
1311 * @param a the allocated region for the glyph view
1312 * @param direction the direction from the current position; Must be one of
1313 * {@link SwingConstants#EAST}, {@link SwingConstants#WEST},
1314 * {@link SwingConstants#NORTH} or {@link SwingConstants#SOUTH}
1315 * @param biasRet filled with the bias of the resulting location when method
1316 * returns
1317 *
1318 * @return the location within the document that should be used to place the
1319 * caret when moving the caret around the document
1320 *
1321 * @throws BadLocationException if <code>pos</code> is an invalid model
1322 * location
1323 * @throws IllegalArgumentException if <code>d</code> is invalid
1324 */
1325 public int getNextVisualPositionFrom(int pos, Position.Bias bias, Shape a,
1326 int direction, Position.Bias[] biasRet)
1327 throws BadLocationException
1328 {
1329 checkPainter();
1330 GlyphPainter painter = getGlyphPainter();
1331 return painter.getNextVisualPositionFrom(this, pos, bias, a, direction,
1332 biasRet);
1333 }
1334 }