001/* View.java --
002   Copyright (C) 2002, 2004, 2005, 2006  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.Container;
042import java.awt.Graphics;
043import java.awt.Rectangle;
044import java.awt.Shape;
045
046import javax.swing.SwingConstants;
047import javax.swing.SwingUtilities;
048import javax.swing.event.DocumentEvent;
049
050public abstract class View implements SwingConstants
051{
052  public static final int BadBreakWeight = 0;
053  public static final int ExcellentBreakWeight = 2000;
054  public static final int ForcedBreakWeight = 3000;
055  public static final int GoodBreakWeight = 1000;
056
057  public static final int X_AXIS = 0;
058  public static final int Y_AXIS = 1;
059
060  private Element elt;
061  private View parent;
062
063  /**
064   * Creates a new <code>View</code> instance.
065   *
066   * @param elem an <code>Element</code> value
067   */
068  public View(Element elem)
069  {
070    elt = elem;
071  }
072
073  public abstract void paint(Graphics g, Shape s);
074
075  /**
076   * Sets the parent for this view. This is the first method that is beeing
077   * called on a view to setup the view hierarchy. This is also the last method
078   * beeing called when the view is disconnected from the view hierarchy, in
079   * this case <code>parent</code> is null.
080   *
081   * If <code>parent</code> is <code>null</code>, a call to this method also
082   * calls <code>setParent</code> on the children, thus disconnecting them from
083   * the view hierarchy. That means that super must be called when this method
084   * is overridden.
085   *
086   * @param parent the parent to set, <code>null</code> when this view is
087   *        beeing disconnected from the view hierarchy
088   */
089  public void setParent(View parent)
090  {
091    if (parent == null)
092      {
093        int numChildren = getViewCount();
094        for (int i = 0; i < numChildren; i++)
095          {
096            View child = getView(i);
097            // It is important that we only reset the parent on views that
098            // actually belong to us. In FlowView the child may already be
099            // reparented.
100            if (child.getParent() == this)
101              child.setParent(null);
102          }
103      }
104
105    this.parent = parent;
106  }
107
108  public View getParent()
109  {
110    return parent;
111  }
112
113  public Container getContainer()
114  {
115    View parent = getParent();
116    if (parent == null)
117      return null;
118    else
119      return parent.getContainer();
120  }
121
122  public Document getDocument()
123  {
124    return getElement().getDocument();
125  }
126
127  public Element getElement()
128  {
129    return elt;
130  }
131
132  /**
133   * Returns the preferred span along the specified axis. Normally the view is
134   * rendered with the span returned here if that is possible.
135   *
136   * @param axis the axis
137   *
138   * @return the preferred span along the specified axis
139   */
140  public abstract float getPreferredSpan(int axis);
141
142  /**
143   * Returns the resize weight of this view. A value of <code>0</code> or less
144   * means this view is not resizeable. Positive values make the view
145   * resizeable. The default implementation returns <code>0</code>
146   * unconditionally.
147   *
148   * @param axis the axis
149   *
150   * @return the resizability of this view along the specified axis
151   */
152  public int getResizeWeight(int axis)
153  {
154    return 0;
155  }
156
157  /**
158   * Returns the maximum span along the specified axis. The default
159   * implementation will forward to
160   * {@link #getPreferredSpan(int)} unless {@link #getResizeWeight(int)}
161   * returns a value > 0, in which case this returns {@link Integer#MIN_VALUE}.
162   *
163   * @param axis the axis
164   *
165   * @return the maximum span along the specified axis
166   */
167  public float getMaximumSpan(int axis)
168  {
169    float max = Integer.MAX_VALUE;
170    if (getResizeWeight(axis) <= 0)
171      max = getPreferredSpan(axis);
172    return max;
173  }
174
175  /**
176   * Returns the minimum span along the specified axis. The default
177   * implementation will forward to
178   * {@link #getPreferredSpan(int)} unless {@link #getResizeWeight(int)}
179   * returns a value > 0, in which case this returns <code>0</code>.
180   *
181   * @param axis the axis
182   *
183   * @return the minimum span along the specified axis
184   */
185  public float getMinimumSpan(int axis)
186  {
187    float min = 0;
188    if (getResizeWeight(axis) <= 0)
189      min = getPreferredSpan(axis);
190    return min;
191  }
192
193  public void setSize(float width, float height)
194  {
195    // The default implementation does nothing.
196  }
197
198  /**
199   * Returns the alignment of this view along the baseline of the parent view.
200   * An alignment of <code>0.0</code> will align this view with the left edge
201   * along the baseline, an alignment of <code>0.5</code> will align it
202   * centered to the baseline, an alignment of <code>1.0</code> will align
203   * the right edge along the baseline.
204   *
205   * The default implementation returns 0.5 unconditionally.
206   *
207   * @param axis the axis
208   *
209   * @return the alignment of this view along the parents baseline for the
210   *         specified axis
211   */
212  public float getAlignment(int axis)
213  {
214    return 0.5f;
215  }
216
217  public AttributeSet getAttributes()
218  {
219    return getElement().getAttributes();
220  }
221
222  public boolean isVisible()
223  {
224    return true;
225  }
226
227  public int getViewCount()
228  {
229    return 0;
230  }
231
232  public View getView(int index)
233  {
234    return null;
235  }
236
237  public ViewFactory getViewFactory()
238  {
239    View parent = getParent();
240    return parent != null ? parent.getViewFactory() : null;
241  }
242
243  /**
244   * Replaces a couple of child views with new child views. If
245   * <code>length == 0</code> then this is a simple insertion, if
246   * <code>views == null</code> this only removes some child views.
247   *
248   * @param offset the offset at which to replace
249   * @param length the number of child views to be removed
250   * @param views the new views to be inserted, may be <code>null</code>
251   */
252  public void replace(int offset, int length, View[] views)
253  {
254    // Default implementation does nothing.
255  }
256
257  public void insert(int offset, View view)
258  {
259    View[] array = { view };
260    replace(offset, 1, array);
261  }
262
263  public void append(View view)
264  {
265    View[] array = { view };
266    int offset = getViewCount();
267    replace(offset, 0, array);
268  }
269
270  public void removeAll()
271  {
272    replace(0, getViewCount(), null);
273  }
274
275  public void remove(int index)
276  {
277    replace(index, 1, null);
278  }
279
280  public View createFragment(int p0, int p1)
281  {
282    // The default implementation doesn't support fragmentation.
283    return this;
284  }
285
286  public int getStartOffset()
287  {
288    return getElement().getStartOffset();
289  }
290
291  public int getEndOffset()
292  {
293    return getElement().getEndOffset();
294  }
295
296  public Shape getChildAllocation(int index, Shape a)
297  {
298    return null;
299  }
300
301  /**
302   * @since 1.4
303   */
304  public int getViewIndex(float x, float y, Shape allocation)
305  {
306    return -1;
307  }
308
309  /**
310   * @since 1.4
311   */
312  public String getToolTipText(float x, float y, Shape allocation)
313  {
314    int index = getViewIndex(x, y, allocation);
315
316    String text = null;
317    if (index >= 0)
318      {
319        allocation = getChildAllocation(index, allocation);
320        Rectangle r = allocation instanceof Rectangle ? (Rectangle) allocation
321                                                      : allocation.getBounds();
322        if (r.contains(x, y))
323          text = getView(index).getToolTipText(x, y, allocation);
324      }
325    return text;
326  }
327
328  /**
329   * @since 1.3
330   */
331  public Graphics getGraphics()
332  {
333    return getContainer().getGraphics();
334  }
335
336  public void preferenceChanged(View child, boolean width, boolean height)
337  {
338    View p = getParent();
339    if (p != null)
340      p.preferenceChanged(this, width, height);
341  }
342
343  public int getBreakWeight(int axis, float pos, float len)
344  {
345    int weight = BadBreakWeight;
346    if (len > getPreferredSpan(axis))
347      weight = GoodBreakWeight;
348    return weight;
349  }
350
351  public View breakView(int axis, int offset, float pos, float len)
352  {
353    return this;
354  }
355
356  /**
357   * @since 1.3
358   */
359  public int getViewIndex(int pos, Position.Bias b)
360  {
361    return -1;
362  }
363
364  /**
365   * Receive notification about an insert update to the text model.
366   *
367   * The default implementation of this method does the following:
368   * <ul>
369   * <li>Call {@link #updateChildren} if the element that this view is
370   * responsible for has changed. This makes sure that the children can
371   * correctly represent the model.<li>
372   * <li>Call {@link #forwardUpdate}. This forwards the DocumentEvent to
373   * the child views.<li>
374   * <li>Call {@link #updateLayout}. Gives the view a chance to either
375   * repair its layout, reschedule layout or do nothing at all.</li>
376   * </ul>
377   *
378   * @param ev the DocumentEvent that describes the change
379   * @param shape the shape of the view
380   * @param vf the ViewFactory for creating child views
381   */
382  public void insertUpdate(DocumentEvent ev, Shape shape, ViewFactory vf)
383  {
384    if (getViewCount() > 0)
385      {
386        Element el = getElement();
387        DocumentEvent.ElementChange ec = ev.getChange(el);
388        if (ec != null)
389          {
390            if (! updateChildren(ec, ev, vf))
391              ec = null;
392          }
393        forwardUpdate(ec, ev, shape, vf);
394        updateLayout(ec, ev, shape);
395      }
396  }
397
398  /**
399   * Receive notification about a remove update to the text model.
400   *
401   * The default implementation of this method does the following:
402   * <ul>
403   * <li>Call {@link #updateChildren} if the element that this view is
404   * responsible for has changed. This makes sure that the children can
405   * correctly represent the model.<li>
406   * <li>Call {@link #forwardUpdate}. This forwards the DocumentEvent to
407   * the child views.<li>
408   * <li>Call {@link #updateLayout}. Gives the view a chance to either
409   * repair its layout, reschedule layout or do nothing at all.</li>
410   * </ul>
411   *
412   * @param ev the DocumentEvent that describes the change
413   * @param shape the shape of the view
414   * @param vf the ViewFactory for creating child views
415   */
416  public void removeUpdate(DocumentEvent ev, Shape shape, ViewFactory vf)
417  {
418    Element el = getElement();
419    DocumentEvent.ElementChange ec = ev.getChange(el);
420    if (ec != null)
421      {
422        if (! updateChildren(ec, ev, vf))
423          ec = null;
424      }
425    forwardUpdate(ec, ev, shape, vf);
426    updateLayout(ec, ev, shape);
427  }
428
429  /**
430   * Receive notification about a change update to the text model.
431   *
432   * The default implementation of this method does the following:
433   * <ul>
434   * <li>Call {@link #updateChildren} if the element that this view is
435   * responsible for has changed. This makes sure that the children can
436   * correctly represent the model.<li>
437   * <li>Call {@link #forwardUpdate}. This forwards the DocumentEvent to
438   * the child views.<li>
439   * <li>Call {@link #updateLayout}. Gives the view a chance to either
440   * repair its layout, reschedule layout or do nothing at all.</li>
441   * </ul>
442   *
443   * @param ev the DocumentEvent that describes the change
444   * @param shape the shape of the view
445   * @param vf the ViewFactory for creating child views
446   */
447  public void changedUpdate(DocumentEvent ev, Shape shape, ViewFactory vf)
448  {
449    if (getViewCount() > 0)
450      {
451        Element el = getElement();
452        DocumentEvent.ElementChange ec = ev.getChange(el);
453        if (ec != null)
454          {
455            if (! updateChildren(ec, ev, vf))
456              ec = null;
457          }
458        forwardUpdate(ec, ev, shape, vf);
459        updateLayout(ec, ev, shape);
460      }
461  }
462
463  /**
464   * Updates the list of children that is returned by {@link #getView}
465   * and {@link #getViewCount}.
466   *
467   * Element that are specified as beeing added in the ElementChange record are
468   * assigned a view for using the ViewFactory. Views of Elements that
469   * are specified as beeing removed are removed from the list.
470   *
471   * @param ec the ElementChange record that describes the change of the
472   *           element
473   * @param ev the DocumentEvent describing the change of the document model
474   * @param vf the ViewFactory to use for creating new views
475   *
476   * @return whether or not the child views represent the child elements of
477   *         the element that this view is responsible for. Some views may
478   *         create views that are responsible only for parts of the element
479   *         that they are responsible for and should then return false.
480   *
481   * @since 1.3
482   */
483  protected boolean updateChildren(DocumentEvent.ElementChange ec,
484                                   DocumentEvent ev,
485                                   ViewFactory vf)
486  {
487    Element[] added = ec.getChildrenAdded();
488    Element[] removed = ec.getChildrenRemoved();
489    int index = ec.getIndex();
490
491    View[] newChildren = null;
492    if (added != null)
493      {
494        newChildren = new View[added.length];
495        for (int i = 0; i < added.length; ++i)
496          newChildren[i] = vf.create(added[i]);
497      }
498    int numRemoved = removed != null ? removed.length : 0;
499    replace(index, numRemoved, newChildren);
500
501    return true;
502  }
503
504  /**
505   * Forwards the DocumentEvent to child views that need to get notified
506   * of the change to the model. This calles {@link #forwardUpdateToView}
507   * for each View that must be forwarded to.
508   *
509   * If <code>ec</code> is not <code>null</code> (this means there have been
510   * structural changes to the element that this view is responsible for) this
511   * method should recognize this and don't notify newly added child views.
512   *
513   * @param ec the ElementChange describing the element changes (may be
514   *           <code>null</code> if there were no changes)
515   * @param ev the DocumentEvent describing the changes to the model
516   * @param shape the current allocation of the view
517   * @param vf the ViewFactory used to create new Views
518   *
519   * @since 1.3
520   */
521  protected void forwardUpdate(DocumentEvent.ElementChange ec,
522                               DocumentEvent ev, Shape shape, ViewFactory vf)
523  {
524    int count = getViewCount();
525    if (count > 0)
526      {
527        // Determine start index.
528        int startOffset = ev.getOffset();
529        int startIndex = getViewIndex(startOffset, Position.Bias.Backward);
530
531        // For REMOVE events we have to forward the event to the last element,
532        // for the case that an Element has been removed that represente
533        // the offset.
534        if (startIndex == -1 && ev.getType() == DocumentEvent.EventType.REMOVE
535            && startOffset >= getEndOffset())
536          {
537            startIndex = getViewCount() - 1;
538          }
539
540        // When startIndex is on a view boundary, forward event to the
541        // previous view too.
542        if (startIndex >= 0)
543          {
544            View v = getView(startIndex);
545            if (v != null)
546              {
547                if (v.getStartOffset() == startOffset && startOffset > 0)
548                  startIndex = Math.max(0, startIndex - 1);
549              }
550          }
551        startIndex = Math.max(0, startIndex);
552
553        // Determine end index.
554        int endIndex = startIndex;
555        if (ev.getType() != DocumentEvent.EventType.REMOVE)
556          {
557            endIndex = getViewIndex(startOffset + ev.getLength(),
558                                    Position.Bias.Forward);
559            if (endIndex < 0)
560              endIndex = getViewCount() - 1;
561          }
562
563        // Determine hole that comes from added elements (we don't forward
564        // the event to newly added views.
565        int startAdded = endIndex + 1;
566        int endAdded = startAdded;
567        Element[] added = (ec != null) ? ec.getChildrenAdded() : null;
568        if (added != null && added.length > 0)
569          {
570            startAdded = ec.getIndex();
571            endAdded = startAdded + added.length - 1;
572          }
573
574        // Forward event to all views between startIndex and endIndex,
575        // and leave out all views in the hole.
576        for (int i = startIndex; i <= endIndex; i++)
577          {
578            // Skip newly added child views.
579            if (! (i >= startAdded && i <= endAdded))
580              {
581                View child = getView(i);
582                if (child != null)
583                  {
584                    Shape childAlloc = getChildAllocation(i, shape);
585                    forwardUpdateToView(child, ev, childAlloc, vf);
586                  }
587              }
588          }
589      }
590  }
591
592  /**
593   * Forwards an update event to the given child view. This calls
594   * {@link #insertUpdate}, {@link #removeUpdate} or {@link #changedUpdate},
595   * depending on the type of document event.
596   *
597   * @param view the View to forward the event to
598   * @param ev the DocumentEvent to forward
599   * @param shape the current allocation of the View
600   * @param vf the ViewFactory used to create new Views
601   *
602   * @since 1.3
603   */
604  protected void forwardUpdateToView(View view, DocumentEvent ev, Shape shape,
605                                     ViewFactory vf)
606  {
607    DocumentEvent.EventType type = ev.getType();
608    if (type == DocumentEvent.EventType.INSERT)
609      view.insertUpdate(ev, shape, vf);
610    else if (type == DocumentEvent.EventType.REMOVE)
611      view.removeUpdate(ev, shape, vf);
612    else if (type == DocumentEvent.EventType.CHANGE)
613      view.changedUpdate(ev, shape, vf);
614  }
615
616  /**
617   * Updates the layout.
618   *
619   * @param ec the ElementChange that describes the changes to the element
620   * @param ev the DocumentEvent that describes the changes to the model
621   * @param shape the current allocation for this view
622   *
623   * @since 1.3
624   */
625  protected void updateLayout(DocumentEvent.ElementChange ec,
626                              DocumentEvent ev, Shape shape)
627  {
628    if (ec != null && shape != null)
629      {
630        preferenceChanged(null, true, true);
631        Container c = getContainer();
632        if (c != null)
633          c.repaint();
634      }
635  }
636
637  /**
638   * Maps a position in the document into the coordinate space of the View.
639   * The output rectangle usually reflects the font height but has a width
640   * of zero.
641   *
642   * @param pos the position of the character in the model
643   * @param a the area that is occupied by the view
644   * @param b either {@link Position.Bias#Forward} or
645   *        {@link Position.Bias#Backward} depending on the preferred
646   *        direction bias. If <code>null</code> this defaults to
647   *        <code>Position.Bias.Forward</code>
648   *
649   * @return a rectangle that gives the location of the document position
650   *         inside the view coordinate space
651   *
652   * @throws BadLocationException if <code>pos</code> is invalid
653   * @throws IllegalArgumentException if b is not one of the above listed
654   *         valid values
655   */
656  public abstract Shape modelToView(int pos, Shape a, Position.Bias b)
657    throws BadLocationException;
658
659  /**
660   * Maps a region in the document into the coordinate space of the View.
661   *
662   * @param p1 the beginning position inside the document
663   * @param b1 the direction bias for the beginning position
664   * @param p2 the end position inside the document
665   * @param b2 the direction bias for the end position
666   * @param a the area that is occupied by the view
667   *
668   * @return a rectangle that gives the span of the document region
669   *         inside the view coordinate space
670   *
671   * @throws BadLocationException if <code>p1</code> or <code>p2</code> are
672   *         invalid
673   * @throws IllegalArgumentException if b1 or b2 is not one of the above
674   *         listed valid values
675   */
676  public Shape modelToView(int p1, Position.Bias b1,
677                           int p2, Position.Bias b2, Shape a)
678    throws BadLocationException
679  {
680    if (b1 != Position.Bias.Forward && b1 != Position.Bias.Backward)
681      throw new IllegalArgumentException
682        ("b1 must be either Position.Bias.Forward or Position.Bias.Backward");
683    if (b2 != Position.Bias.Forward && b2 != Position.Bias.Backward)
684      throw new IllegalArgumentException
685        ("b2 must be either Position.Bias.Forward or Position.Bias.Backward");
686
687    Shape s1 = modelToView(p1, a, b1);
688    // Special case for p2 == end index.
689    Shape s2;
690    if (p2 != getEndOffset())
691      {
692        s2 = modelToView(p2, a, b2);
693      }
694    else
695      {
696        try
697          {
698            s2 = modelToView(p2, a, b2);
699          }
700        catch (BadLocationException ex)
701          {
702            // Assume the end rectangle to be at the right edge of the
703            // view.
704            Rectangle aRect = a instanceof Rectangle ? (Rectangle) a
705                                                     : a.getBounds();
706            s2 = new Rectangle(aRect.x + aRect.width - 1, aRect.y, 1,
707                               aRect.height);
708          }
709      }
710
711    // Need to modify the rectangle, so we create a copy in all cases.
712    Rectangle r1 = s1.getBounds();
713    Rectangle r2 = s2 instanceof Rectangle ? (Rectangle) s2
714                                           : s2.getBounds();
715
716    // For multiline view, let the resulting rectangle span the whole view.
717    if (r1.y != r2.y)
718      {
719        Rectangle aRect = a instanceof Rectangle ? (Rectangle) a
720                                                 : a.getBounds();
721        r1.x = aRect.x;
722        r1.width = aRect.width;
723      }
724
725    return SwingUtilities.computeUnion(r2.x, r2.y, r2.width, r2.height, r1);
726  }
727
728  /**
729   * Maps a position in the document into the coordinate space of the View.
730   * The output rectangle usually reflects the font height but has a width
731   * of zero.
732   *
733   * This method is deprecated and calls
734   * {@link #modelToView(int, Position.Bias, int, Position.Bias, Shape)} with
735   * a bias of {@link Position.Bias#Forward}.
736   *
737   * @param pos the position of the character in the model
738   * @param a the area that is occupied by the view
739   *
740   * @return a rectangle that gives the location of the document position
741   *         inside the view coordinate space
742   *
743   * @throws BadLocationException if <code>pos</code> is invalid
744   *
745   * @deprecated Use {@link #modelToView(int, Shape, Position.Bias)} instead.
746   */
747  public Shape modelToView(int pos, Shape a) throws BadLocationException
748  {
749    return modelToView(pos, a, Position.Bias.Forward);
750  }
751
752  /**
753   * Maps coordinates from the <code>View</code>'s space into a position
754   * in the document model.
755   *
756   * @param x the x coordinate in the view space
757   * @param y the y coordinate in the view space
758   * @param a the allocation of this <code>View</code>
759   * @param b the bias to use
760   *
761   * @return the position in the document that corresponds to the screen
762   *         coordinates <code>x, y</code>
763   */
764  public abstract int viewToModel(float x, float y, Shape a, Position.Bias[] b);
765
766  /**
767   * Maps coordinates from the <code>View</code>'s space into a position
768   * in the document model. This method is deprecated and only there for
769   * compatibility.
770   *
771   * @param x the x coordinate in the view space
772   * @param y the y coordinate in the view space
773   * @param a the allocation of this <code>View</code>
774   *
775   * @return the position in the document that corresponds to the screen
776   *         coordinates <code>x, y</code>
777   *
778   * @deprecated Use {@link #viewToModel(float, float, Shape, Position.Bias[])}
779   *             instead.
780   */
781  public int viewToModel(float x, float y, Shape a)
782  {
783    Position.Bias[] biasRet = new Position.Bias[1];
784    biasRet[0] = Position.Bias.Forward;
785    return viewToModel(x, y, a, biasRet);
786  }
787
788  /**
789   * Dumps the complete View hierarchy. This method can be used for debugging
790   * purposes.
791   */
792  protected void dump()
793  {
794    // Climb up the hierarchy to the parent.
795    View parent = getParent();
796    if (parent != null)
797      parent.dump();
798    else
799      dump(0);
800  }
801
802  /**
803   * Dumps the view hierarchy below this View with the specified indentation
804   * level.
805   *
806   * @param indent the indentation level to be used for this view
807   */
808  void dump(int indent)
809  {
810    for (int i = 0; i < indent; ++i)
811      System.out.print('.');
812    System.out.println(this + "(" + getStartOffset() + "," + getEndOffset() + ": " + getElement());
813
814    int count = getViewCount();
815    for (int i = 0; i < count; ++i)
816      getView(i).dump(indent + 1);
817  }
818
819  /**
820   * Returns the document position that is (visually) nearest to the given
821   * document position <code>pos</code> in the given direction <code>d</code>.
822   *
823   * @param pos the document position
824   * @param b the bias for <code>pos</code>
825   * @param a the allocation for this view
826   * @param d the direction, must be either {@link SwingConstants#NORTH},
827   *        {@link SwingConstants#SOUTH}, {@link SwingConstants#WEST} or
828   *        {@link SwingConstants#EAST}
829   * @param biasRet an array of {@link Position.Bias} that can hold at least
830   *        one element, which is filled with the bias of the return position
831   *        on method exit
832   *
833   * @return the document position that is (visually) nearest to the given
834   *         document position <code>pos</code> in the given direction
835   *         <code>d</code>
836   *
837   * @throws BadLocationException if <code>pos</code> is not a valid offset in
838   *         the document model
839   * @throws IllegalArgumentException if <code>d</code> is not a valid direction
840   */
841  public int getNextVisualPositionFrom(int pos, Position.Bias b,
842                                       Shape a, int d,
843                                       Position.Bias[] biasRet)
844    throws BadLocationException
845  {
846    int ret = pos;
847    Rectangle r;
848    View parent;
849
850    switch (d)
851    {
852      case EAST:
853        // TODO: take component orientation into account?
854        // Note: If pos is below zero the implementation will return
855        // pos + 1 regardless of whether that value is a correct offset
856        // in the document model. However this is what the RI does.
857        ret = Math.min(pos + 1, getEndOffset());
858        break;
859      case WEST:
860        // TODO: take component orientation into account?
861        ret = Math.max(pos - 1, getStartOffset());
862        break;
863      case NORTH:
864        // Try to find a suitable offset by examining the area above.
865        parent = getParent();
866        r =  parent.modelToView(pos, a, b).getBounds();
867        ret = parent.viewToModel(r.x, r.y - 1, a, biasRet);
868        break;
869      case SOUTH:
870        // Try to find a suitable offset by examining the area below.
871        parent = getParent();
872        r =  parent.modelToView(pos, a, b).getBounds();
873        ret = parent.viewToModel(r.x + r.width, r.y + r.height, a, biasRet);
874        break;
875      default:
876        throw new IllegalArgumentException("Illegal value for d");
877    }
878
879    return ret;
880  }
881}