001    /* ParagraphView.java -- A composite View
002       Copyright (C) 2005  Free Software Foundation, Inc.
003    
004    This file is part of GNU Classpath.
005    
006    GNU Classpath is free software; you can redistribute it and/or modify
007    it under the terms of the GNU General Public License as published by
008    the Free Software Foundation; either version 2, or (at your option)
009    any later version.
010    
011    GNU Classpath is distributed in the hope that it will be useful, but
012    WITHOUT ANY WARRANTY; without even the implied warranty of
013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014    General Public License for more details.
015    
016    You should have received a copy of the GNU General Public License
017    along with GNU Classpath; see the file COPYING.  If not, write to the
018    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019    02110-1301 USA.
020    
021    Linking this library statically or dynamically with other modules is
022    making a combined work based on this library.  Thus, the terms and
023    conditions of the GNU General Public License cover the whole
024    combination.
025    
026    As a special exception, the copyright holders of this library give you
027    permission to link this library with independent modules to produce an
028    executable, regardless of the license terms of these independent
029    modules, and to copy and distribute the resulting executable under
030    terms of your choice, provided that you also meet, for each linked
031    independent module, the terms and conditions of the license of that
032    module.  An independent module is a module which is not derived from
033    or based on this library.  If you modify this library, you may extend
034    this exception to your version of the library, but you are not
035    obligated to do so.  If you do not wish to do so, delete this
036    exception statement from your version. */
037    
038    
039    package javax.swing.text;
040    
041    import java.awt.Shape;
042    
043    import javax.swing.SizeRequirements;
044    import 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     */
052    public 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    }