KickJava   Java API By Example, From Geeks To Geeks.

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


1 /*
2  * @(#)PlainView.java 1.74 04/04/15
3  *
4  * Copyright 2004 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.util.Vector JavaDoc;
10 import java.util.Properties JavaDoc;
11 import java.awt.*;
12 import javax.swing.event.*;
13
14 /**
15  * Implements View interface for a simple multi-line text view
16  * that has text in one font and color. The view represents each
17  * child element as a line of text.
18  *
19  * @author Timothy Prinzing
20  * @version 1.74 04/15/04
21  * @see View
22  */

23 public class PlainView extends View JavaDoc implements TabExpander JavaDoc {
24
25     /**
26      * Constructs a new PlainView wrapped on an element.
27      *
28      * @param elem the element
29      */

30     public PlainView(Element JavaDoc elem) {
31         super(elem);
32     }
33
34     /**
35      * Returns the tab size set for the document, defaulting to 8.
36      *
37      * @return the tab size
38      */

39     protected int getTabSize() {
40         Integer JavaDoc i = (Integer JavaDoc) getDocument().getProperty(PlainDocument.tabSizeAttribute);
41         int size = (i != null) ? i.intValue() : 8;
42         return size;
43     }
44
45     /**
46      * Renders a line of text, suppressing whitespace at the end
47      * and expanding any tabs. This is implemented to make calls
48      * to the methods <code>drawUnselectedText</code> and
49      * <code>drawSelectedText</code> so that the way selected and
50      * unselected text are rendered can be customized.
51      *
52      * @param lineIndex the line to draw >= 0
53      * @param g the <code>Graphics</code> context
54      * @param x the starting X position >= 0
55      * @param y the starting Y position >= 0
56      * @see #drawUnselectedText
57      * @see #drawSelectedText
58      */

59     protected void drawLine(int lineIndex, Graphics g, int x, int y) {
60         Element JavaDoc line = getElement().getElement(lineIndex);
61     Element JavaDoc elem;
62     
63     try {
64         if (line.isLeaf()) {
65                 drawElement(lineIndex, line, g, x, y);
66         } else {
67             // this line contains the composed text.
68
int count = line.getElementCount();
69         for(int i = 0; i < count; i++) {
70             elem = line.getElement(i);
71             x = drawElement(lineIndex, elem, g, x, y);
72         }
73         }
74         } catch (BadLocationException JavaDoc e) {
75             throw new StateInvariantError JavaDoc("Can't render line: " + lineIndex);
76         }
77     }
78    
79     private int drawElement(int lineIndex, Element JavaDoc elem, Graphics g, int x, int y) throws BadLocationException JavaDoc {
80     int p0 = elem.getStartOffset();
81         int p1 = elem.getEndOffset();
82         p1 = Math.min(getDocument().getLength(), p1);
83
84         if (lineIndex == 0) {
85             x += firstLineOffset;
86         }
87     AttributeSet JavaDoc attr = elem.getAttributes();
88     if (Utilities.isComposedTextAttributeDefined(attr)) {
89         g.setColor(unselected);
90         x = Utilities.drawComposedText(this, attr, g, x, y,
91                     p0-elem.getStartOffset(),
92                     p1-elem.getStartOffset());
93     } else {
94         if (sel0 == sel1 || selected == unselected) {
95         // no selection, or it is invisible
96
x = drawUnselectedText(g, x, y, p0, p1);
97         } else if ((p0 >= sel0 && p0 <= sel1) && (p1 >= sel0 && p1 <= sel1)) {
98         x = drawSelectedText(g, x, y, p0, p1);
99         } else if (sel0 >= p0 && sel0 <= p1) {
100         if (sel1 >= p0 && sel1 <= p1) {
101             x = drawUnselectedText(g, x, y, p0, sel0);
102             x = drawSelectedText(g, x, y, sel0, sel1);
103             x = drawUnselectedText(g, x, y, sel1, p1);
104         } else {
105             x = drawUnselectedText(g, x, y, p0, sel0);
106             x = drawSelectedText(g, x, y, sel0, p1);
107         }
108         } else if (sel1 >= p0 && sel1 <= p1) {
109         x = drawSelectedText(g, x, y, p0, sel1);
110         x = drawUnselectedText(g, x, y, sel1, p1);
111         } else {
112         x = drawUnselectedText(g, x, y, p0, p1);
113         }
114     }
115         
116         return x;
117     }
118
119     /**
120      * Renders the given range in the model as normal unselected
121      * text. Uses the foreground or disabled color to render the text.
122      *
123      * @param g the graphics context
124      * @param x the starting X coordinate >= 0
125      * @param y the starting Y coordinate >= 0
126      * @param p0 the beginning position in the model >= 0
127      * @param p1 the ending position in the model >= 0
128      * @return the X location of the end of the range >= 0
129      * @exception BadLocationException if the range is invalid
130      */

131     protected int drawUnselectedText(Graphics g, int x, int y,
132                                      int p0, int p1) throws BadLocationException JavaDoc {
133         g.setColor(unselected);
134         Document JavaDoc doc = getDocument();
135         Segment JavaDoc s = SegmentCache.getSharedSegment();
136         doc.getText(p0, p1 - p0, s);
137         int ret = Utilities.drawTabbedText(this, s, x, y, g, this, p0);
138         SegmentCache.releaseSharedSegment(s);
139         return ret;
140     }
141
142     /**
143      * Renders the given range in the model as selected text. This
144      * is implemented to render the text in the color specified in
145      * the hosting component. It assumes the highlighter will render
146      * the selected background.
147      *
148      * @param g the graphics context
149      * @param x the starting X coordinate >= 0
150      * @param y the starting Y coordinate >= 0
151      * @param p0 the beginning position in the model >= 0
152      * @param p1 the ending position in the model >= 0
153      * @return the location of the end of the range
154      * @exception BadLocationException if the range is invalid
155      */

156     protected int drawSelectedText(Graphics g, int x,
157                                    int y, int p0, int p1) throws BadLocationException JavaDoc {
158         g.setColor(selected);
159         Document JavaDoc doc = getDocument();
160         Segment JavaDoc s = SegmentCache.getSharedSegment();
161         doc.getText(p0, p1 - p0, s);
162         int ret = Utilities.drawTabbedText(this, s, x, y, g, this, p0);
163         SegmentCache.releaseSharedSegment(s);
164         return ret;
165     }
166
167     /**
168      * Gives access to a buffer that can be used to fetch
169      * text from the associated document.
170      *
171      * @return the buffer
172      */

173     protected final Segment JavaDoc getLineBuffer() {
174         if (lineBuffer == null) {
175             lineBuffer = new Segment JavaDoc();
176         }
177         return lineBuffer;
178     }
179
180     /**
181      * Checks to see if the font metrics and longest line
182      * are up-to-date.
183      *
184      * @since 1.4
185      */

186     protected void updateMetrics() {
187     Component host = getContainer();
188     Font f = host.getFont();
189     if (font != f) {
190         // The font changed, we need to recalculate the
191
// longest line.
192
calculateLongestLine();
193         tabSize = getTabSize() * metrics.charWidth('m');
194     }
195     }
196
197     // ---- View methods ----------------------------------------------------
198

199     /**
200      * Determines the preferred span for this view along an
201      * axis.
202      *
203      * @param axis may be either View.X_AXIS or View.Y_AXIS
204      * @return the span the view would like to be rendered into >= 0.
205      * Typically the view is told to render into the span
206      * that is returned, although there is no guarantee.
207      * The parent may choose to resize or break the view.
208      * @exception IllegalArgumentException for an invalid axis
209      */

210     public float getPreferredSpan(int axis) {
211     updateMetrics();
212         switch (axis) {
213         case View.X_AXIS:
214         return getLineWidth(longLine);
215         case View.Y_AXIS:
216             return getElement().getElementCount() * metrics.getHeight();
217         default:
218             throw new IllegalArgumentException JavaDoc("Invalid axis: " + axis);
219         }
220     }
221
222     /**
223      * Renders using the given rendering surface and area on that surface.
224      * The view may need to do layout and create child views to enable
225      * itself to render into the given allocation.
226      *
227      * @param g the rendering surface to use
228      * @param a the allocated region to render into
229      *
230      * @see View#paint
231      */

232     public void paint(Graphics g, Shape a) {
233     Shape originalA = a;
234     a = adjustPaintRegion(a);
235         Rectangle alloc = (Rectangle) a;
236         tabBase = alloc.x;
237     JTextComponent JavaDoc host = (JTextComponent JavaDoc) getContainer();
238         Highlighter JavaDoc h = host.getHighlighter();
239         g.setFont(host.getFont());
240         sel0 = host.getSelectionStart();
241         sel1 = host.getSelectionEnd();
242         unselected = (host.isEnabled()) ?
243             host.getForeground() : host.getDisabledTextColor();
244     Caret JavaDoc c = host.getCaret();
245         selected = c.isSelectionVisible() && h != null ?
246                        host.getSelectedTextColor() : unselected;
247     updateMetrics();
248
249         // If the lines are clipped then we don't expend the effort to
250
// try and paint them. Since all of the lines are the same height
251
// with this object, determination of what lines need to be repainted
252
// is quick.
253
Rectangle clip = g.getClipBounds();
254         int fontHeight = metrics.getHeight();
255         int heightBelow = (alloc.y + alloc.height) - (clip.y + clip.height);
256         int linesBelow = Math.max(0, heightBelow / fontHeight);
257         int heightAbove = clip.y - alloc.y;
258         int linesAbove = Math.max(0, heightAbove / fontHeight);
259         int linesTotal = alloc.height / fontHeight;
260
261     if (alloc.height % fontHeight != 0) {
262         linesTotal++;
263     }
264         // update the visible lines
265
Rectangle lineArea = lineToRect(a, linesAbove);
266         int y = lineArea.y + metrics.getAscent();
267         int x = lineArea.x;
268         Element JavaDoc map = getElement();
269     int lineCount = map.getElementCount();
270         int endLine = Math.min(lineCount, linesTotal - linesBelow);
271     lineCount--;
272     LayeredHighlighter JavaDoc dh = (h instanceof LayeredHighlighter JavaDoc) ?
273                        (LayeredHighlighter JavaDoc)h : null;
274         for (int line = linesAbove; line < endLine; line++) {
275         if (dh != null) {
276         Element JavaDoc lineElement = map.getElement(line);
277         if (line == lineCount) {
278             dh.paintLayeredHighlights(g, lineElement.getStartOffset(),
279                           lineElement.getEndOffset(),
280                           originalA, host, this);
281         }
282         else {
283             dh.paintLayeredHighlights(g, lineElement.getStartOffset(),
284                           lineElement.getEndOffset() - 1,
285                           originalA, host, this);
286         }
287         }
288             drawLine(line, g, x, y);
289             y += fontHeight;
290             if (line == 0) {
291                 // This should never really happen, in so far as if
292
// firstLineOffset is non 0, there should only be one
293
// line of text.
294
x -= firstLineOffset;
295             }
296         }
297     }
298
299     /**
300      * Should return a shape ideal for painting based on the passed in
301      * Shape <code>a</code>. This is useful if painting in a different
302      * region. The default implementation returns <code>a</code>.
303      */

304     Shape adjustPaintRegion(Shape a) {
305     return a;
306     }
307
308     /**
309      * Provides a mapping from the document model coordinate space
310      * to the coordinate space of the view mapped to it.
311      *
312      * @param pos the position to convert >= 0
313      * @param a the allocated region to render into
314      * @return the bounding box of the given position
315      * @exception BadLocationException if the given position does not
316      * represent a valid location in the associated document
317      * @see View#modelToView
318      */

319     public Shape modelToView(int pos, Shape a, Position.Bias JavaDoc b) throws BadLocationException JavaDoc {
320         // line coordinates
321
Document JavaDoc doc = getDocument();
322         Element JavaDoc map = getElement();
323         int lineIndex = map.getElementIndex(pos);
324         Rectangle lineArea = lineToRect(a, lineIndex);
325         
326         // determine span from the start of the line
327
tabBase = lineArea.x;
328         Element JavaDoc line = map.getElement(lineIndex);
329         int p0 = line.getStartOffset();
330         Segment JavaDoc s = SegmentCache.getSharedSegment();
331         doc.getText(p0, pos - p0, s);
332         int xOffs = Utilities.getTabbedTextWidth(s, metrics, tabBase, this,p0);
333         SegmentCache.releaseSharedSegment(s);
334
335         // fill in the results and return
336
lineArea.x += xOffs;
337         lineArea.width = 1;
338         lineArea.height = metrics.getHeight();
339         return lineArea;
340     }
341
342     /**
343      * Provides a mapping from the view coordinate space to the logical
344      * coordinate space of the model.
345      *
346      * @param fx the X coordinate >= 0
347      * @param fy the Y coordinate >= 0
348      * @param a the allocated region to render into
349      * @return the location within the model that best represents the
350      * given point in the view >= 0
351      * @see View#viewToModel
352      */

353     public int viewToModel(float fx, float fy, Shape a, Position.Bias JavaDoc[] bias) {
354     // PENDING(prinz) properly calculate bias
355
bias[0] = Position.Bias.Forward;
356
357         Rectangle alloc = a.getBounds();
358         Document JavaDoc doc = getDocument();
359         int x = (int) fx;
360         int y = (int) fy;
361         if (y < alloc.y) {
362             // above the area covered by this icon, so the the position
363
// is assumed to be the start of the coverage for this view.
364
return getStartOffset();
365         } else if (y > alloc.y + alloc.height) {
366             // below the area covered by this icon, so the the position
367
// is assumed to be the end of the coverage for this view.
368
return getEndOffset() - 1;
369         } else {
370             // positioned within the coverage of this view vertically,
371
// so we figure out which line the point corresponds to.
372
// if the line is greater than the number of lines contained, then
373
// simply use the last line as it represents the last possible place
374
// we can position to.
375
Element JavaDoc map = doc.getDefaultRootElement();
376             int lineIndex = Math.abs((y - alloc.y) / metrics.getHeight() );
377             if (lineIndex >= map.getElementCount()) {
378                 return getEndOffset() - 1;
379             }
380             Element JavaDoc line = map.getElement(lineIndex);
381             int dx = 0;
382             if (lineIndex == 0) {
383                 alloc.x += firstLineOffset;
384                 alloc.width -= firstLineOffset;
385             }
386             if (x < alloc.x) {
387                 // point is to the left of the line
388
return line.getStartOffset();
389             } else if (x > alloc.x + alloc.width) {
390                 // point is to the right of the line
391
return line.getEndOffset() - 1;
392             } else {
393                 // Determine the offset into the text
394
try {
395                     int p0 = line.getStartOffset();
396                     int p1 = line.getEndOffset() - 1;
397                     Segment JavaDoc s = SegmentCache.getSharedSegment();
398                     doc.getText(p0, p1 - p0, s);
399                     tabBase = alloc.x;
400                     int offs = p0 + Utilities.getTabbedTextOffset(s, metrics,
401                                                                   tabBase, x, this, p0);
402                     SegmentCache.releaseSharedSegment(s);
403                     return offs;
404                 } catch (BadLocationException JavaDoc e) {
405                     // should not happen
406
return -1;
407                 }
408             }
409         }
410     }
411
412     /**
413      * Gives notification that something was inserted into the document
414      * in a location that this view is responsible for.
415      *
416      * @param changes the change information from the associated document
417      * @param a the current allocation of the view
418      * @param f the factory to use to rebuild if the view has children
419      * @see View#insertUpdate
420      */

421     public void insertUpdate(DocumentEvent changes, Shape a, ViewFactory JavaDoc f) {
422     updateDamage(changes, a, f);
423     }
424
425     /**
426      * Gives notification that something was removed from the document
427      * in a location that this view is responsible for.
428      *
429      * @param changes the change information from the associated document
430      * @param a the current allocation of the view
431      * @param f the factory to use to rebuild if the view has children
432      * @see View#removeUpdate
433      */

434     public void removeUpdate(DocumentEvent changes, Shape a, ViewFactory JavaDoc f) {
435         updateDamage(changes, a, f);
436     }
437
438     /**
439      * Gives notification from the document that attributes were changed
440      * in a location that this view is responsible for.
441      *
442      * @param changes the change information from the associated document
443      * @param a the current allocation of the view
444      * @param f the factory to use to rebuild if the view has children
445      * @see View#changedUpdate
446      */

447     public void changedUpdate(DocumentEvent changes, Shape a, ViewFactory JavaDoc f) {
448         updateDamage(changes, a, f);
449     }
450
451     /**
452      * Sets the size of the view. This should cause
453      * layout of the view along the given axis, if it
454      * has any layout duties.
455      *
456      * @param width the width >= 0
457      * @param height the height >= 0
458      */

459     public void setSize(float width, float height) {
460         super.setSize(width, height);
461         updateMetrics();
462     }
463
464     // --- TabExpander methods ------------------------------------------
465

466     /**
467      * Returns the next tab stop position after a given reference position.
468      * This implementation does not support things like centering so it
469      * ignores the tabOffset argument.
470      *
471      * @param x the current position >= 0
472      * @param tabOffset the position within the text stream
473      * that the tab occurred at >= 0.
474      * @return the tab stop, measured in points >= 0
475      */

476     public float nextTabStop(float x, int tabOffset) {
477     if (tabSize == 0) {
478         return x;
479     }
480         int ntabs = (((int) x) - tabBase) / tabSize;
481         return tabBase + ((ntabs + 1) * tabSize);
482     }
483     
484     // --- local methods ------------------------------------------------
485

486     /*
487      * Repaint the region of change covered by the given document
488      * event. Damages the line that begins the range to cover
489      * the case when the insert/remove is only on one line.
490      * If lines are added or removed, damages the whole
491      * view. The longest line is checked to see if it has
492      * changed.
493      *
494      * @since 1.4
495      */

496     protected void updateDamage(DocumentEvent changes, Shape a, ViewFactory JavaDoc f) {
497     Component host = getContainer();
498     updateMetrics();
499     Element JavaDoc elem = getElement();
500     DocumentEvent.ElementChange ec = changes.getChange(elem);
501     
502     Element JavaDoc[] added = (ec != null) ? ec.getChildrenAdded() : null;
503     Element JavaDoc[] removed = (ec != null) ? ec.getChildrenRemoved() : null;
504     if (((added != null) && (added.length > 0)) ||
505         ((removed != null) && (removed.length > 0))) {
506         // lines were added or removed...
507
if (added != null) {
508         int currWide = getLineWidth(longLine);
509         for (int i = 0; i < added.length; i++) {
510             int w = getLineWidth(added[i]);
511             if (w > currWide) {
512             currWide = w;
513             longLine = added[i];
514             }
515         }
516         }
517         if (removed != null) {
518         for (int i = 0; i < removed.length; i++) {
519             if (removed[i] == longLine) {
520             calculateLongestLine();
521             break;
522             }
523         }
524         }
525         preferenceChanged(null, true, true);
526         host.repaint();
527     } else {
528         Element JavaDoc map = getElement();
529         int line = map.getElementIndex(changes.getOffset());
530         damageLineRange(line, line, a, host);
531         if (changes.getType() == DocumentEvent.EventType.INSERT) {
532         // check to see if the line is longer than current
533
// longest line.
534
int w = getLineWidth(longLine);
535         Element JavaDoc e = map.getElement(line);
536         if (e == longLine) {
537             preferenceChanged(null, true, false);
538         } else if (getLineWidth(e) > w) {
539             longLine = e;
540             preferenceChanged(null, true, false);
541         }
542         } else if (changes.getType() == DocumentEvent.EventType.REMOVE) {
543         if (map.getElement(line) == longLine) {
544             // removed from longest line... recalc
545
calculateLongestLine();
546             preferenceChanged(null, true, false);
547         }
548         }
549     }
550     }
551
552     /**
553      * Repaint the given line range.
554      *
555      * @param host the component hosting the view (used to call repaint)
556      * @param a the region allocated for the view to render into
557      * @param line0 the starting line number to repaint. This must
558      * be a valid line number in the model.
559      * @param line1 the ending line number to repaint. This must
560      * be a valid line number in the model.
561      * @since 1.4
562      */

563     protected void damageLineRange(int line0, int line1, Shape a, Component host) {
564         if (a != null) {
565             Rectangle area0 = lineToRect(a, line0);
566             Rectangle area1 = lineToRect(a, line1);
567             if ((area0 != null) && (area1 != null)) {
568                 Rectangle damage = area0.union(area1);
569                 host.repaint(damage.x, damage.y, damage.width, damage.height);
570             } else {
571                 host.repaint();
572             }
573         }
574     }
575
576     /**
577      * Determine the rectangle that represents the given line.
578      *
579      * @param a the region allocated for the view to render into
580      * @param line the line number to find the region of. This must
581      * be a valid line number in the model.
582      * @since 1.4
583      */

584     protected Rectangle lineToRect(Shape a, int line) {
585         Rectangle r = null;
586     updateMetrics();
587         if (metrics != null) {
588             Rectangle alloc = a.getBounds();
589             if (line == 0) {
590                 alloc.x += firstLineOffset;
591                 alloc.width -= firstLineOffset;
592             }
593             r = new Rectangle(alloc.x, alloc.y + (line * metrics.getHeight()),
594                               alloc.width, metrics.getHeight());
595         }
596         return r;
597     }
598
599     /**
600      * Iterate over the lines represented by the child elements
601      * of the element this view represents, looking for the line
602      * that is the longest. The <em>longLine</em> variable is updated to
603      * represent the longest line contained. The <em>font</em> variable
604      * is updated to indicate the font used to calculate the
605      * longest line.
606      */

607     private void calculateLongestLine() {
608     Component c = getContainer();
609     font = c.getFont();
610     metrics = c.getFontMetrics(font);
611     Document JavaDoc doc = getDocument();
612     Element JavaDoc lines = getElement();
613     int n = lines.getElementCount();
614     int maxWidth = -1;
615     for (int i = 0; i < n; i++) {
616         Element JavaDoc line = lines.getElement(i);
617         int w = getLineWidth(line);
618         if (w > maxWidth) {
619         maxWidth = w;
620         longLine = line;
621         }
622     }
623     }
624
625     /**
626      * Calculate the width of the line represented by
627      * the given element. It is assumed that the font
628      * and font metrics are up-to-date.
629      */

630     private int getLineWidth(Element JavaDoc line) {
631     int p0 = line.getStartOffset();
632     int p1 = line.getEndOffset();
633     int w;
634         Segment JavaDoc s = SegmentCache.getSharedSegment();
635     try {
636         line.getDocument().getText(p0, p1 - p0, s);
637         w = Utilities.getTabbedTextWidth(s, metrics, tabBase, this, p0);
638     } catch (BadLocationException JavaDoc ble) {
639         w = 0;
640     }
641         SegmentCache.releaseSharedSegment(s);
642     return w;
643     }
644
645     // --- member variables -----------------------------------------------
646

647     /**
648      * Font metrics for the current font.
649      */

650     protected FontMetrics metrics;
651
652     /**
653      * The current longest line. This is used to calculate
654      * the preferred width of the view. Since the calculation
655      * is potentially expensive we try to avoid it by stashing
656      * which line is currently the longest.
657      */

658     Element JavaDoc longLine;
659
660     /**
661      * Font used to calculate the longest line... if this
662      * changes we need to recalculate the longest line
663      */

664     Font font;
665
666     Segment JavaDoc lineBuffer;
667     int tabSize;
668     int tabBase;
669     
670     int sel0;
671     int sel1;
672     Color unselected;
673     Color selected;
674
675     /**
676      * Offset of where to draw the first character on the first line.
677      * This is a hack and temporary until we can better address the problem
678      * of text measuring. This field is actually never set directly in
679      * PlainView, but by FieldView.
680      */

681     int firstLineOffset;
682     
683 }
684
Popular Tags