001    /* View.java --
002       Copyright (C) 2002, 2004, 2005, 2006  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.Container;
042    import java.awt.Graphics;
043    import java.awt.Rectangle;
044    import java.awt.Shape;
045    
046    import javax.swing.SwingConstants;
047    import javax.swing.SwingUtilities;
048    import javax.swing.event.DocumentEvent;
049    
050    public 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    }