KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > javax > swing > text > GlyphView


1 /*
2  * @(#)GlyphView.java 1.41 06/06/29
3  *
4  * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
5  * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
6  */

7 package javax.swing.text;
8
9 import java.awt.*;
10 import java.text.BreakIterator JavaDoc;
11 import javax.swing.event.*;
12 import java.util.BitSet JavaDoc;
13
14 import com.sun.java.swing.SwingUtilities2;
15
16 /**
17  * A GlyphView is a styled chunk of text that represents a view
18  * mapped over an element in the text model. This view is generally
19  * responsible for displaying text glyphs using character level
20  * attributes in some way.
21  * An implementation of the GlyphPainter class is used to do the
22  * actual rendering and model/view translations. This separates
23  * rendering from layout and management of the association with
24  * the model.
25  * <p>
26  * The view supports breaking for the purpose of formatting.
27  * The fragments produced by breaking share the view that has
28  * primary responsibility for the element (i.e. they are nested
29  * classes and carry only a small amount of state of their own)
30  * so they can share its resources.
31  * <p>
32  * Since this view
33  * represents text that may have tabs embedded in it, it implements the
34  * <code>TabableView</code> interface. Tabs will only be
35  * expanded if this view is embedded in a container that does
36  * tab expansion. ParagraphView is an example of a container
37  * that does tab expansion.
38  * <p>
39  *
40  * @since 1.3
41  *
42  * @author Timothy Prinzing
43  * @version 1.41 06/29/06
44  */

