1 /* ParagraphView.java -- A composite View
2    Copyright (C) 2005  Free Software Foundation, Inc.
3 
4 This file is part of GNU Classpath.
5 
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
10 
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 General Public License for more details.
15 
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING.  If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301 USA.
20 
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library.  Thus, the terms and
23 conditions of the GNU General Public License cover the whole
24 combination.
25 
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module.  An independent module is a module which is not derived from
33 or based on this library.  If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so.  If you do not wish to do so, delete this
36 exception statement from your version. */
37 
38 
39 package javax.swing.text;
40 
41 import java.awt.Shape;
42 
43 import javax.swing.SizeRequirements;
44 import javax.swing.event.DocumentEvent;
45 
46 /**
47  * A {@link FlowView} that flows it's children horizontally and boxes the rows
48  * vertically.
49  *
50  * @author Roman Kennke (roman@kennke.org)
51  */
52 public class ParagraphView extends FlowView implements TabExpander
53 {
54   /**
55    * A specialized horizontal <code>BoxView</code> that represents exactly
56    * one row in a <code>ParagraphView</code>.
57    */
58   class Row extends BoxView
59   {
60     /**
61      * Creates a new instance of <code>Row</code>.
62      */
Row(Element el)63     Row(Element el)
64     {
65       super(el, X_AXIS);
66     }
67 
68     /**
69      * Overridden to adjust when we are the first line, and firstLineIndent
70      * is not 0.
71      */
getLeftInset()72     public short getLeftInset()
73     {
74       short leftInset = super.getLeftInset();
75       View parent = getParent();
76       if (parent != null)
77         {
78           if (parent.getView(0) == this)
79             leftInset += firstLineIndent;
80         }
81       return leftInset;
82     }
83 
getAlignment(int axis)84     public float getAlignment(int axis)
85     {
86       float align;
87       if (axis == X_AXIS)
88         switch (justification)
89           {
90           case StyleConstants.ALIGN_RIGHT:
91             align = 1.0F;
92             break;
93           case StyleConstants.ALIGN_CENTER:
94           case StyleConstants.ALIGN_JUSTIFIED:
95             align = 0.5F;
96             break;
97           case StyleConstants.ALIGN_LEFT:
98           default:
99             align = 0.0F;
100           }
101       else
102         align = super.getAlignment(axis);
103       return align;
104     }
105 
106     /**
107      * Overridden because child views are not necessarily laid out in model
108      * order.
109      */
getViewIndexAtPosition(int pos)110     protected int getViewIndexAtPosition(int pos)
111     {
112       int index = -1;
113       if (pos >= getStartOffset() && pos < getEndOffset())
114         {
115           int nviews = getViewCount();
116           for (int i = 0; i < nviews && index == -1; i++)
117             {
118               View child = getView(i);
119               if (pos >= child.getStartOffset() && pos < child.getEndOffset())
120                 index = i;
121             }
122         }
123       return index;
124     }
125 
126 
127     /**
128      * Overridden to perform a baseline layout. The normal BoxView layout
129      * isn't completely suitable for rows.
130      */
layoutMinorAxis(int targetSpan, int axis, int[] offsets, int[] spans)131     protected void layoutMinorAxis(int targetSpan, int axis, int[] offsets,
132                                    int[] spans)
133     {
134       baselineLayout(targetSpan, axis, offsets, spans);
135     }
136 
137     /**
138      * Overridden to perform a baseline layout. The normal BoxView layout
139      * isn't completely suitable for rows.
140      */
calculateMinorAxisRequirements(int axis, SizeRequirements r)141     protected SizeRequirements calculateMinorAxisRequirements(int axis,
142                                                             SizeRequirements r)
143     {
144       return baselineRequirements(axis, r);
145     }
146 
loadChildren(ViewFactory vf)147     protected void loadChildren(ViewFactory vf)
148     {
149       // Do nothing here. The children are added while layouting.
150     }
151 
152     /**
153      * Overridden to determine the minimum start offset of the row's children.
154      */
getStartOffset()155     public int getStartOffset()
156     {
157       // Determine minimum start offset of the children.
158       int offset = Integer.MAX_VALUE;
159       int n = getViewCount();
160       for (int i = 0; i < n; i++)
161         {
162           View v = getView(i);
163           offset = Math.min(offset, v.getStartOffset());
164         }
165       return offset;
166     }
167 
168     /**
169      * Overridden to determine the maximum end offset of the row's children.
170      */
getEndOffset()171     public int getEndOffset()
172     {
173       // Determine minimum start offset of the children.
174       int offset = 0;
175       int n = getViewCount();
176       for (int i = 0; i < n; i++)
177         {
178           View v = getView(i);
179           offset = Math.max(offset, v.getEndOffset());
180         }
181       return offset;
182     }
183   }
184 
185   /**
186    * The indentation of the first line of the paragraph.
187    */
188   protected int firstLineIndent;
189 
190   /**
191    * The justification of the paragraph.
192    */
193   private int justification;
194 
195   /**
196    * The line spacing of this paragraph.
197    */
198   private float lineSpacing;
199 
200   /**
201    * The TabSet of this paragraph.
202    */
203   private TabSet tabSet;
204 
205   /**
206    * Creates a new <code>ParagraphView</code> for the given
207    * <code>Element</code>.
208    *
209    * @param element the element that is rendered by this ParagraphView
210    */
ParagraphView(Element element)211   public ParagraphView(Element element)
212   {
213     super(element, Y_AXIS);
214   }
215 
nextTabStop(float x, int tabOffset)216   public float nextTabStop(float x, int tabOffset)
217   {
218     throw new InternalError("Not implemented yet");
219   }
220 
221   /**
222    * Creates a new view that represents a row within a flow.
223    *
224    * @return a view for a new row
225    */
createRow()226   protected View createRow()
227   {
228     return new Row(getElement());
229   }
230 
231   /**
232    * Returns the alignment for this paragraph view for the specified axis.
233    * For the X_AXIS the paragraph view will be aligned at it's left edge
234    * (0.0F). For the Y_AXIS the paragraph view will be aligned at the
235    * center of it's first row.
236    *
237    * @param axis the axis which is examined
238    *
239    * @return the alignment for this paragraph view for the specified axis
240    */
getAlignment(int axis)241   public float getAlignment(int axis)
242   {
243     float align;
244     if (axis == X_AXIS)
245       align = 0.5F;
246     else if (getViewCount() > 0)
247       {
248         float prefHeight = getPreferredSpan(Y_AXIS);
249         float firstRowHeight = getView(0).getPreferredSpan(Y_AXIS);
250         align = (firstRowHeight / 2.F) / prefHeight;
251       }
252     else
253       align = 0.5F;
254     return align;
255   }
256 
257   /**
258    * Receives notification when some attributes of the displayed element
259    * changes. This triggers a refresh of the cached attributes of this
260    * paragraph.
261    *
262    * @param ev the document event
263    * @param a the allocation of this view
264    * @param vf the view factory to use for creating new child views
265    */
changedUpdate(DocumentEvent ev, Shape a, ViewFactory vf)266   public void changedUpdate(DocumentEvent ev, Shape a, ViewFactory vf)
267   {
268     setPropertiesFromAttributes();
269     layoutChanged(X_AXIS);
270     layoutChanged(Y_AXIS);
271     super.changedUpdate(ev, a, vf);
272   }
273 
274   /**
275    * Fetches the cached properties from the element's attributes.
276    */
setPropertiesFromAttributes()277   protected void setPropertiesFromAttributes()
278   {
279     Element el = getElement();
280     AttributeSet atts = el.getAttributes();
281     setFirstLineIndent(StyleConstants.getFirstLineIndent(atts));
282     setLineSpacing(StyleConstants.getLineSpacing(atts));
283     setJustification(StyleConstants.getAlignment(atts));
284     tabSet = StyleConstants.getTabSet(atts);
285   }
286 
287   /**
288    * Sets the indentation of the first line of the paragraph.
289    *
290    * @param i the indentation to set
291    */
setFirstLineIndent(float i)292   protected void setFirstLineIndent(float i)
293   {
294     firstLineIndent = (int) i;
295   }
296 
297   /**
298    * Sets the justification of the paragraph.
299    *
300    * @param j the justification to set
301    */
setJustification(int j)302   protected void setJustification(int j)
303   {
304     justification = j;
305   }
306 
307   /**
308    * Sets the line spacing for this paragraph.
309    *
310    * @param s the line spacing to set
311    */
setLineSpacing(float s)312   protected void setLineSpacing(float s)
313   {
314     lineSpacing = s;
315   }
316 
317   /**
318    * Returns the i-th view from the logical views, before breaking into rows.
319    *
320    * @param i the index of the logical view to return
321    *
322    * @return the i-th view from the logical views, before breaking into rows
323    */
getLayoutView(int i)324   protected View getLayoutView(int i)
325   {
326     return layoutPool.getView(i);
327   }
328 
329   /**
330    * Returns the number of logical child views.
331    *
332    * @return the number of logical child views
333    */
getLayoutViewCount()334   protected int getLayoutViewCount()
335   {
336     return layoutPool.getViewCount();
337   }
338 
339   /**
340    * Returns the TabSet used by this ParagraphView.
341    *
342    * @return the TabSet used by this ParagraphView
343    */
getTabSet()344   protected TabSet getTabSet()
345   {
346     return tabSet;
347   }
348 
349   /**
350    * Finds the next offset in the document that has one of the characters
351    * specified in <code>string</code>. If there is no such character found,
352    * this returns -1.
353    *
354    * @param string the characters to search for
355    * @param start the start offset
356    *
357    * @return the next offset in the document that has one of the characters
358    *         specified in <code>string</code>
359    */
findOffsetToCharactersInString(char[] string, int start)360   protected int findOffsetToCharactersInString(char[] string, int start)
361   {
362     int offset = -1;
363     Document doc = getDocument();
364     Segment text = new Segment();
365     try
366       {
367         doc.getText(start, doc.getLength() - start, text);
368         int index = start;
369 
370         searchLoop:
371         while (true)
372           {
373             char ch = text.next();
374             if (ch == Segment.DONE)
375               break;
376 
377             for (int j = 0; j < string.length; ++j)
378               {
379                 if (string[j] == ch)
380                   {
381                     offset = index;
382                     break searchLoop;
383                   }
384               }
385             index++;
386           }
387       }
388     catch (BadLocationException ex)
389       {
390         // Ignore this and return -1.
391       }
392     return offset;
393   }
394 
getClosestPositionTo(int pos, Position.Bias bias, Shape a, int direction, Position.Bias[] biasRet, int rowIndex, int x)395   protected int getClosestPositionTo(int pos, Position.Bias bias, Shape a,
396                                      int direction, Position.Bias[] biasRet,
397                                      int rowIndex, int x)
398     throws BadLocationException
399   {
400     // FIXME: Implement this properly. However, this looks like it might
401     // have been replaced by viewToModel.
402     return pos;
403   }
404 
405   /**
406    * Returns the size that is used by this view (or it's child views) between
407    * <code>startOffset</code> and <code>endOffset</code>. If the child views
408    * implement the {@link TabableView} interface, then this is used to
409    * determine the span, otherwise we use the preferred span of the child
410    * views.
411    *
412    * @param startOffset the start offset
413    * @param endOffset the end offset
414    *
415    * @return the span used by the view between <code>startOffset</code> and
416    *         <code>endOffset</cod>
417    */
getPartialSize(int startOffset, int endOffset)418   protected float getPartialSize(int startOffset, int endOffset)
419   {
420     int startIndex = getViewIndex(startOffset, Position.Bias.Backward);
421     int endIndex = getViewIndex(endOffset, Position.Bias.Forward);
422     float span;
423     if (startIndex == endIndex)
424       {
425         View child = getView(startIndex);
426         if (child instanceof TabableView)
427           {
428             TabableView tabable = (TabableView) child;
429             span = tabable.getPartialSpan(startOffset, endOffset);
430           }
431         else
432           span = child.getPreferredSpan(X_AXIS);
433       }
434     else if (endIndex - startIndex == 1)
435       {
436         View child1 = getView(startIndex);
437         if (child1 instanceof TabableView)
438           {
439             TabableView tabable = (TabableView) child1;
440             span = tabable.getPartialSpan(startOffset, child1.getEndOffset());
441           }
442         else
443           span = child1.getPreferredSpan(X_AXIS);
444         View child2 = getView(endIndex);
445         if (child2 instanceof TabableView)
446           {
447             TabableView tabable = (TabableView) child2;
448             span += tabable.getPartialSpan(child2.getStartOffset(), endOffset);
449           }
450         else
451           span += child2.getPreferredSpan(X_AXIS);
452       }
453     else
454       {
455         // Start with the first view.
456         View child1 = getView(startIndex);
457         if (child1 instanceof TabableView)
458           {
459             TabableView tabable = (TabableView) child1;
460             span = tabable.getPartialSpan(startOffset, child1.getEndOffset());
461           }
462         else
463           span = child1.getPreferredSpan(X_AXIS);
464 
465         // Add up the view spans between the start and the end view.
466         for (int i = startIndex + 1; i < endIndex; i++)
467           {
468             View child = getView(i);
469             span += child.getPreferredSpan(X_AXIS);
470           }
471 
472         // Add the span of the last view.
473         View child2 = getView(endIndex);
474         if (child2 instanceof TabableView)
475           {
476             TabableView tabable = (TabableView) child2;
477             span += tabable.getPartialSpan(child2.getStartOffset(), endOffset);
478           }
479         else
480           span += child2.getPreferredSpan(X_AXIS);
481       }
482     return span;
483   }
484 
485   /**
486    * Returns the location where the tabs are calculated from. This returns
487    * <code>0.0F</code> by default.
488    *
489    * @return the location where the tabs are calculated from
490    */
getTabBase()491   protected float getTabBase()
492   {
493     return 0.0F;
494   }
495 
496   /**
497    * @specnote This method is specified to take a Row parameter, which is a
498    *           private inner class of that class, which makes it unusable from
499    *           application code. Also, this method seems to be replaced by
500    *           {@link FlowStrategy#adjustRow(FlowView, int, int, int)}.
501    *
502    */
adjustRow(Row r, int desiredSpan, int x)503   protected void adjustRow(Row r, int desiredSpan, int x)
504   {
505   }
506 
507   /**
508    * @specnote This method's signature differs from the one defined in
509    *           {@link View} and is therefore never called. It is probably there
510    *           for historical reasons.
511    */
breakView(int axis, float len, Shape a)512   public View breakView(int axis, float len, Shape a)
513   {
514     // This method is not used.
515     return null;
516   }
517 
518   /**
519    * @specnote This method's signature differs from the one defined in
520    *           {@link View} and is therefore never called. It is probably there
521    *           for historical reasons.
522    */
getBreakWeight(int axis, float len)523   public int getBreakWeight(int axis, float len)
524   {
525     // This method is not used.
526     return 0;
527   }
528 }
529