001/* ParagraphView.java -- A composite View
002   Copyright (C) 2005  Free Software Foundation, Inc.
003
004This file is part of GNU Classpath.
005
006GNU Classpath is free software; you can redistribute it and/or modify
007it under the terms of the GNU General Public License as published by
008the Free Software Foundation; either version 2, or (at your option)
009any later version.
010
011GNU Classpath is distributed in the hope that it will be useful, but
012WITHOUT ANY WARRANTY; without even the implied warranty of
013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014General Public License for more details.
015
016You should have received a copy of the GNU General Public License
017along with GNU Classpath; see the file COPYING.  If not, write to the
018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
01902110-1301 USA.
020
021Linking this library statically or dynamically with other modules is
022making a combined work based on this library.  Thus, the terms and
023conditions of the GNU General Public License cover the whole
024combination.
025
026As a special exception, the copyright holders of this library give you
027permission to link this library with independent modules to produce an
028executable, regardless of the license terms of these independent
029modules, and to copy and distribute the resulting executable under
030terms of your choice, provided that you also meet, for each linked
031independent module, the terms and conditions of the license of that
032module.  An independent module is a module which is not derived from
033or based on this library.  If you modify this library, you may extend
034this exception to your version of the library, but you are not
035obligated to do so.  If you do not wish to do so, delete this
036exception statement from your version. */
037
038
039package javax.swing.text;
040
041import java.awt.Shape;
042
043import javax.swing.SizeRequirements;
044import javax.swing.event.DocumentEvent;
045
046/**
047 * A {@link FlowView} that flows it's children horizontally and boxes the rows
048 * vertically.
049 *
050 * @author Roman Kennke (roman@kennke.org)
051 */
052public class ParagraphView extends FlowView implements TabExpander
053{
054  /**
055   * A specialized horizontal <code>BoxView</code> that represents exactly
056   * one row in a <code>ParagraphView</code>.
057   */
058  class Row extends BoxView
059  {
060    /**
061     * Creates a new instance of <code>Row</code>.
062     */
063    Row(Element el)
064    {
065      super(el, X_AXIS);
066    }
067
068    /**
069     * Overridden to adjust when we are the first line, and firstLineIndent
070     * is not 0.
071     */
072    public short getLeftInset()
073    {
074      short leftInset = super.getLeftInset();
075      View parent = getParent();
076      if (parent != null)
077        {
078          if (parent.getView(0) == this)
079            leftInset += firstLineIndent;
080        }
081      return leftInset;
082    }
083
084    public float getAlignment(int axis)
085    {
086      float align;
087      if (axis == X_AXIS)
088        switch (justification)
089          {
090          case StyleConstants.ALIGN_RIGHT:
091            align = 1.0F;
092            break;
093          case StyleConstants.ALIGN_CENTER:
094          case StyleConstants.ALIGN_JUSTIFIED:
095            align = 0.5F;
096            break;
097          case StyleConstants.ALIGN_LEFT:
098          default:
099            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     */
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     */
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     */
141    protected SizeRequirements calculateMinorAxisRequirements(int axis,
142                                                            SizeRequirements r)
143    {
144      return baselineRequirements(axis, r);
145    }
146
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     */
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     */
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   */
211  public ParagraphView(Element element)
212  {
213    super(element, Y_AXIS);
214  }
215
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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
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   */
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   */
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   */
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   */
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   */
523  public int getBreakWeight(int axis, float len)
524  {
525    // This method is not used.
526    return 0;
527  }
528}