KickJava   Java API By Example, From Geeks To Geeks.

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


1 /*
2  * @(#)ParagraphView.java 1.94 06/04/10
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.Arrays JavaDoc;
10 import java.awt.*;
11 import java.awt.font.TextAttribute JavaDoc;
12 import javax.swing.event.*;
13 import javax.swing.SizeRequirements JavaDoc;
14
15 /**
16  * View of a simple line-wrapping paragraph that supports
17  * multiple fonts, colors, components, icons, etc. It is
18  * basically a vertical box with a margin around it. The
19  * contents of the box are a bunch of rows which are special
20  * horizontal boxes. This view creates a collection of
21  * views that represent the child elements of the paragraph
22  * element. Each of these views are placed into a row
23  * directly if they will fit, otherwise the <code>breakView</code>
24  * method is called to try and carve the view into pieces
25  * that fit.
26  *
27  * @author Timothy Prinzing
28  * @author Scott Violet
29  * @author Igor Kushnirskiy
30  * @version 1.94 04/10/06
31  * @see View
32  */

33 public class ParagraphView extends FlowView JavaDoc implements TabExpander JavaDoc {
34
35     /**
36      * Constructs a <code>ParagraphView</code> for the given element.
37      *
38      * @param elem the element that this view is responsible for
39      */

40     public ParagraphView(Element JavaDoc elem) {
41     super(elem, View.Y_AXIS);
42     setPropertiesFromAttributes();
43     Document JavaDoc doc = elem.getDocument();
44     Object JavaDoc i18nFlag = doc.getProperty(AbstractDocument.I18NProperty);
45     if ((i18nFlag != null) && i18nFlag.equals(Boolean.TRUE)) {
46         try {
47         if (i18nStrategy == null) {
48             // the classname should probably come from a property file.
49
String JavaDoc classname = "javax.swing.text.TextLayoutStrategy";
50             ClassLoader JavaDoc loader = getClass().getClassLoader();
51             if (loader != null) {
52             i18nStrategy = loader.loadClass(classname);
53             } else {
54             i18nStrategy = Class.forName(classname);
55             }
56         }
57         Object JavaDoc o = i18nStrategy.newInstance();
58         if (o instanceof FlowStrategy) {
59             strategy = (FlowStrategy) o;
60         }
61         } catch (Throwable JavaDoc e) {
62         throw new StateInvariantError JavaDoc("ParagraphView: Can't create i18n strategy: "
63                           + e.getMessage());
64         }
65     }
66     }
67
68     /**
69      * Sets the type of justification.
70      *
71      * @param j one of the following values:
72      * <ul>
73      * <li><code>StyleConstants.ALIGN_LEFT</code>
74      * <li><code>StyleConstants.ALIGN_CENTER</code>
75      * <li><code>StyleConstants.ALIGN_RIGHT</code>
76      * </ul>
77      */

78     protected void setJustification(int j) {
79     justification = j;
80     }
81
82     /**
83      * Sets the line spacing.
84      *
85      * @param ls the value is a factor of the line hight
86      */

87     protected void setLineSpacing(float ls) {
88     lineSpacing = ls;
89     }
90
91     /**
92      * Sets the indent on the first line.
93      *
94      * @param fi the value in points
95      */

96     protected void setFirstLineIndent(float fi) {
97     firstLineIndent = (int) fi;
98     }
99
100     /**
101      * Set the cached properties from the attributes.
102      */

103     protected void setPropertiesFromAttributes() {
104     AttributeSet JavaDoc attr = getAttributes();
105     if (attr != null) {
106         setParagraphInsets(attr);
107         Integer JavaDoc a = (Integer JavaDoc)attr.getAttribute(StyleConstants.Alignment);
108         int alignment;
109         if (a == null) {
110         Document JavaDoc doc = getElement().getDocument();
111         Object JavaDoc o = doc.getProperty(TextAttribute.RUN_DIRECTION);
112         if ((o != null) && o.equals(TextAttribute.RUN_DIRECTION_RTL)) {
113             alignment = StyleConstants.ALIGN_RIGHT;
114         } else {
115             alignment = StyleConstants.ALIGN_LEFT;
116         }
117         } else {
118         alignment = a.intValue();
119         }
120         setJustification(alignment);
121         setLineSpacing(StyleConstants.getLineSpacing(attr));
122         setFirstLineIndent(StyleConstants.getFirstLineIndent(attr));
123     }
124     }
125
126     /**
127      * Returns the number of views that this view is
128      * responsible for.
129      * The child views of the paragraph are rows which
130      * have been used to arrange pieces of the <code>View</code>s
131      * that represent the child elements. This is the number
132      * of views that have been tiled in two dimensions,
133      * and should be equivalent to the number of child elements
134      * to the element this view is responsible for.
135      *
136      * @return the number of views that this <code>ParagraphView</code>
137      * is responsible for
138      */

139     protected int getLayoutViewCount() {
140     return layoutPool.getViewCount();
141     }
142
143     /**
144      * Returns the view at a given <code>index</code>.
145      * The child views of the paragraph are rows which
146      * have been used to arrange pieces of the <code>Views</code>
147      * that represent the child elements. This methods returns
148      * the view responsible for the child element index
149      * (prior to breaking). These are the Views that were
150      * produced from a factory (to represent the child
151      * elements) and used for layout.
152      *
153      * @param index the <code>index</code> of the desired view
154      * @return the view at <code>index</code>
155      */

156     protected View JavaDoc getLayoutView(int index) {
157     return layoutPool.getView(index);
158     }
159
160     /**
161      * Adjusts the given row if possible to fit within the
162      * layout span. By default this will try to find the
163      * highest break weight possible nearest the end of
164      * the row. If a forced break is encountered, the
165      * break will be positioned there.
166      * <p>
167      * This is meant for internal usage, and should not be used directly.
168      *
169      * @param r the row to adjust to the current layout
170      * span
171      * @param desiredSpan the current layout span >= 0
172      * @param x the location r starts at
173      */

174     protected void adjustRow(Row r, int desiredSpan, int x) {
175     }
176
177     /**
178      * Returns the next visual position for the cursor, in
179      * either the east or west direction.
180      * Overridden from <code>CompositeView</code>.
181      * @param pos position into the model
182      * @param b either <code>Position.Bias.Forward</code> or
183      * <code>Position.Bias.Backward</code>
184      * @param a the allocated region to render into
185      * @param direction either <code>SwingConstants.NORTH</code>
186      * or <code>SwingConstants.SOUTH</code>
187      * @param biasRet an array containing the bias that were checked
188      * in this method
189      * @return the location in the model that represents the
190      * next location visual position
191      */

192     protected int getNextNorthSouthVisualPositionFrom(int pos, Position.Bias JavaDoc b,
193                               Shape a, int direction,
194                               Position.Bias JavaDoc[] biasRet)
195                                             throws BadLocationException JavaDoc {
196     int vIndex;
197     if(pos == -1) {
198         vIndex = (direction == NORTH) ?
199              getViewCount() - 1 : 0;
200     }
201     else {
202         if(b == Position.Bias.Backward && pos > 0) {
203         vIndex = getViewIndexAtPosition(pos - 1);
204         }
205         else {
206         vIndex = getViewIndexAtPosition(pos);
207         }
208         if(direction == NORTH) {
209         if(vIndex == 0) {
210             return -1;
211         }
212         vIndex--;
213         }
214         else if(++vIndex >= getViewCount()) {
215         return -1;
216         }
217     }
218     // vIndex gives index of row to look in.
219
JTextComponent JavaDoc text = (JTextComponent JavaDoc)getContainer();
220     Caret JavaDoc c = text.getCaret();
221     Point magicPoint;
222     magicPoint = (c != null) ? c.getMagicCaretPosition() : null;
223     int x;
224     if(magicPoint == null) {
225         Shape posBounds;
226         try {
227         posBounds = text.getUI().modelToView(text, pos, b);
228         } catch (BadLocationException JavaDoc exc) {
229         posBounds = null;
230         }
231         if(posBounds == null) {
232         x = 0;
233         }
234         else {
235         x = posBounds.getBounds().x;
236         }
237     }
238     else {
239         x = magicPoint.x;
240     }
241     return getClosestPositionTo(pos, b, a, direction, biasRet, vIndex, x);
242     }
243
244     /**
245      * Returns the closest model position to <code>x</code>.
246      * <code>rowIndex</code> gives the index of the view that corresponds
247      * that should be looked in.
248      * @param pos position into the model
249      * @param a the allocated region to render into
250      * @param direction one of the following values:
251      * <ul>
252      * <li><code>SwingConstants.NORTH</code>
253      * <li><code>SwingConstants.SOUTH</code>
254      * </ul>
255      * @param biasRet an array containing the bias that were checked
256      * in this method
257      * @param rowIndex the index of the view
258      * @param x the x coordinate of interest
259      * @return the closest model position to <code>x</code>
260      */

261     // NOTE: This will not properly work if ParagraphView contains
262
// other ParagraphViews. It won't raise, but this does not message
263
// the children views with getNextVisualPositionFrom.
264
protected int getClosestPositionTo(int pos, Position.Bias JavaDoc b, Shape a,
265                                        int direction, Position.Bias JavaDoc[] biasRet,
266                                        int rowIndex, int x)
267               throws BadLocationException JavaDoc {
268         JTextComponent JavaDoc text = (JTextComponent JavaDoc)getContainer();
269         Document JavaDoc doc = getDocument();
270         AbstractDocument JavaDoc aDoc = (doc instanceof AbstractDocument JavaDoc) ?
271                                 (AbstractDocument JavaDoc)doc : null;
272         View JavaDoc row = getView(rowIndex);
273         int lastPos = -1;
274         // This could be made better to check backward positions too.
275
biasRet[0] = Position.Bias.Forward;
276         for(int vc = 0, numViews = row.getViewCount(); vc < numViews; vc++) {
277             View JavaDoc v = row.getView(vc);
278             int start = v.getStartOffset();
279             boolean ltr = (aDoc != null) ? aDoc.isLeftToRight
280                            (start, start + 1) : true;
281             if(ltr) {
282                 lastPos = start;
283                 for(int end = v.getEndOffset(); lastPos < end; lastPos++) {
284                     float xx = text.modelToView(lastPos).getBounds().x;
285                     if(xx >= x) {
286                         while (++lastPos < end &&
287                                text.modelToView(lastPos).getBounds().x == xx) {
288                         }
289                         return --lastPos;
290                     }
291                 }
292                 lastPos--;
293             }
294             else {
295                 for(lastPos = v.getEndOffset() - 1; lastPos >= start;
296                     lastPos--) {
297                     float xx = text.modelToView(lastPos).getBounds().x;
298                     if(xx >= x) {
299                         while (--lastPos >= start &&
300                                text.modelToView(lastPos).getBounds().x == xx) {
301                         }
302                         return ++lastPos;
303                     }
304                 }
305                 lastPos++;
306             }
307         }
308         if(lastPos == -1) {
309             return getStartOffset();
310         }
311         return lastPos;
312     }
313
314     /**
315      * Determines in which direction the next view lays.
316      * Consider the <code>View</code> at index n.
317      * Typically the <code>View</code>s are layed out
318      * from left to right, so that the <code>View</code>
319      * to the EAST will be at index n + 1, and the
320      * <code>View</code> to the WEST will be at index n - 1.
321      * In certain situations, such as with bidirectional text,
322      * it is possible that the <code>View</code> to EAST is not
323      * at index n + 1, but rather at index n - 1,
324      * or that the <code>View</code> to the WEST is not at
325      * index n - 1, but index n + 1. In this case this method
326      * would return true, indicating the <code>View</code>s are
327      * layed out in descending order.
328      * <p>
329      * This will return true if the text is layed out right
330      * to left at position, otherwise false.
331      *
332      * @param position position into the model
333      * @param bias either <code>Position.Bias.Forward</code> or
334      * <code>Position.Bias.Backward</code>
335      * @return true if the text is layed out right to left at
336      * position, otherwise false.
337      */

338     protected boolean flipEastAndWestAtEnds(int position,
339                         Position.Bias JavaDoc bias) {
340     Document JavaDoc doc = getDocument();
341     if(doc instanceof AbstractDocument JavaDoc &&
342        !((AbstractDocument JavaDoc)doc).isLeftToRight(getStartOffset(),
343                           getStartOffset() + 1)) {
344         return true;
345     }
346     return false;
347     }
348
349     // --- FlowView methods ---------------------------------------------
350

351     /**
352      * Fetches the constraining span to flow against for
353      * the given child index.
354      * @param index the index of the view being queried
355      * @return the constraining span for the given view at
356      * <code>index</code>
357      */

358     public int getFlowSpan(int index) {
359     View JavaDoc child = getView(index);
360     int adjust = 0;
361     if (child instanceof Row) {
362         Row row = (Row) child;
363         adjust = row.getLeftInset() + row.getRightInset();
364     }
365     int span = layoutSpan - adjust;
366     return span;
367     }
368
369     /**
370      * Fetches the location along the flow axis that the
371      * flow span will start at.
372      * @param index the index of the view being queried
373      * @return the location for the given view at
374      * <code>index</code>
375      */

376     public int getFlowStart(int index) {
377     View JavaDoc child = getView(index);
378     int adjust = 0;
379     if (child instanceof Row) {
380         Row row = (Row) child;
381         adjust = row.getLeftInset();
382     }
383     return tabBase + adjust;
384     }
385
386     /**
387      * Create a <code>View</code> that should be used to hold a
388      * a row's worth of children in a flow.
389      * @return the new <code>View</code>
390      */

391     protected View JavaDoc createRow() {
392     return new Row(getElement());
393     }
394     
395     // --- TabExpander methods ------------------------------------------
396

397     /**
398      * Returns the next tab stop position given a reference position.
399      * This view implements the tab coordinate system, and calls
400      * <code>getTabbedSpan</code> on the logical children in the process
401      * of layout to determine the desired span of the children. The
402      * logical children can delegate their tab expansion upward to
403      * the paragraph which knows how to expand tabs.
404      * <code>LabelView</code> is an example of a view that delegates
405      * its tab expansion needs upward to the paragraph.
406      * <p>
407      * This is implemented to try and locate a <code>TabSet</code>
408      * in the paragraph element's attribute set. If one can be
409      * found, its settings will be used, otherwise a default expansion
410      * will be provided. The base location for for tab expansion
411      * is the left inset from the paragraphs most recent allocation
412      * (which is what the layout of the children is based upon).
413      *
414      * @param x the X reference position
415      * @param tabOffset the position within the text stream
416      * that the tab occurred at >= 0
417      * @return the trailing end of the tab expansion >= 0
418      * @see TabSet
419      * @see TabStop
420      * @see LabelView
421      */

422     public float nextTabStop(float x, int tabOffset) {
423     // If the text isn't left justified, offset by 10 pixels!
424
if(justification != StyleConstants.ALIGN_LEFT)
425             return x + 10.0f;
426         x -= tabBase;
427         TabSet JavaDoc tabs = getTabSet();
428         if(tabs == null) {
429             // a tab every 72 pixels.
430
return (float)(tabBase + (((int)x / 72 + 1) * 72));
431         }
432         TabStop JavaDoc tab = tabs.getTabAfter(x + .01f);
433         if(tab == null) {
434             // no tab, do a default of 5 pixels.
435
// Should this cause a wrapping of the line?
436
return tabBase + x + 5.0f;
437         }
438         int alignment = tab.getAlignment();
439         int offset;
440         switch(alignment) {
441         default:
442         case TabStop.ALIGN_LEFT:
443             // Simple case, left tab.
444
return tabBase + tab.getPosition();
445         case TabStop.ALIGN_BAR:
446             // PENDING: what does this mean?
447
return tabBase + tab.getPosition();
448         case TabStop.ALIGN_RIGHT:
449         case TabStop.ALIGN_CENTER:
450             offset = findOffsetToCharactersInString(tabChars,
451                                                     tabOffset + 1);
452             break;
453         case TabStop.ALIGN_DECIMAL:
454             offset = findOffsetToCharactersInString(tabDecimalChars,
455                                                     tabOffset + 1);
456             break;
457         }
458         if (offset == -1) {
459             offset = getEndOffset();
460         }
461         float charsSize = getPartialSize(tabOffset + 1, offset);
462         switch(alignment) {
463         case TabStop.ALIGN_RIGHT:
464         case TabStop.ALIGN_DECIMAL:
465             // right and decimal are treated the same way, the new
466
// position will be the location of the tab less the
467
// partialSize.
468
return tabBase + Math.max(x, tab.getPosition() - charsSize);
469         case TabStop.ALIGN_CENTER:
470             // Similar to right, but half the partialSize.
471
return tabBase + Math.max(x, tab.getPosition() - charsSize / 2.0f);
472         }
473         // will never get here!
474
return x;
475     }
476
477     /**
478      * Gets the <code>Tabset</code> to be used in calculating tabs.
479      *
480      * @return the <code>TabSet</code>
481      */

482     protected TabSet JavaDoc getTabSet() {
483     return StyleConstants.getTabSet(getElement().getAttributes());
484     }
485
486     /**
487      * Returns the size used by the views between
488      * <code>startOffset</code> and <code>endOffset</code>.
489      * This uses <code>getPartialView</code> to calculate the
490      * size if the child view implements the
491      * <code>TabableView</code> interface. If a
492      * size is needed and a <code>View</code> does not implement
493      * the <code>TabableView</code> interface,
494      * the <code>preferredSpan</code> will be used.
495      *
496      * @param startOffset the starting document offset >= 0
497      * @param endOffset the ending document offset >= startOffset
498      * @return the size >= 0
499      */

500     protected float getPartialSize(int startOffset, int endOffset) {
501         float size = 0.0f;
502         int viewIndex;
503         int numViews = getViewCount();
504         View JavaDoc view;
505         int viewEnd;
506         int tempEnd;
507
508         // Have to search layoutPool!
509
// PENDING: when ParagraphView supports breaking location
510
// into layoutPool will have to change!
511
viewIndex = getElement().getElementIndex(startOffset);
512         numViews = layoutPool.getViewCount();
513         while(startOffset < endOffset && viewIndex < numViews) {
514             view = layoutPool.getView(viewIndex++);
515             viewEnd = view.getEndOffset();
516             tempEnd = Math.min(endOffset, viewEnd);
517             if(view instanceof TabableView JavaDoc)
518                 size += ((TabableView JavaDoc)view).getPartialSpan(startOffset, tempEnd);
519             else if(startOffset == view.getStartOffset() &&
520                     tempEnd == view.getEndOffset())
521                 size += view.getPreferredSpan(View.X_AXIS);
522             else
523                 // PENDING: should we handle this better?
524
return 0.0f;
525             startOffset = viewEnd;
526         }
527         return size;
528     }
529
530     /**
531      * Finds the next character in the document with a character in
532      * <code>string</code>, starting at offset <code>start</code>. If
533      * there are no characters found, -1 will be returned.
534      *
535      * @param string the string of characters
536      * @param start where to start in the model >= 0
537      * @return the document offset, or -1 if no characters found
538      */

539     protected int findOffsetToCharactersInString(char[] string,
540                                                  int start) {
541         int stringLength = string.length;
542         int end = getEndOffset();
543         Segment JavaDoc seg = new Segment JavaDoc();
544         try {
545             getDocument().getText(start, end - start, seg);
546         } catch (BadLocationException JavaDoc ble) {
547             return -1;
548         }
549         for(int counter = seg.offset, maxCounter = seg.offset + seg.count;
550             counter < maxCounter; counter++) {
551             char currentChar = seg.array[counter];
552             for(int subCounter = 0; subCounter < stringLength;
553                 subCounter++) {
554                 if(currentChar == string[subCounter])
555                     return counter - seg.offset + start;
556             }
557         }
558         // No match.
559
return -1;
560     }
561
562     /**
563      * Returns where the tabs are calculated from.
564      * @return where tabs are calculated from
565      */

566     protected float getTabBase() {
567     return (float)tabBase;
568     }
569
570     // ---- View methods ----------------------------------------------------
571

572     /**
573      * Renders using the given rendering surface and area on that
574      * surface. This is implemented to delgate to the superclass
575      * after stashing the base coordinate for tab calculations.
576      *
577      * @param g the rendering surface to use
578      * @param a the allocated region to render into
579      * @see View#paint
580      */

581     public void paint(Graphics g, Shape a) {
582         Rectangle alloc = (a instanceof Rectangle) ? (Rectangle)a : a.getBounds();
583         tabBase = alloc.x + getLeftInset();
584     super.paint(g, a);
585
586     // line with the negative firstLineIndent value needs
587
// special handling
588
if (firstLineIndent < 0) {
589         Shape sh = getChildAllocation(0, a);
590         if ((sh != null) && sh.intersects(alloc)) {
591         int x = alloc.x + getLeftInset() + firstLineIndent;
592         int y = alloc.y + getTopInset();
593                                                                               
594         Rectangle clip = g.getClipBounds();
595         tempRect.x = x + getOffset(X_AXIS, 0);
596         tempRect.y = y + getOffset(Y_AXIS, 0);
597         tempRect.width = getSpan(X_AXIS, 0) - firstLineIndent;
598         tempRect.height = getSpan(Y_AXIS, 0);
599         if (tempRect.intersects(clip)) {
600             tempRect.x = tempRect.x - firstLineIndent;
601             paintChild(g, tempRect, 0);
602         }
603         }
604     }
605     }
606
607     /**
608      * Determines the desired alignment for this view along an
609      * axis. This is implemented to give the alignment to the
610      * center of the first row along the y axis, and the default
611      * along the x axis.
612      *
613      * @param axis may be either <code>View.X_AXIS</code> or
614      * <code>View.Y_AXIS</code>
615      * @return the desired alignment. This should be a value
616      * between 0.0 and 1.0 inclusive, where 0 indicates alignment at the
617      * origin and 1.0 indicates alignment to the full span
618      * away from the origin. An alignment of 0.5 would be the
619      * center of the view.
620      */

621     public float getAlignment(int axis) {
622         switch (axis) {
623         case Y_AXIS:
624         float a = 0.5f;
625         if (getViewCount() != 0) {
626         int paragraphSpan = (int) getPreferredSpan(View.Y_AXIS);
627         View JavaDoc v = getView(0);
628         int rowSpan = (int) v.getPreferredSpan(View.Y_AXIS);
629         a = (paragraphSpan != 0) ? ((float)(rowSpan / 2)) / paragraphSpan : 0;
630         }
631             return a;
632     case X_AXIS:
633         return 0.5f;
634     default:
635             throw new IllegalArgumentException JavaDoc("Invalid axis: " + axis);
636     }
637     }
638
639     /**
640      * Breaks this view on the given axis at the given length.
641      * <p>
642      * <code>ParagraphView</code> instances are breakable
643      * along the <code>Y_AXIS</code> only, and only if
644      * <code>len</code> is after the first line.
645      *
646      * @param axis may be either <code>View.X_AXIS</code>
647      * or <code>View.Y_AXIS</code>
648      * @param len specifies where a potential break is desired
649      * along the given axis >= 0
650      * @param a the current allocation of the view
651      * @return the fragment of the view that represents the
652      * given span, if the view can be broken; if the view
653      * doesn't support breaking behavior, the view itself is
654      * returned
655      * @see View#breakView
656      */

657     public View JavaDoc breakView(int axis, float len, Shape a) {
658         if(axis == View.Y_AXIS) {
659             if(a != null) {
660                 Rectangle alloc = a.getBounds();
661                 setSize(alloc.width, alloc.height);
662             }
663             // Determine what row to break on.
664

665             // PENDING(prinz) add break support
666
return this;
667         }
668         return this;
669     }
670
671     /**
672      * Gets the break weight for a given location.
673      * <p>
674      * <code>ParagraphView</code> instances are breakable
675      * along the <code>Y_AXIS</code> only, and only if
676      * <code>len</code> is after the first row. If the length
677      * is less than one row, a value of <code>BadBreakWeight</code>
678      * is returned.
679      *
680      * @param axis may be either <code>View.X_AXIS</code>
681      * or <code>View.Y_AXIS</code>
682      * @param len specifies where a potential break is desired >= 0
683      * @return a value indicating the attractiveness of breaking here;
684      * either <code>GoodBreakWeight</code> or <code>BadBreakWeight</code>
685      * @see View#getBreakWeight
686      */

687     public int getBreakWeight(int axis, float len) {
688         if(axis == View.Y_AXIS) {
689             // PENDING(prinz) make this return a reasonable value
690
// when paragraph breaking support is re-implemented.
691
// If less than one row, bad weight value should be
692
// returned.
693
//return GoodBreakWeight;
694
return BadBreakWeight;
695         }
696         return BadBreakWeight;
697     }
698
699     /**
700      * Gives notification from the document that attributes were changed
701      * in a location that this view is responsible for.
702      *
703      * @param changes the change information from the
704      * associated document
705      * @param a the current allocation of the view
706      * @param f the factory to use to rebuild if the view has children
707      * @see View#changedUpdate
708      */

709     public void changedUpdate(DocumentEvent changes, Shape a, ViewFactory JavaDoc f) {
710         // update any property settings stored, and layout should be
711
// recomputed
712
setPropertiesFromAttributes();
713     layoutChanged(X_AXIS);
714     layoutChanged(Y_AXIS);
715     super.changedUpdate(changes, a, f);
716     }
717
718     
719     // --- variables -----------------------------------------------
720

721     private int justification;
722     private float lineSpacing;
723     /** Indentation for the first line, from the left inset. */
724     protected int firstLineIndent = 0;
725
726     /**
727      * Used by the TabExpander functionality to determine
728      * where to base the tab calculations. This is basically
729      * the location of the left side of the paragraph.
730      */

731     private int tabBase;
732
733     /**
734      * Used to create an i18n-based layout strategy
735      */

736     static Class JavaDoc i18nStrategy;
737
738     /** Used for searching for a tab. */
739     static char[] tabChars;
740     /** Used for searching for a tab or decimal character. */
741     static char[] tabDecimalChars;
742
743     static {
744         tabChars = new char[1];
745         tabChars[0] = '\t';
746         tabDecimalChars = new char[2];
747         tabDecimalChars[0] = '\t';
748         tabDecimalChars[1] = '.';
749     }
750
751     /**
752      * Internally created view that has the purpose of holding
753      * the views that represent the children of the paragraph
754      * that have been arranged in rows.
755      */

756     class Row extends BoxView JavaDoc {
757
758         Row(Element JavaDoc elem) {
759             super(elem, View.X_AXIS);
760         }
761
762         /**
763          * This is reimplemented to do nothing since the
764          * paragraph fills in the row with its needed
765          * children.
766          */

767         protected void loadChildren(ViewFactory JavaDoc f) {
768         }
769
770     /**
771      * Fetches the attributes to use when rendering. This view
772      * isn't directly responsible for an element so it returns
773      * the outer classes attributes.
774      */

775         public AttributeSet JavaDoc getAttributes() {
776         View JavaDoc p = getParent();
777         return (p != null) ? p.getAttributes() : null;
778     }
779
780         public float getAlignment(int axis) {
781             if (axis == View.X_AXIS) {
782                 switch (justification) {
783                 case StyleConstants.ALIGN_LEFT:
784                     return 0;
785                 case StyleConstants.ALIGN_RIGHT:
786                     return 1;
787                 case StyleConstants.ALIGN_CENTER:
788                     return 0.5f;
789                 case StyleConstants.ALIGN_JUSTIFIED:
790                     float rv = 0.5f;
791                     //if we can justifiy the content always align to
792
//the left.
793
if (isJustifiableDocument()) {
794                         rv = 0f;
795                     }
796                     return rv;
797                 }
798             }
799             return super.getAlignment(axis);
800         }
801
802         /**
803          * Provides a mapping from the document model coordinate space
804          * to the coordinate space of the view mapped to it. This is
805          * implemented to let the superclass find the position along
806          * the major axis and the allocation of the row is used
807          * along the minor axis, so that even though the children
808          * are different heights they all get the same caret height.
809          *
810          * @param pos the position to convert
811          * @param a the allocated region to render into
812          * @return the bounding box of the given position
813          * @exception BadLocationException if the given position does not represent a
814          * valid location in the associated document
815          * @see View#modelToView
816          */

817         public Shape modelToView(int pos, Shape a, Position.Bias JavaDoc b) throws BadLocationException JavaDoc {
818             Rectangle r = a.getBounds();
819         View JavaDoc v = getViewAtPosition(pos, r);
820         if ((v != null) && (!v.getElement().isLeaf())) {
821         // Don't adjust the height if the view represents a branch.
822
return super.modelToView(pos, a, b);
823         }
824         r = a.getBounds();
825             int height = r.height;
826             int y = r.y;
827             Shape loc = super.modelToView(pos, a, b);
828             r = loc.getBounds();
829             r.height = height;
830             r.y = y;
831             return r;
832         }
833
834         /**
835          * Range represented by a row in the paragraph is only
836          * a subset of the total range of the paragraph element.
837          * @see View#getRange
838          */

839         public int getStartOffset() {
840         int offs = Integer.MAX_VALUE;
841             int n = getViewCount();
842         for (int i = 0; i < n; i++) {
843         View JavaDoc v = getView(i);
844         offs = Math.min(offs, v.getStartOffset());
845         }
846             return offs;
847         }
848
849         public int getEndOffset() {
850         int offs = 0;
851             int n = getViewCount();
852         for (int i = 0; i < n; i++) {
853         View JavaDoc v = getView(i);
854         offs = Math.max(offs, v.getEndOffset());
855         }
856             return offs;
857         }
858
859     /**
860      * Perform layout for the minor axis of the box (i.e. the
861      * axis orthoginal to the axis that it represents). The results
862      * of the layout should be placed in the given arrays which represent
863      * the allocations to the children along the minor axis.
864      * <p>
865      * This is implemented to do a baseline layout of the children
866      * by calling BoxView.baselineLayout.
867      *
868      * @param targetSpan the total span given to the view, which
869      * whould be used to layout the children.
870      * @param axis the axis being layed out.
871      * @param offsets the offsets from the origin of the view for
872      * each of the child views. This is a return value and is
873      * filled in by the implementation of this method.
874      * @param spans the span of each child view. This is a return
875      * value and is filled in by the implementation of this method.
876      * @return the offset and span for each child view in the
877      * offsets and spans parameters
878      */

879         protected void layoutMinorAxis(int targetSpan, int axis, int[] offsets, int[] spans) {
880         baselineLayout(targetSpan, axis, offsets, spans);
881     }
882
883         protected SizeRequirements JavaDoc calculateMinorAxisRequirements(int axis,
884                                   SizeRequirements JavaDoc r) {
885         return baselineRequirements(axis, r);
886     }
887
888
889         private boolean isLastRow() {
890             View JavaDoc parent;
891             return ((parent = getParent()) == null
892                     || this == parent.getView(parent.getViewCount() - 1));
893         }
894         
895         private boolean isBrokenRow() {
896             boolean rv = false;
897             int viewsCount = getViewCount();
898             if (viewsCount > 0) {
899                 View JavaDoc lastView = getView(viewsCount - 1);
900                 if (lastView.getBreakWeight(X_AXIS, 0, 0) >=
901                       ForcedBreakWeight) {
902                     rv = true;
903                 }
904             }
905             return rv;
906         }
907         
908         private boolean isJustifiableDocument() {
909             return (! Boolean.TRUE.equals(getDocument().getProperty(
910                           AbstractDocument.I18NProperty)));
911         }
912         
913         /**
914          * Whether we need to justify this {@code Row}.
915          * At this time (jdk1.6) we support justification on for non
916          * 18n text.
917          *
918          * @return {@code true} if this {@code Row} should be justified.
919          */

920         private boolean isJustifyEnabled() {
921             boolean ret = (justification == StyleConstants.ALIGN_JUSTIFIED);
922
923             //no justification for i18n documents
924
ret = ret && isJustifiableDocument();
925
926             //no justification for the last row
927
ret = ret && ! isLastRow();
928             
929             //no justification for the broken rows
930
ret = ret && ! isBrokenRow();
931
932             return ret;
933         }
934
935
936         //Calls super method after setting spaceAddon to 0.
937
//Justification should not affect MajorAxisRequirements
938
@Override JavaDoc
939         protected SizeRequirements JavaDoc calculateMajorAxisRequirements(int axis,
940                 SizeRequirements JavaDoc r) {
941             int oldJustficationData[] = justificationData;
942             justificationData = null;
943             SizeRequirements JavaDoc ret = super.calculateMajorAxisRequirements(axis, r);
944             if (isJustifyEnabled()) {
945                 justificationData = oldJustficationData;
946             }
947             return ret;
948         }
949
950         @Override JavaDoc
951         protected void layoutMajorAxis(int targetSpan, int axis,
952                                        int[] offsets, int[] spans) {
953             int oldJustficationData[] = justificationData;
954             justificationData = null;
955             super.layoutMajorAxis(targetSpan, axis, offsets, spans);
956             if (! isJustifyEnabled()) {
957                 return;
958             }
959
960             int currentSpan = 0;
961             for (int span : spans) {
962                 currentSpan += span;
963             }
964             if (currentSpan == targetSpan) {
965                 //no need to justify
966
return;
967             }
968             
969             // we justify text by enlarging spaces by the {@code spaceAddon}.
970
// justification is started to the right of the rightmost TAB.
971
// leading and trailing spaces are not extendable.
972
//
973
// GlyphPainter1 uses
974
// justificationData
975
// for all painting and measurement.
976

977             int extendableSpaces = 0;
978             int startJustifiableContent = -1;
979             int endJustifiableContent = -1;
980             int lastLeadingSpaces = 0;
981
982             int rowStartOffset = getStartOffset();
983             int rowEndOffset = getEndOffset();
984             int spaceMap[] = new int[rowEndOffset - rowStartOffset];
985             Arrays.fill(spaceMap, 0);
986             for (int i = getViewCount() - 1; i >= 0 ; i--) {
987                 View JavaDoc view = getView(i);
988                 if (view instanceof GlyphView JavaDoc) {
989                     GlyphView.JustificationInfo JavaDoc justificationInfo =
990                         ((GlyphView JavaDoc) view).getJustificationInfo(rowStartOffset);
991                     final int viewStartOffset = view.getStartOffset();
992                     final int offset = viewStartOffset - rowStartOffset;
993                     for (int j = 0; j < justificationInfo.spaceMap.length(); j++) {
994                         if (justificationInfo.spaceMap.get(j)) {
995                             spaceMap[j + offset] = 1;
996                         }
997                     }
998                     if (startJustifiableContent > 0) {
999                         if (justificationInfo.end >= 0) {
1000                            extendableSpaces += justificationInfo.trailingSpaces;
1001                        } else {
1002                            lastLeadingSpaces += justificationInfo.trailingSpaces;
1003                        }
1004                    }
1005                    if (justificationInfo.start >= 0) {
1006                        startJustifiableContent =
1007                            justificationInfo.start + viewStartOffset;
1008                        extendableSpaces += lastLeadingSpaces;
1009                    }
1010                    if (justificationInfo.end >= 0
1011                          && endJustifiableContent < 0) {
1012                        endJustifiableContent =
1013                            justificationInfo.end + viewStartOffset;
1014                    }
1015                    extendableSpaces += justificationInfo.contentSpaces;
1016                    lastLeadingSpaces = justificationInfo.leadingSpaces;
1017                    if (justificationInfo.hasTab) {
1018                        break;
1019                    }
1020                }
1021            }
1022            if (extendableSpaces <= 0) {
1023                //there is nothing we can do to justify
1024
return;
1025            }
1026            int adjustment = (targetSpan - currentSpan);
1027            int spaceAddon = (extendableSpaces > 0)
1028                ? adjustment / extendableSpaces
1029                : 0;
1030            int spaceAddonLeftoverEnd = -1;
1031            for (int i = startJustifiableContent - rowStartOffset,
1032                     leftover = adjustment - spaceAddon * extendableSpaces;
1033                     leftover > 0;
1034                     leftover -= spaceMap[i],
1035                     i++) {
1036                spaceAddonLeftoverEnd = i;
1037            }
1038            if (spaceAddon > 0 || spaceAddonLeftoverEnd >= 0) {
1039                justificationData = (oldJustficationData != null)
1040                    ? oldJustficationData
1041                    : new int[END_JUSTIFIABLE + 1];
1042                justificationData[SPACE_ADDON] = spaceAddon;
1043                justificationData[SPACE_ADDON_LEFTOVER_END] =
1044                    spaceAddonLeftoverEnd;
1045                justificationData[START_JUSTIFIABLE] =
1046                    startJustifiableContent - rowStartOffset;
1047                justificationData[END_JUSTIFIABLE] =
1048                    endJustifiableContent - rowStartOffset;
1049                super.layoutMajorAxis(targetSpan, axis, offsets, spans);
1050            }
1051        }
1052
1053        //for justified row we assume the maximum horizontal span
1054
//is MAX_VALUE.
1055
@Override JavaDoc
1056        public float getMaximumSpan(int axis) {
1057            float ret;
1058            if (View.X_AXIS == axis
1059                  && isJustifyEnabled()) {
1060                ret = Float.MAX_VALUE;
1061            } else {
1062              ret = super.getMaximumSpan(axis);
1063            }
1064            return ret;
1065        }
1066
1067    /**
1068     * Fetches the child view index representing the given position in
1069     * the model.
1070     *
1071     * @param pos the position >= 0
1072     * @return index of the view representing the given position, or
1073     * -1 if no view represents that position
1074     */

1075    protected int getViewIndexAtPosition(int pos) {
1076        // This is expensive, but are views are not necessarily layed
1077
// out in model order.
1078
if(pos < getStartOffset() || pos >= getEndOffset())
1079        return -1;
1080        for(int counter = getViewCount() - 1; counter >= 0; counter--) {
1081        View JavaDoc v = getView(counter);
1082        if(pos >= v.getStartOffset() &&
1083           pos < v.getEndOffset()) {
1084            return counter;
1085        }
1086        }
1087        return -1;
1088    }
1089
1090    /**
1091     * Gets the left inset.
1092     *
1093     * @return the inset
1094     */

1095    protected short getLeftInset() {
1096        View JavaDoc parentView;
1097        int adjustment = 0;
1098        if ((parentView = getParent()) != null) { //use firstLineIdent for the first row
1099
if (this == parentView.getView(0)) {
1100            adjustment = firstLineIndent;
1101        }
1102        }
1103        return (short)(super.getLeftInset() + adjustment);
1104    }
1105    
1106    protected short getBottomInset() {
1107        return (short)(super.getBottomInset() +
1108               ((minorRequest != null) ? minorRequest.preferred : 0) *
1109               lineSpacing);
1110    }
1111
1112        final static int SPACE_ADDON = 0;
1113        final static int SPACE_ADDON_LEFTOVER_END = 1;
1114        final static int START_JUSTIFIABLE = 2;
1115        //this should be the last index in justificationData
1116
final static int END_JUSTIFIABLE = 3;
1117
1118        int justificationData[] = null;
1119    }
1120
1121}
1122
Popular Tags