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.Color;
042    import java.awt.Graphics;
043    import java.awt.Rectangle;
044    import java.awt.Shape;
045    
046    import javax.swing.SizeRequirements;
047    import javax.swing.event.DocumentEvent;
048    
049    /**
050     * A {@link FlowView} that flows it's children horizontally and boxes the rows
051     * vertically.
052     *
053     * @author Roman Kennke (roman@kennke.org)
054     */
055    public class ParagraphView extends FlowView implements TabExpander
056    {
057      /**
058       * A specialized horizontal <code>BoxView</code> that represents exactly
059       * one row in a <code>ParagraphView</code>.
060       */
061      class Row extends BoxView
062      {
063        /**
064         * Creates a new instance of <code>Row</code>.
065         */
066        Row(Element el)
067        {
068          super(el, X_AXIS);
069        }
070    
071        /**
072         * Overridden to adjust when we are the first line, and firstLineIndent
073         * is not 0.
074         */
075        public short getLeftInset()
076        {
077          short leftInset = super.getLeftInset();
078          View parent = getParent();
079          if (parent != null)
080            {
081              if (parent.getView(0) == this)
082                leftInset += firstLineIndent;
083            }
084          return leftInset;
085        }
086    
087        public float getAlignment(int axis)
088        {
089          float align;
090          if (axis == X_AXIS)
091            switch (justification)
092              {
093              case StyleConstants.ALIGN_RIGHT:
094                align = 1.0F;
095                break;
096              case StyleConstants.ALIGN_CENTER:
097              case StyleConstants.ALIGN_JUSTIFIED:
098                align = 0.5F;
099                break;
100              case StyleConstants.ALIGN_LEFT:
101              default:
102                align = 0.0F;
103              }
104          else
105            align = super.getAlignment(axis);
106          return align;
107        }
108    
109        /**
110         * Overridden because child views are not necessarily laid out in model
111         * order.
112         */
113        protected int getViewIndexAtPosition(int pos)
114        {
115          int index = -1;
116          if (pos >= getStartOffset() && pos < getEndOffset())
117            {
118              int nviews = getViewCount();
119              for (int i = 0; i < nviews && index == -1; i++)
120                {
121                  View child = getView(i);
122                  if (pos >= child.getStartOffset() && pos < child.getEndOffset())
123                    index = i;
124                }
125            }
126          return index;
127        }
128    
129    
130        /**
131         * Overridden to perform a baseline layout. The normal BoxView layout
132         * isn't completely suitable for rows.
133         */
134        protected void layoutMinorAxis(int targetSpan, int axis, int[] offsets,
135                                       int[] spans)
136        {
137          baselineLayout(targetSpan, axis, offsets, spans);
138        }
139    
140        /**
141         * Overridden to perform a baseline layout. The normal BoxView layout
142         * isn't completely suitable for rows.
143         */
144        protected SizeRequirements calculateMinorAxisRequirements(int axis,
145                                                                SizeRequirements r)
146        {
147          return baselineRequirements(axis, r);
148        }
149    
150        protected void loadChildren(ViewFactory vf)
151        {
152          // Do nothing here. The children are added while layouting.
153        }
154    
155        /**
156         * Overridden to determine the minimum start offset of the row's children.
157         */
158        public int getStartOffset()
159        {
160          // Determine minimum start offset of the children.
161          int offset = Integer.MAX_VALUE;
162          int n = getViewCount();
163          for (int i = 0; i < n; i++)
164            {
165              View v = getView(i);
166              offset = Math.min(offset, v.getStartOffset());
167            }
168          return offset;
169        }
170    
171        /**
172         * Overridden to determine the maximum end offset of the row's children.
173         */
174        public int getEndOffset()
175        {
176          // Determine minimum start offset of the children.
177          int offset = 0;
178          int n = getViewCount();
179          for (int i = 0; i < n; i++)
180            {
181              View v = getView(i);
182              offset = Math.max(offset, v.getEndOffset());
183            }
184          return offset;
185        }
186      }
187    
188      /**
189       * The indentation of the first line of the paragraph.
190       */
191      protected int firstLineIndent;
192    
193      /**
194       * The justification of the paragraph.
195       */
196      private int justification;
197    
198      /**
199       * The line spacing of this paragraph.
200       */
201      private float lineSpacing;
202    
203      /**
204       * The TabSet of this paragraph.
205       */
206      private TabSet tabSet;
207    
208      /**
209       * Creates a new <code>ParagraphView</code> for the given
210       * <code>Element</code>.
211       *
212       * @param element the element that is rendered by this ParagraphView
213       */
214      public ParagraphView(Element element)
215      {
216        super(element, Y_AXIS);
217      }
218    
219      public float nextTabStop(float x, int tabOffset)
220      {
221        throw new InternalError("Not implemented yet");
222      }
223    
224      /**
225       * Creates a new view that represents a row within a flow.
226       *
227       * @return a view for a new row
228       */
229      protected View createRow()
230      {
231        return new Row(getElement());
232      }
233    
234      /**
235       * Returns the alignment for this paragraph view for the specified axis.
236       * For the X_AXIS the paragraph view will be aligned at it's left edge
237       * (0.0F). For the Y_AXIS the paragraph view will be aligned at the
238       * center of it's first row.
239       *
240       * @param axis the axis which is examined
241       *
242       * @return the alignment for this paragraph view for the specified axis
243       */
244      public float getAlignment(int axis)
245      {
246        float align;
247        if (axis == X_AXIS)
248          align = 0.5F;
249        else if (getViewCount() > 0)
250          {
251            float prefHeight = getPreferredSpan(Y_AXIS);
252            float firstRowHeight = getView(0).getPreferredSpan(Y_AXIS);
253            align = (firstRowHeight / 2.F) / prefHeight;
254          }
255        else
256          align = 0.5F;
257        return align;
258      }
259    
260      /**
261       * Receives notification when some attributes of the displayed element
262       * changes. This triggers a refresh of the cached attributes of this
263       * paragraph.
264       *
265       * @param ev the document event
266       * @param a the allocation of this view
267       * @param vf the view factory to use for creating new child views
268       */
269      public void changedUpdate(DocumentEvent ev, Shape a, ViewFactory vf)
270      {
271        setPropertiesFromAttributes();
272        layoutChanged(X_AXIS);
273        layoutChanged(Y_AXIS);
274        super.changedUpdate(ev, a, vf);
275      }
276    
277      /**
278       * Fetches the cached properties from the element's attributes.
279       */
280      protected void setPropertiesFromAttributes()
281      {
282        Element el = getElement();
283        AttributeSet atts = el.getAttributes();
284        setFirstLineIndent(StyleConstants.getFirstLineIndent(atts));
285        setLineSpacing(StyleConstants.getLineSpacing(atts));
286        setJustification(StyleConstants.getAlignment(atts));
287        tabSet = StyleConstants.getTabSet(atts);
288      }
289    
290      /**
291       * Sets the indentation of the first line of the paragraph.
292       *
293       * @param i the indentation to set
294       */
295      protected void setFirstLineIndent(float i)
296      {
297        firstLineIndent = (int) i;
298      }
299    
300      /**
301       * Sets the justification of the paragraph.
302       *
303       * @param j the justification to set 
304       */
305      protected void setJustification(int j)
306      {
307        justification = j;
308      }
309    
310      /**
311       * Sets the line spacing for this paragraph.
312       *
313       * @param s the line spacing to set
314       */
315      protected void setLineSpacing(float s)
316      {
317        lineSpacing = s;
318      }
319    
320      /**
321       * Returns the i-th view from the logical views, before breaking into rows.
322       *
323       * @param i the index of the logical view to return
324       *
325       * @return the i-th view from the logical views, before breaking into rows
326       */
327      protected View getLayoutView(int i)
328      {
329        return layoutPool.getView(i);
330      }
331    
332      /**
333       * Returns the number of logical child views.
334       *
335       * @return the number of logical child views
336       */
337      protected int getLayoutViewCount()
338      {
339        return layoutPool.getViewCount();
340      }
341    
342      /**
343       * Returns the TabSet used by this ParagraphView.
344       *
345       * @return the TabSet used by this ParagraphView
346       */
347      protected TabSet getTabSet()
348      {
349        return tabSet;
350      }
351    
352      /**
353       * Finds the next offset in the document that has one of the characters
354       * specified in <code>string</code>. If there is no such character found,
355       * this returns -1.
356       *
357       * @param string the characters to search for
358       * @param start the start offset
359       *
360       * @return the next offset in the document that has one of the characters
361       *         specified in <code>string</code>
362       */
363      protected int findOffsetToCharactersInString(char[] string, int start)
364      {
365        int offset = -1;
366        Document doc = getDocument();
367        Segment text = new Segment();
368        try
369          {
370            doc.getText(start, doc.getLength() - start, text);
371            int index = start;
372    
373            searchLoop:
374            while (true)
375              {
376                char ch = text.next();
377                if (ch == Segment.DONE)
378                  break;
379    
380                for (int j = 0; j < string.length; ++j)
381                  {
382                    if (string[j] == ch)
383                      {
384                        offset = index;
385                        break searchLoop;
386                      }
387                  }
388                index++;
389              }
390          }
391        catch (BadLocationException ex)
392          {
393            // Ignore this and return -1.
394          }
395        return offset;
396      }
397    
398      protected int getClosestPositionTo(int pos, Position.Bias bias, Shape a,
399                                         int direction, Position.Bias[] biasRet,
400                                         int rowIndex, int x)
401        throws BadLocationException
402      {
403        // FIXME: Implement this properly. However, this looks like it might
404        // have been replaced by viewToModel.
405        return pos;
406      }
407    
408      /**
409       * Returns the size that is used by this view (or it's child views) between
410       * <code>startOffset</code> and <code>endOffset</code>. If the child views
411       * implement the {@link TabableView} interface, then this is used to
412       * determine the span, otherwise we use the preferred span of the child
413       * views.
414       *
415       * @param startOffset the start offset
416       * @param endOffset the end offset
417       *
418       * @return the span used by the view between <code>startOffset</code> and
419       *         <code>endOffset</cod>
420       */
421      protected float getPartialSize(int startOffset, int endOffset)
422      {
423        int startIndex = getViewIndex(startOffset, Position.Bias.Backward);
424        int endIndex = getViewIndex(endOffset, Position.Bias.Forward);
425        float span;
426        if (startIndex == endIndex)
427          {
428            View child = getView(startIndex);
429            if (child instanceof TabableView)
430              {
431                TabableView tabable = (TabableView) child;
432                span = tabable.getPartialSpan(startOffset, endOffset);
433              }
434            else
435              span = child.getPreferredSpan(X_AXIS);
436          }
437        else if (endIndex - startIndex == 1)
438          {
439            View child1 = getView(startIndex);
440            if (child1 instanceof TabableView)
441              {
442                TabableView tabable = (TabableView) child1;
443                span = tabable.getPartialSpan(startOffset, child1.getEndOffset());
444              }
445            else
446              span = child1.getPreferredSpan(X_AXIS);
447            View child2 = getView(endIndex);
448            if (child2 instanceof TabableView)
449              {
450                TabableView tabable = (TabableView) child2;
451                span += tabable.getPartialSpan(child2.getStartOffset(), endOffset);
452              }
453            else
454              span += child2.getPreferredSpan(X_AXIS);
455          }
456        else
457          {
458            // Start with the first view.
459            View child1 = getView(startIndex);
460            if (child1 instanceof TabableView)
461              {
462                TabableView tabable = (TabableView) child1;
463                span = tabable.getPartialSpan(startOffset, child1.getEndOffset());
464              }
465            else
466              span = child1.getPreferredSpan(X_AXIS);
467    
468            // Add up the view spans between the start and the end view.
469            for (int i = startIndex + 1; i < endIndex; i++)
470              {
471                View child = getView(i);
472                span += child.getPreferredSpan(X_AXIS);
473              }
474    
475            // Add the span of the last view.
476            View child2 = getView(endIndex);
477            if (child2 instanceof TabableView)
478              {
479                TabableView tabable = (TabableView) child2;
480                span += tabable.getPartialSpan(child2.getStartOffset(), endOffset);
481              }
482            else
483              span += child2.getPreferredSpan(X_AXIS);
484          }
485        return span;
486      }
487    
488      /**
489       * Returns the location where the tabs are calculated from. This returns
490       * <code>0.0F</code> by default.
491       *
492       * @return the location where the tabs are calculated from
493       */
494      protected float getTabBase()
495      {
496        return 0.0F;
497      }
498    
499      /**
500       * @specnote This method is specified to take a Row parameter, which is a
501       *           private inner class of that class, which makes it unusable from
502       *           application code. Also, this method seems to be replaced by
503       *           {@link FlowStrategy#adjustRow(FlowView, int, int, int)}.
504       *
505       */
506      protected void adjustRow(Row r, int desiredSpan, int x)
507      {
508      }
509    
510      /**
511       * @specnote This method's signature differs from the one defined in
512       *           {@link View} and is therefore never called. It is probably there
513       *           for historical reasons.
514       */
515      public View breakView(int axis, float len, Shape a)
516      {
517        // This method is not used.
518        return null;
519      }
520    
521      /**
522       * @specnote This method's signature differs from the one defined in
523       *           {@link View} and is therefore never called. It is probably there
524       *           for historical reasons.
525       */
526      public int getBreakWeight(int axis, float len)
527      {
528        // This method is not used.
529        return 0;
530      }
531    }