45 public class GlyphView extends View JavaDoc implements TabableView JavaDoc, Cloneable JavaDoc {
46
47     /**
48      * Constructs a new view wrapped on an element.
49      *
50      * @param elem the element
51      */

52     public GlyphView(Element JavaDoc elem) {
53     super(elem);
54     offset = 0;
55     length = 0;
56         Element JavaDoc parent = elem.getParentElement();
57         AttributeSet JavaDoc attr = elem.getAttributes();
58
59         // if there was an implied CR
60
impliedCR = (attr != null && attr.getAttribute(IMPLIED_CR) != null &&
61         // if this is non-empty paragraph
62
parent != null && parent.getElementCount() > 1);
63         skipWidth = elem.getName().equals("br");
64     }
65
66     /**
67      * Creates a shallow copy. This is used by the
68      * createFragment and breakView methods.
69      *
70      * @return the copy
71      */

72     protected final Object JavaDoc clone() {
73     Object JavaDoc o;
74     try {
75         o = super.clone();
76     } catch (CloneNotSupportedException JavaDoc cnse) {
77         o = null;
78     }
79     return o;
80     }
81
82     /**
83      * Fetch the currently installed glyph painter.
84      * If a painter has not yet been installed, and
85      * a default was not yet needed, null is returned.
86      */

87     public GlyphPainter getGlyphPainter() {
88     return painter;
89     }
90
91     /**
92      * Sets the painter to use for rendering glyphs.
93      */

94     public void setGlyphPainter(GlyphPainter p) {
95     painter = p;
96     }
97
98     /**
99      * Fetch a reference to the text that occupies
100      * the given range. This is normally used by
101      * the GlyphPainter to determine what characters
102      * it should render glyphs for.
103      *
104      * @param p0 the starting document offset >= 0
105      * @param p1 the ending document offset >= p0
106      * @return the <code>Segment</code> containing the text
107      */

108      public Segment JavaDoc getText(int p0, int p1) {
109          // When done with the returned Segment it should be released by
110
// invoking:
111
// SegmentCache.releaseSharedSegment(segment);
112
Segment JavaDoc text = SegmentCache.getSharedSegment();
113          try {
114              Document JavaDoc doc = getDocument();
115              doc.getText(p0, p1 - p0, text);
116          } catch (BadLocationException JavaDoc bl) {
117              throw new StateInvariantError JavaDoc("GlyphView: Stale view: " + bl);
118          }
119          return text;
120      }
121
122     /**
123      * Fetch the background color to use to render the
124      * glyphs. If there is no background color, null should
125      * be returned. This is implemented to call
126      * <code>StyledDocument.getBackground</code> if the associated
127      * document is a styled document, otherwise it returns null.
128      */

129     public Color getBackground() {
130     Document JavaDoc doc = getDocument();
131     if (doc instanceof StyledDocument JavaDoc) {
132         AttributeSet JavaDoc attr = getAttributes();
133         if (attr.isDefined(StyleConstants.Background)) {
134         return ((StyledDocument JavaDoc)doc).getBackground(attr);
135         }
136     }
137     return null;
138     }
139
140     /**
141      * Fetch the foreground color to use to render the
142      * glyphs. If there is no foreground color, null should
143      * be returned. This is implemented to call
144      * <code>StyledDocument.getBackground</code> if the associated
145      * document is a StyledDocument. If the associated document
146      * is not a StyledDocument, the associated components foreground
147      * color is used. If there is no associated component, null
148      * is returned.
149      */

150     public Color getForeground() {
151     Document JavaDoc doc = getDocument();
152     if (doc instanceof StyledDocument JavaDoc) {
153         AttributeSet JavaDoc attr = getAttributes();
154         return ((StyledDocument JavaDoc)doc).getForeground(attr);
155     }
156     Component c = getContainer();
157     if (c != null) {
158         return c.getForeground();
159     }
160     return null;
161     }
162
163     /**
164      * Fetch the font that the glyphs should be based
165      * upon. This is implemented to call
166      * <code>StyledDocument.getFont</code> if the associated
167      * document is a StyledDocument. If the associated document
168      * is not a StyledDocument, the associated components font
169      * is used. If there is no associated component, null
170      * is returned.
171      */

172     public Font getFont() {
173     Document JavaDoc doc = getDocument();
174     if (doc instanceof StyledDocument JavaDoc) {
175         AttributeSet JavaDoc attr = getAttributes();
176         return ((StyledDocument JavaDoc)doc).getFont(attr);
177     }
178     Component c = getContainer();
179     if (c != null) {
180         return c.getFont();
181     }
182     return null;
183     }
184     
185     /**
186      * Determine if the glyphs should be underlined. If true,
187      * an underline should be drawn through the baseline.
188      */

189     public boolean isUnderline() {
190     AttributeSet JavaDoc attr = getAttributes();
191     return StyleConstants.isUnderline(attr);
192     }
193
194     /**
195      * Determine if the glyphs should have a strikethrough
196      * line. If true, a line should be drawn through the center
197      * of the glyphs.
198      */

199     public boolean isStrikeThrough() {
200     AttributeSet JavaDoc attr = getAttributes();
201     return StyleConstants.isStrikeThrough(attr);
202     }
203
204     /**
205      * Determine if the glyphs should be rendered as superscript.
206      */

207     public boolean isSubscript() {
208     AttributeSet JavaDoc attr = getAttributes();
209     return StyleConstants.isSubscript(attr);
210     }
211
212     /**
213      * Determine if the glyphs should be rendered as subscript.
214      */

215     public boolean isSuperscript() {
216     AttributeSet JavaDoc attr = getAttributes();
217     return StyleConstants.isSuperscript(attr);
218     }
219
220     /**
221      * Fetch the TabExpander to use if tabs are present in this view.
222      */

223     public TabExpander JavaDoc getTabExpander() {
224     return expander;
225     }
226
227     /**
228      * Check to see that a glyph painter exists. If a painter
229      * doesn't exist, a default glyph painter will be installed.
230      */

231     protected void checkPainter() {
232     if (painter == null) {
233         if (defaultPainter == null) {
234         // the classname should probably come from a property file.
235
String JavaDoc classname = "javax.swing.text.GlyphPainter1";
236         try {
237             Class JavaDoc c;
238             ClassLoader JavaDoc loader = getClass().getClassLoader();
239             if (loader != null) {
240             c = loader.loadClass(classname);
241             } else {
242                 c = Class.forName(classname);
243             }
244             Object JavaDoc o = c.newInstance();
245             if (o instanceof GlyphPainter) {
246             defaultPainter = (GlyphPainter) o;
247             }
248         } catch (Throwable JavaDoc e) {
249             throw new StateInvariantError JavaDoc("GlyphView: Can't load glyph painter: "
250                           + classname);
251         }
252         }
253         setGlyphPainter(defaultPainter.getPainter(this, getStartOffset(),
254                               getEndOffset()));
255     }
256     }
257
258     // --- TabableView methods --------------------------------------
259

260     /**
261      * Determines the desired span when using the given
262      * tab expansion implementation.
263      *
264      * @param x the position the view would be located
265      * at for the purpose of tab expansion >= 0.
266      * @param e how to expand the tabs when encountered.
267      * @return the desired span >= 0
268      * @see TabableView#getTabbedSpan
269      */

270     public float getTabbedSpan(float x, TabExpander JavaDoc e) {
271     checkPainter();
272
273         TabExpander JavaDoc old = expander;
274     expander = e;
275
276         if (expander != old) {
277             // setting expander can change horizontal span of the view,
278
// so we have to call preferenceChanged()
279
preferenceChanged(null, true, false);
280         }
281
282     this.x = (int) x;
283     int p0 = getStartOffset();
284     int p1 = getEndOffset();
285     float width = painter.getSpan(this, p0, p1, expander, x);
286     return width;
287     }
288     
289     /**
290      * Determines the span along the same axis as tab
291      * expansion for a portion of the view. This is
292      * intended for use by the TabExpander for cases
293      * where the tab expansion involves aligning the
294      * portion of text that doesn't have whitespace
295      * relative to the tab stop. There is therefore
296      * an assumption that the range given does not
297      * contain tabs.
298      * <p>
299      * This method can be called while servicing the
300      * getTabbedSpan or getPreferredSize. It has to
301      * arrange for its own text buffer to make the
302      * measurements.
303      *
304      * @param p0 the starting document offset >= 0
305      * @param p1 the ending document offset >= p0
306      * @return the span >= 0
307      */

308     public float getPartialSpan(int p0, int p1) {
309     checkPainter();
310     float width = painter.getSpan(this, p0, p1, expander, x);
311     return width;
312     }
313
314     // --- View methods ---------------------------------------------
315

316     /**
317      * Fetches the portion of the model that this view is responsible for.
318      *
319      * @return the starting offset into the model
320      * @see View#getStartOffset
321      */

322     public int getStartOffset() {
323     Element JavaDoc e = getElement();
324     return (length > 0) ? e.getStartOffset() + offset : e.getStartOffset();
325     }
326     
327     /**
328      * Fetches the portion of the model that this view is responsible for.
329      *
330      * @return the ending offset into the model
331      * @see View#getEndOffset
332      */

333     public int getEndOffset() {
334     Element JavaDoc e = getElement();
335     return (length > 0) ? e.getStartOffset() + offset + length : e.getEndOffset();
336     }
337
338     /**
339      * Lazily initializes the selections field
340      */

341     private void initSelections(int p0, int p1) {
342         int viewPosCount = p1 - p0 + 1;
343         if (selections == null || viewPosCount > selections.length) {
344             selections = new byte[viewPosCount];
345             return;
346         }
347         for (int i = 0; i < viewPosCount; selections[i++] = 0);
348     }
349
350     /**
351      * Renders a portion of a text style run.
352      *
353      * @param g the rendering surface to use
354      * @param a the allocated region to render into
355      */

356     public void paint(Graphics g, Shape a) {
357     checkPainter();
358
359     boolean paintedText = false;
360     Component c = getContainer();
361     int p0 = getStartOffset();
362     int p1 = getEndOffset();
363     Rectangle alloc = (a instanceof Rectangle) ? (Rectangle)a : a.getBounds();
364     Color bg = getBackground();
365     Color fg = getForeground();
366
367         if (c instanceof JTextComponent JavaDoc) {
368             JTextComponent JavaDoc tc = (JTextComponent JavaDoc) c;
369         if (!tc.isEnabled()) {
370         fg = tc.getDisabledTextColor();
371         }
372         }
373     if (bg != null) {
374         g.setColor(bg);
375         g.fillRect(alloc.x, alloc.y, alloc.width, alloc.height);
376     }
377     if (c instanceof JTextComponent JavaDoc) {
378         JTextComponent JavaDoc tc = (JTextComponent JavaDoc) c;
379         Highlighter JavaDoc h = tc.getHighlighter();
380         if (h instanceof LayeredHighlighter JavaDoc) {
381         ((LayeredHighlighter JavaDoc)h).paintLayeredHighlights
382             (g, p0, p1, a, tc, this);
383         }
384     }
385
386     if (Utilities.isComposedTextElement(getElement())) {
387         Utilities.paintComposedText(g, a.getBounds(), this);
388         paintedText = true;
389     } else if(c instanceof JTextComponent JavaDoc) {
390         JTextComponent JavaDoc tc = (JTextComponent JavaDoc) c;
391         Color selFG = tc.getSelectedTextColor();
392             
393         if (// there's a highlighter (bug 4532590), and
394
(tc.getHighlighter() != null) &&
395                 // selected text color is different from regular foreground
396
(selFG != null) && !selFG.equals(fg)) {
397                 
398                 Highlighter.Highlight JavaDoc[] h = tc.getHighlighter().getHighlights();
399                 if(h.length != 0) {
400                     boolean initialized = false;
401                     int viewSelectionCount = 0;
402                     for (int i = 0; i < h.length; i++) {
403                         Highlighter.Highlight JavaDoc highlight = h[i];
404                         int hStart = highlight.getStartOffset();
405                         int hEnd = highlight.getEndOffset();
406                         if (hStart > p1 || hEnd < p0) {
407                             // the selection is out of this view
408
continue;
409                         }
410                         if (!SwingUtilities2.useSelectedTextColor(highlight, tc)) {
411                             continue;
412                         }
413                         if (hStart <= p0 && hEnd >= p1){
414                             // the whole view is selected
415
paintTextUsingColor(g, a, selFG, p0, p1);
416                             paintedText = true;
417                             break;
418                         }
419                         // the array is lazily created only when the view
420
// is partially selected
421
if (!initialized) {
422                             initSelections(p0, p1);
423                             initialized = true;
424                         }
425                         hStart = Math.max(p0, hStart);
426                         hEnd = Math.min(p1, hEnd);
427                         paintTextUsingColor(g, a, selFG, hStart, hEnd);
428                         // the array represents view positions [0, p1-p0+1]
429
// later will iterate this array and sum its
430
// elements. Positions with sum == 0 are not selected.
431
selections[hStart-p0]++;
432                         selections[hEnd-p0]--;
433
434                         viewSelectionCount++;
435                     }
436  
437                     if (!paintedText && viewSelectionCount > 0) {
438                         // the view is partially selected
439
int curPos = -1;
440                         int startPos = 0;
441                         int viewLen = p1 - p0;
442                         while (curPos++ < viewLen) {
443                             // searching for the next selection start
444
while(curPos < viewLen &&
445                                     selections[curPos] == 0) curPos++;
446                             if (startPos != curPos) {
447                                 // paint unselected text
448
paintTextUsingColor(g, a, fg,
449                                         p0 + startPos, p0 + curPos);
450                             }
451                             int checkSum = 0;
452                             // searching for next start position of unselected text
453
while (curPos < viewLen &&
454                                     (checkSum += selections[curPos]) != 0) curPos++;
455                             startPos = curPos;
456                         }
457                         paintedText = true;
458                     }
459                 }
460         }
461     }
462     if(!paintedText)
463         paintTextUsingColor(g, a, fg, p0, p1);
464     }
465
466     /**
467      * Paints the specified region of text in the specified color.
468      */

469     final void paintTextUsingColor(Graphics g, Shape a, Color c, int p0, int p1) {
470     // render the glyphs
471
g.setColor(c);
472     painter.paint(this, g, a, p0, p1);
473
474     // render underline or strikethrough if set.
475
boolean underline = isUnderline();
476     boolean strike = isStrikeThrough();
477     if (underline || strike) {
478         // calculate x coordinates
479
Rectangle alloc = (a instanceof Rectangle) ? (Rectangle)a : a.getBounds();
480         View JavaDoc parent = getParent();
481         if ((parent != null) && (parent.getEndOffset() == p1)) {
482         // strip whitespace on end
483
Segment JavaDoc s = getText(p0, p1);
484         while ((s.count > 0) && (Character.isWhitespace(s.array[s.count-1]))) {
485             p1 -= 1;
486             s.count -= 1;
487         }
488                 SegmentCache.releaseSharedSegment(s);
489         }
490         int x0 = alloc.x;
491         int p = getStartOffset();
492         if (p != p0) {
493         x0 += (int) painter.getSpan(this, p, p0, getTabExpander(), x0);
494         }
495         int x1 = x0 + (int) painter.getSpan(this, p0, p1, getTabExpander(), x0);
496
497         // calculate y coordinate
498
int d = (int) painter.getDescent(this);
499         int y = alloc.y + alloc.height - (int) painter.getDescent(this);
500         if (underline) {
501         int yTmp = y;
502         yTmp += 1;
503         g.drawLine(x0, yTmp, x1, yTmp);
504         }
505         if (strike) {
506         int yTmp = y;
507         // move y coordinate above baseline
508
yTmp -= (int) (painter.getAscent(this) * 0.3f);
509         g.drawLine(x0, yTmp, x1, yTmp);
510         }
511
512     }
513     }
514
515     /**
516      * Determines the preferred span for this view along an
517      * axis.
518      *
519      * @param axis may be either View.X_AXIS or View.Y_AXIS
520      * @return the span the view would like to be rendered into >= 0.
521      * Typically the view is told to render into the span
522      * that is returned, although there is no guarantee.
523      * The parent may choose to resize or break the view.
524      */

525     public float getPreferredSpan(int axis) {
526         if (impliedCR) {
527             return 0;
528         }
529     checkPainter();
530     int p0 = getStartOffset();
531     int p1 = getEndOffset();
532     switch (axis) {
533     case View.X_AXIS:
534         if (skipWidth) {
535         return 0;
536         }
537         return painter.getSpan(this, p0, p1, expander, this.x);
538     case View.Y_AXIS:
539         float h = painter.getHeight(this);
540         if (isSuperscript()) {
541         h += h/3;
542         }
543         return h;
544     default:
545         throw new IllegalArgumentException JavaDoc("Invalid axis: " + axis);
546     }
547     }
548
549     /**
550      * Determines the desired alignment for this view along an
551      * axis. For the label, the alignment is along the font
552      * baseline for the y axis, and the superclasses alignment
553      * along the x axis.
554      *
555      * @param axis may be either View.X_AXIS or View.Y_AXIS
556      * @return the desired alignment. This should be a value
557      * between 0.0 and 1.0 inclusive, where 0 indicates alignment at the
558      * origin and 1.0 indicates alignment to the full span
559      * away from the origin. An alignment of 0.5 would be the
560      * center of the view.
561      */

562     public float getAlignment(int axis) {
563     checkPainter();
564     if (axis == View.Y_AXIS) {
565         boolean sup = isSuperscript();
566         boolean sub = isSubscript();
567         float h = painter.getHeight(this);
568         float d = painter.getDescent(this);
569         float a = painter.getAscent(this);
570         float align;
571         if (sup) {
572         align = 1.0f;
573         } else if (sub) {
574         align = (h > 0) ? (h - (d + (a / 2))) / h : 0;
575         } else {
576         align = (h > 0) ? (h - d) / h : 0;
577         }
578         return align;
579     }
580     return super.getAlignment(axis);
581     }
582
583     /**
584      * Provides a mapping from the document model coordinate space
585      * to the coordinate space of the view mapped to it.
586      *
587      * @param pos the position to convert >= 0
588      * @param a the allocated region to render into
589      * @param b either <code>Position.Bias.Forward</code>
590      * or <code>Position.Bias.Backward</code>
591      * @return the bounding box of the given position
592      * @exception BadLocationException if the given position does not represent a
593      * valid location in the associated document
594      * @see View#modelToView
595      */

596     public Shape modelToView(int pos, Shape a, Position.Bias JavaDoc b) throws BadLocationException JavaDoc {
597     checkPainter();
598     return painter.modelToView(this, pos, b, a);
599     }
600
601     /**
602      * Provides a mapping from the view coordinate space to the logical
603      * coordinate space of the model.
604      *
605      * @param x the X coordinate >= 0
606      * @param y the Y coordinate >= 0
607      * @param a the allocated region to render into
608      * @param biasReturn either <code>Position.Bias.Forward</code>
609      * or <code>Position.Bias.Backward</code> is returned as the
610      * zero-th element of this array
611      * @return the location within the model that best represents the
612      * given point of view >= 0
613      * @see View#viewToModel
614      */

615     public int viewToModel(float x, float y, Shape a, Position.Bias JavaDoc[] biasReturn) {
616     checkPainter();
617     return painter.viewToModel(this, x, y, a, biasReturn);
618     }
619
620     /**
621      * Determines how attractive a break opportunity in
622      * this view is. This can be used for determining which
623      * view is the most attractive to call <code>breakView</code>
624      * on in the process of formatting. The
625      * higher the weight, the more attractive the break. A
626      * value equal to or lower than <code>View.BadBreakWeight</code>
627      * should not be considered for a break. A value greater
628      * than or equal to <code>View.ForcedBreakWeight</code> should
629      * be broken.
630      * <p>
631      * This is implemented to forward to the superclass for
632      * the Y_AXIS. Along the X_AXIS the following values
633      * may be returned.
634      * <dl>
635      * <dt><b>View.ExcellentBreakWeight</b>
636      * <dd>if there is whitespace proceeding the desired break
637      * location.
638      * <dt><b>View.BadBreakWeight</b>
639      * <dd>if the desired break location results in a break
640      * location of the starting offset.
641      * <dt><b>View.GoodBreakWeight</b>
642      * <dd>if the other conditions don't occur.
643      * </dl>
644      * This will normally result in the behavior of breaking
645      * on a whitespace location if one can be found, otherwise
646      * breaking between characters.
647      *
648      * @param axis may be either View.X_AXIS or View.Y_AXIS
649      * @param pos the potential location of the start of the
650      * broken view >= 0. This may be useful for calculating tab
651      * positions.
652      * @param len specifies the relative length from <em>pos</em>
653      * where a potential break is desired >= 0.
654      * @return the weight, which should be a value between
655      * View.ForcedBreakWeight and View.BadBreakWeight.
656      * @see LabelView
657      * @see ParagraphView
658      * @see View#BadBreakWeight
659      * @see View#GoodBreakWeight
660      * @see View#ExcellentBreakWeight
661      * @see View#ForcedBreakWeight
662      */

663     public int getBreakWeight(int axis, float pos, float len) {
664     if (axis == View.X_AXIS) {
665         checkPainter();
666         int p0 = getStartOffset();
667         int p1 = painter.getBoundedPosition(this, p0, pos, len);
668         if (p1 == p0) {
669         // can't even fit a single character
670
return View.BadBreakWeight;
671         }
672             if (getBreakSpot(p0, p1) != -1) {
673                 return View.ExcellentBreakWeight;
674             }
675         // Nothing good to break on.
676
// breaking on the View boundary is better than splitting it
677
if (p1 == getEndOffset()) {
678                 return View.GoodBreakWeight;
679             } else {
680                 return View.GoodBreakWeight - 1;
681             }
682     }
683     return super.getBreakWeight(axis, pos, len);
684     }
685
686     /**
687      * Breaks this view on the given axis at the given length.
688      * This is implemented to attempt to break on a whitespace
689      * location, and returns a fragment with the whitespace at
690      * the end. If a whitespace location can't be found, the
691      * nearest character is used.
692      *
693      * @param axis may be either View.X_AXIS or View.Y_AXIS
694      * @param p0 the location in the model where the
695      * fragment should start it's representation >= 0.
696      * @param pos the position along the axis that the
697      * broken view would occupy >= 0. This may be useful for
698      * things like tab calculations.
699      * @param len specifies the distance along the axis
700      * where a potential break is desired >= 0.
701      * @return the fragment of the view that represents the
702      * given span, if the view can be broken. If the view
703      * doesn't support breaking behavior, the view itself is
704      * returned.
705      * @see View#breakView
706      */

707     public View JavaDoc breakView(int axis, int p0, float pos, float len) {
708     if (axis == View.X_AXIS) {
709         checkPainter();
710         int p1 = painter.getBoundedPosition(this, p0, pos, len);
711             int breakSpot = getBreakSpot(p0, p1);
712
713             if (breakSpot != -1) {
714                 p1 = breakSpot;
715             }
716             // else, no break in the region, return a fragment of the
717
// bounded region.
718
if (p0 == getStartOffset() && p1 == getEndOffset()) {
719                 return this;
720             }
721         GlyphView JavaDoc v = (GlyphView JavaDoc) createFragment(p0, p1);
722         v.x = (int) pos;
723         return v;
724     }
725     return this;
726     }
727
728     /**
729      * Returns a location to break at in the passed in region, or -1 if
730      * there isn't a good location to break at in the specified region.
731      */

732     private int getBreakSpot(int p0, int p1) {
733         Document JavaDoc doc = getDocument();
734
735         if (doc != null && Boolean.TRUE.equals(doc.getProperty(
736                                    AbstractDocument.MultiByteProperty))) {
737             return getBreakSpotUseBreakIterator(p0, p1);
738         }
739         return getBreakSpotUseWhitespace(p0, p1);
740     }
741
742     /**
743      * Returns the appropriate place to break based on the last whitespace
744      * character encountered.
745      */

746     private int getBreakSpotUseWhitespace(int p0, int p1) {
747         Segment JavaDoc s = getText(p0, p1);
748
749         for (char ch = s.last(); ch != Segment.DONE; ch = s.previous()) {
750             if (Character.isWhitespace(ch)) {
751                 // found whitespace
752
SegmentCache.releaseSharedSegment(s);
753                 return s.getIndex() - s.getBeginIndex() + 1 + p0;
754             }
755         }
756         SegmentCache.releaseSharedSegment(s);
757         return -1;
758     }
759      
760     /**
761      * Returns the appropriate place to break based on BreakIterator.
762      */

763     private int getBreakSpotUseBreakIterator(int p0, int p1) {
764         // Certain regions require context for BreakIterator, start from
765
// our parents start offset.
766
Element JavaDoc parent = getElement().getParentElement();
767         int parent0;
768         int parent1;
769         Container c = getContainer();
770         BreakIterator JavaDoc breaker;
771
772         if (parent == null) {
773             parent0 = p0;
774             parent1 = p1;
775         }
776         else {
777             parent0 = parent.getStartOffset();
778             parent1 = parent.getEndOffset();
779         }
780         if (c != null) {
781             breaker = BreakIterator.getLineInstance(c.getLocale());
782         }
783         else {
784             breaker = BreakIterator.getLineInstance();
785         }
786
787         Segment JavaDoc s = getText(parent0, parent1);
788         int breakPoint;
789
790         // Needed to initialize the Segment.
791
s.first();
792         breaker.setText(s);
793
794         if (p1 == parent1) {
795             // This will most likely return the end, the assumption is
796
// that if parent1 == p1, then we are the last portion of
797
// a paragraph
798
breakPoint = breaker.last();
799         }
800         else if (p1 + 1 == parent1) {
801             // assert(s.count > 1)
802
breakPoint = breaker.next(s.offset + s.count - 2);
803             if (breakPoint >= s.count + s.offset) {
804                 breakPoint = breaker.preceding(s.offset + s.count - 1);
805             }
806         }
807         else {
808             breakPoint = breaker.preceding(p1 - parent0 + s.offset + 1);
809         }
810
811         int retValue = -1;
812
813         if (breakPoint != BreakIterator.DONE) {
814             breakPoint = breakPoint - s.offset + parent0;
815             if (breakPoint > p0) {
816                 if (p0 == parent0 && breakPoint == p0) {
817                     retValue = -1;
818                 }
819                 else if (breakPoint <= p1) {
820                     retValue = breakPoint;
821                 }
822             }
823         }
824         SegmentCache.releaseSharedSegment(s);
825         return retValue;
826     }
827
828     /**
829      * Creates a view that represents a portion of the element.
830      * This is potentially useful during formatting operations
831      * for taking measurements of fragments of the view. If
832      * the view doesn't support fragmenting (the default), it
833      * should return itself.
834      * <p>
835      * This view does support fragmenting. It is implemented
836      * to return a nested class that shares state in this view
837      * representing only a portion of the view.
838      *
839      * @param p0 the starting offset >= 0. This should be a value
840      * greater or equal to the element starting offset and
841      * less than the element ending offset.
842      * @param p1 the ending offset > p0. This should be a value
843      * less than or equal to the elements end offset and
844      * greater than the elements starting offset.
845      * @return the view fragment, or itself if the view doesn't
846      * support breaking into fragments
847      * @see LabelView
848      */

849     public View JavaDoc createFragment(int p0, int p1) {
850     checkPainter();
851     Element JavaDoc elem = getElement();
852     GlyphView JavaDoc v = (GlyphView JavaDoc) clone();
853     v.offset = p0 - elem.getStartOffset();
854     v.length = p1 - p0;
855     v.painter = painter.getPainter(v, p0, p1);
856         v.justificationInfo = null;
857     return v;
858     }
859
860     /**
861      * Provides a way to determine the next visually represented model
862      * location that one might place a caret. Some views may not be
863      * visible, they might not be in the same order found in the model, or
864      * they just might not allow access to some of the locations in the
865      * model.
866      *
867      * @param pos the position to convert >= 0
868      * @param a the allocated region to render into
869      * @param direction the direction from the current position that can
870      * be thought of as the arrow keys typically found on a keyboard.
871      * This may be SwingConstants.WEST, SwingConstants.EAST,
872      * SwingConstants.NORTH, or SwingConstants.SOUTH.
873      * @return the location within the model that best represents the next
874      * location visual position.
875      * @exception BadLocationException
876      * @exception IllegalArgumentException for an invalid direction
877      */

878     public int getNextVisualPositionFrom(int pos, Position.Bias JavaDoc b, Shape a,
879                      int direction,
880                      Position.Bias JavaDoc[] biasRet)
881     throws BadLocationException JavaDoc {
882
883         return painter.getNextVisualPositionFrom(this, pos, b, a, direction, biasRet);
884     }
885
886     /**
887      * Gives notification that something was inserted into
888      * the document in a location that this view is responsible for.
889      * This is implemented to call preferenceChanged along the
890      * axis the glyphs are rendered.
891      *
892      * @param e the change information from the associated document
893      * @param a the current allocation of the view
894      * @param f the factory to use to rebuild if the view has children
895      * @see View#insertUpdate
896      */

897     public void insertUpdate(DocumentEvent e, Shape a, ViewFactory JavaDoc f) {
898         justificationInfo = null;
899     syncCR();
900     preferenceChanged(null, true, false);
901     }
902
903     /**
904      * Gives notification that something was removed from the document
905      * in a location that this view is responsible for.
906      * This is implemented to call preferenceChanged along the
907      * axis the glyphs are rendered.
908      *
909      * @param e the change information from the associated document
910      * @param a the current allocation of the view
911      * @param f the factory to use to rebuild if the view has children
912      * @see View#removeUpdate
913      */

914     public void removeUpdate(DocumentEvent e, Shape a, ViewFactory JavaDoc f) {
915         justificationInfo = null;
916     syncCR();
917     preferenceChanged(null, true, false);
918     }
919
920     /**
921      * Gives notification from the document that attributes were changed
922      * in a location that this view is responsible for.
923      * This is implemented to call preferenceChanged along both the
924      * horizontal and vertical axis.
925      *
926      * @param e the change information from the associated document
927      * @param a the current allocation of the view
928      * @param f the factory to use to rebuild if the view has children
929      * @see View#changedUpdate
930      */

931     public void changedUpdate(DocumentEvent e, Shape a, ViewFactory JavaDoc f) {
932     syncCR();
933     preferenceChanged(null, true, true);
934     }
935
936     // checks if the paragraph is empty and updates impliedCR flag
937
// accordingly
938
private void syncCR() {
939         if (impliedCR) {
940             Element JavaDoc parent = getElement().getParentElement();
941             impliedCR = (parent != null && parent.getElementCount() > 1);
942         }
943     }
944
945     /**
946      * Class to hold data needed to justify this GlyphView in a PargraphView.Row
947      */

948     static class JustificationInfo {
949         //justifiable content start
950
final int start;
951         //justifiable content end
952
final int end;
953         final int leadingSpaces;
954         final int contentSpaces;
955         final int trailingSpaces;
956         final boolean hasTab;
957         final BitSet JavaDoc spaceMap;
958         JustificationInfo(int start, int end,
959                           int leadingSpaces,
960                           int contentSpaces,
961                           int trailingSpaces,
962                           boolean hasTab,
963                           BitSet JavaDoc spaceMap) {
964             this.start = start;
965             this.end = end;
966             this.leadingSpaces = leadingSpaces;
967             this.contentSpaces = contentSpaces;
968             this.trailingSpaces = trailingSpaces;
969             this.hasTab = hasTab;
970             this.spaceMap = spaceMap;
971         }
972     }
973
974
975
976     JustificationInfo getJustificationInfo(int rowStartOffset) {
977         if (justificationInfo != null) {
978             return justificationInfo;
979         }
980         //states for the parsing
981
final int TRAILING = 0;
982         final int CONTENT = 1;
983         final int SPACES = 2;
984         int startOffset = getStartOffset();
985         int endOffset = getEndOffset();
986         Segment JavaDoc segment = getText(startOffset, endOffset);
987         int txtOffset = segment.offset;
988         int txtEnd = segment.offset + segment.count - 1;
989         int startContentPosition = txtEnd + 1;
990         int endContentPosition = txtOffset - 1;
991         int lastTabPosition = txtOffset - 1;
992         int trailingSpaces = 0;
993         int contentSpaces = 0;
994         int leadingSpaces = 0;
995         boolean hasTab = false;
996         BitSet JavaDoc spaceMap = new BitSet JavaDoc(endOffset - startOffset + 1);
997
998         //we parse conent to the right of the rightmost TAB only.
999
//we are looking for the trailing and leading spaces.
1000
//position after the leading spaces (startContentPosition)
1001
//position before the trailing spaces (endContentPosition)
1002
for (int i = txtEnd, state = TRAILING; i >= txtOffset; i--) {
1003            if (' ' == segment.array[i]) {
1004                spaceMap.set(i - txtOffset);
1005                if (state == TRAILING) {
1006                    trailingSpaces++;
1007                } else if (state == CONTENT) {
1008                    state = SPACES;
1009                    leadingSpaces = 1;
1010                } else if (state == SPACES) {
1011                    leadingSpaces++;
1012                }
1013            } else if ('\t' == segment.array[i]) {
1014                hasTab = true;
1015                break;
1016            } else {
1017                if (state == TRAILING) {
1018                    if ('\n' != segment.array[i]
1019                          && '\r' != segment.array[i]) {
1020                        state = CONTENT;
1021                        endContentPosition = i;
1022                    }
1023                } else if (state == CONTENT) {
1024                    //do nothing
1025
} else if (state == SPACES) {
1026                    contentSpaces += leadingSpaces;
1027                    leadingSpaces = 0;
1028                }
1029                startContentPosition = i;
1030            }
1031        }
1032
1033        SegmentCache.releaseSharedSegment(segment);
1034
1035        int startJustifiableContent = -1;
1036        if (startContentPosition < txtEnd) {
1037            startJustifiableContent =
1038                startContentPosition - txtOffset;
1039        }
1040        int endJustifiableContent = -1;
1041        if (endContentPosition > txtOffset) {
1042            endJustifiableContent =
1043                endContentPosition - txtOffset;
1044        }
1045        justificationInfo =
1046            new JustificationInfo(startJustifiableContent,
1047                                  endJustifiableContent,
1048                                  leadingSpaces,
1049                                  contentSpaces,
1050                                  trailingSpaces,
1051                                  hasTab,
1052                                  spaceMap);
1053        return justificationInfo;
1054    }
1055
1056    // --- variables ------------------------------------------------
1057

1058    /**
1059    * Used by paint() to store highlighted view positions
1060    */

1061    private byte[] selections = null;
1062
1063    int offset;
1064    int length;
1065    // if it is an implied newline character
1066
boolean impliedCR;
1067    private static final String JavaDoc IMPLIED_CR = "CR";
1068    boolean skipWidth;
1069    
1070    /**
1071     * how to expand tabs
1072     */

1073    TabExpander JavaDoc expander;
1074
1075    /**
1076     * location for determining tab expansion against.
1077     */

1078    int x;
1079
1080    /**
1081     * Glyph rendering functionality.
1082     */

1083    GlyphPainter painter;
1084
1085    /**
1086     * The prototype painter used by default.
1087     */

1088    static GlyphPainter defaultPainter;
1089
1090    private JustificationInfo justificationInfo = null;
1091
1092    /**
1093     * A class to perform rendering of the glyphs.
1094     * This can be implemented to be stateless, or
1095     * to hold some information as a cache to
1096     * facilitate faster rendering and model/view
1097     * translation. At a minimum, the GlyphPainter
1098     * allows a View implementation to perform its
1099     * duties independant of a particular version
1100     * of JVM and selection of capabilities (i.e.
1101     * shaping for i18n, etc).
1102     *
1103     * @since 1.3
1104     */

1105    public static abstract class GlyphPainter {
1106
1107    /**
1108     * Determine the span the glyphs given a start location
1109     * (for tab expansion).
1110     */

1111    public abstract float getSpan(GlyphView JavaDoc v, int p0, int p1, TabExpander JavaDoc e, float x);
1112
1113    public abstract float getHeight(GlyphView JavaDoc v);
1114
1115    public abstract float getAscent(GlyphView JavaDoc v);
1116
1117    public abstract float getDescent(GlyphView JavaDoc v);
1118
1119    /**
1120     * Paint the glyphs representing the given range.
1121     */

1122        public abstract void paint(GlyphView JavaDoc v, Graphics g, Shape a, int p0, int p1);
1123
1124    /**
1125     * Provides a mapping from the document model coordinate space
1126     * to the coordinate space of the view mapped to it.
1127     * This is shared by the broken views.
1128     *
1129         * @param v the <code>GlyphView</code> containing the
1130         * destination coordinate space
1131         * @param pos the position to convert
1132         * @param bias either <code>Position.Bias.Forward</code>
1133         * or <code>Position.Bias.Backward</code>
1134         * @param a Bounds of the View
1135         * @return the bounding box of the given position
1136     * @exception BadLocationException if the given position does not represent a
1137     * valid location in the associated document
1138     * @see View#modelToView
1139     */

1140    public abstract Shape modelToView(GlyphView JavaDoc v,
1141                      int pos, Position.Bias JavaDoc bias,
1142                      Shape a) throws BadLocationException JavaDoc;
1143
1144    /**
1145     * Provides a mapping from the view coordinate space to the logical
1146     * coordinate space of the model.
1147     *
1148         * @param v the <code>GlyphView</code> to provide a mapping for
1149     * @param x the X coordinate
1150     * @param y the Y coordinate
1151     * @param a the allocated region to render into
1152         * @param biasReturn either <code>Position.Bias.Forward</code>
1153         * or <code>Position.Bias.Backward</code>
1154         * is returned as the zero-th element of this array
1155     * @return the location within the model that best represents the
1156     * given point of view
1157     * @see View#viewToModel
1158     */

1159        public abstract int viewToModel(GlyphView JavaDoc v,
1160                    float x, float y, Shape a,
1161                    Position.Bias JavaDoc[] biasReturn);
1162
1163    /**
1164     * Determines the model location that represents the
1165     * maximum advance that fits within the given span.
1166     * This could be used to break the given view. The result
1167     * should be a location just shy of the given advance. This
1168     * differs from viewToModel which returns the closest
1169     * position which might be proud of the maximum advance.
1170     *
1171     * @param v the view to find the model location to break at.
1172     * @param p0 the location in the model where the
1173     * fragment should start it's representation >= 0.
1174     * @param x the graphic location along the axis that the
1175     * broken view would occupy >= 0. This may be useful for
1176     * things like tab calculations.
1177     * @param len specifies the distance into the view
1178     * where a potential break is desired >= 0.
1179     * @return the maximum model location possible for a break.
1180     * @see View#breakView
1181     */

1182        public abstract int getBoundedPosition(GlyphView JavaDoc v, int p0, float x, float len);
1183
1184    /**
1185     * Create a painter to use for the given GlyphView. If
1186     * the painter carries state it can create another painter
1187     * to represent a new GlyphView that is being created. If
1188     * the painter doesn't hold any significant state, it can
1189     * return itself. The default behavior is to return itself.
1190         * @param v the <code>GlyphView</code> to provide a painter for
1191         * @param p0 the starting document offset >= 0
1192         * @param p1 the ending document offset >= p0
1193     */

1194        public GlyphPainter getPainter(GlyphView JavaDoc v, int p0, int p1) {
1195        return this;
1196    }
1197
1198    /**
1199     * Provides a way to determine the next visually represented model
1200     * location that one might place a caret. Some views may not be
1201     * visible, they might not be in the same order found in the model, or
1202     * they just might not allow access to some of the locations in the
1203     * model.
1204     *
1205     * @param v the view to use
1206     * @param pos the position to convert >= 0
1207         * @param b either <code>Position.Bias.Forward</code>
1208         * or <code>Position.Bias.Backward</code>
1209     * @param a the allocated region to render into
1210     * @param direction the direction from the current position that can
1211     * be thought of as the arrow keys typically found on a keyboard.
1212     * This may be SwingConstants.WEST, SwingConstants.EAST,
1213     * SwingConstants.NORTH, or SwingConstants.SOUTH.
1214         * @param biasRet either <code>Position.Bias.Forward</code>
1215         * or <code>Position.Bias.Backward</code>
1216         * is returned as the zero-th element of this array
1217     * @return the location within the model that best represents the next
1218     * location visual position.
1219     * @exception BadLocationException
1220     * @exception IllegalArgumentException for an invalid direction
1221     */

1222        public int getNextVisualPositionFrom(GlyphView JavaDoc v, int pos, Position.Bias JavaDoc b, Shape a,
1223                         int direction,
1224                         Position.Bias JavaDoc[] biasRet)
1225        throws BadLocationException JavaDoc {
1226
1227        int startOffset = v.getStartOffset();
1228        int endOffset = v.getEndOffset();
1229        Segment JavaDoc text;
1230        
1231        switch (direction) {
1232        case View.NORTH:
1233        case View.SOUTH:
1234                if (pos != -1) {
1235                    // Presumably pos is between startOffset and endOffset,
1236
// since GlyphView is only one line, we won't contain
1237
// the position to the nort/south, therefore return -1.
1238
return -1;
1239                }
1240                Container container = v.getContainer();
1241
1242                if (container instanceof JTextComponent JavaDoc) {
1243                    Caret JavaDoc c = ((JTextComponent JavaDoc)container).getCaret();
1244                    Point magicPoint;
1245                    magicPoint = (c != null) ? c.getMagicCaretPosition() :null;
1246
1247                    if (magicPoint == null) {
1248                        biasRet[0] = Position.Bias.Forward;
1249                        return startOffset;
1250                    }
1251                    int value = v.viewToModel(magicPoint.x, 0f, a, biasRet);
1252                    return value;
1253                }
1254                break;
1255        case View.EAST:
1256        if(startOffset == v.getDocument().getLength()) {
1257            if(pos == -1) {
1258            biasRet[0] = Position.Bias.Forward;
1259            return startOffset;
1260            }
1261            // End case for bidi text where newline is at beginning
1262
// of line.
1263
return -1;
1264        }
1265        if(pos == -1) {
1266            biasRet[0] = Position.Bias.Forward;
1267            return startOffset;
1268        }
1269        if(pos == endOffset) {
1270            return -1;
1271        }
1272        if(++pos == endOffset) {
1273                    // Assumed not used in bidi text, GlyphPainter2 will
1274
// override as necessary, therefore return -1.
1275
return -1;
1276        }
1277        else {
1278            biasRet[0] = Position.Bias.Forward;
1279        }
1280        return pos;
1281        case View.WEST:
1282        if(startOffset == v.getDocument().getLength()) {
1283            if(pos == -1) {
1284            biasRet[0] = Position.Bias.Forward;
1285            return startOffset;
1286            }
1287            // End case for bidi text where newline is at beginning
1288
// of line.
1289
return -1;
1290        }
1291        if(pos == -1) {
1292                    // Assumed not used in bidi text, GlyphPainter2 will
1293
// override as necessary, therefore return -1.
1294
biasRet[0] = Position.Bias.Forward;
1295            return endOffset - 1;
1296        }
1297        if(pos == startOffset) {
1298            return -1;
1299        }
1300        biasRet[0] = Position.Bias.Forward;
1301        return (pos - 1);
1302        default:
1303        throw new IllegalArgumentException JavaDoc("Bad direction: " + direction);
1304        }
1305        return pos;
1306
1307    }
1308    }
1309}
1310
1311
Popular Tags