1 /* View.java --
2    Copyright (C) 2002, 2004, 2005, 2006  Free Software Foundation, Inc.
3 
4 This file is part of GNU Classpath.
5 
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
10 
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 General Public License for more details.
15 
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING.  If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301 USA.
20 
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library.  Thus, the terms and
23 conditions of the GNU General Public License cover the whole
24 combination.
25 
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module.  An independent module is a module which is not derived from
33 or based on this library.  If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so.  If you do not wish to do so, delete this
36 exception statement from your version. */
37 
38 
39 package javax.swing.text;
40 
41 import java.awt.Container;
42 import java.awt.Graphics;
43 import java.awt.Rectangle;
44 import java.awt.Shape;
45 
46 import javax.swing.SwingConstants;
47 import javax.swing.SwingUtilities;
48 import javax.swing.event.DocumentEvent;
49 
50 public abstract class View implements SwingConstants
51 {
52   public static final int BadBreakWeight = 0;
53   public static final int ExcellentBreakWeight = 2000;
54   public static final int ForcedBreakWeight = 3000;
55   public static final int GoodBreakWeight = 1000;
56 
57   public static final int X_AXIS = 0;
58   public static final int Y_AXIS = 1;
59 
60   private Element elt;
61   private View parent;
62 
63   /**
64    * Creates a new <code>View</code> instance.
65    *
66    * @param elem an <code>Element</code> value
67    */
View(Element elem)68   public View(Element elem)
69   {
70     elt = elem;
71   }
72 
paint(Graphics g, Shape s)73   public abstract void paint(Graphics g, Shape s);
74 
75   /**
76    * Sets the parent for this view. This is the first method that is beeing
77    * called on a view to setup the view hierarchy. This is also the last method
78    * beeing called when the view is disconnected from the view hierarchy, in
79    * this case <code>parent</code> is null.
80    *
81    * If <code>parent</code> is <code>null</code>, a call to this method also
82    * calls <code>setParent</code> on the children, thus disconnecting them from
83    * the view hierarchy. That means that super must be called when this method
84    * is overridden.
85    *
86    * @param parent the parent to set, <code>null</code> when this view is
87    *        beeing disconnected from the view hierarchy
88    */
setParent(View parent)89   public void setParent(View parent)
90   {
91     if (parent == null)
92       {
93         int numChildren = getViewCount();
94         for (int i = 0; i < numChildren; i++)
95           {
96             View child = getView(i);
97             // It is important that we only reset the parent on views that
98             // actually belong to us. In FlowView the child may already be
99             // reparented.
100             if (child.getParent() == this)
101               child.setParent(null);
102           }
103       }
104 
105     this.parent = parent;
106   }
107 
getParent()108   public View getParent()
109   {
110     return parent;
111   }
112 
getContainer()113   public Container getContainer()
114   {
115     View parent = getParent();
116     if (parent == null)
117       return null;
118     else
119       return parent.getContainer();
120   }
121 
getDocument()122   public Document getDocument()
123   {
124     return getElement().getDocument();
125   }
126 
getElement()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    */
getPreferredSpan(int axis)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    */
getResizeWeight(int axis)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    */
getMaximumSpan(int axis)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    */
getMinimumSpan(int axis)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 
setSize(float width, float height)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    */
getAlignment(int axis)212   public float getAlignment(int axis)
213   {
214     return 0.5f;
215   }
216 
getAttributes()217   public AttributeSet getAttributes()
218   {
219     return getElement().getAttributes();
220   }
221 
isVisible()222   public boolean isVisible()
223   {
224     return true;
225   }
226 
getViewCount()227   public int getViewCount()
228   {
229     return 0;
230   }
231 
getView(int index)232   public View getView(int index)
233   {
234     return null;
235   }
236 
getViewFactory()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    */
replace(int offset, int length, View[] views)252   public void replace(int offset, int length, View[] views)
253   {
254     // Default implementation does nothing.
255   }
256 
insert(int offset, View view)257   public void insert(int offset, View view)
258   {
259     View[] array = { view };
260     replace(offset, 1, array);
261   }
262 
append(View view)263   public void append(View view)
264   {
265     View[] array = { view };
266     int offset = getViewCount();
267     replace(offset, 0, array);
268   }
269 
removeAll()270   public void removeAll()
271   {
272     replace(0, getViewCount(), null);
273   }
274 
remove(int index)275   public void remove(int index)
276   {
277     replace(index, 1, null);
278   }
279 
createFragment(int p0, int p1)280   public View createFragment(int p0, int p1)
281   {
282     // The default implementation doesn't support fragmentation.
283     return this;
284   }
285 
getStartOffset()286   public int getStartOffset()
287   {
288     return getElement().getStartOffset();
289   }
290 
getEndOffset()291   public int getEndOffset()
292   {
293     return getElement().getEndOffset();
294   }
295 
getChildAllocation(int index, Shape a)296   public Shape getChildAllocation(int index, Shape a)
297   {
298     return null;
299   }
300 
301   /**
302    * @since 1.4
303    */
getViewIndex(float x, float y, Shape allocation)304   public int getViewIndex(float x, float y, Shape allocation)
305   {
306     return -1;
307   }
308 
309   /**
310    * @since 1.4
311    */
getToolTipText(float x, float y, Shape allocation)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    */
getGraphics()331   public Graphics getGraphics()
332   {
333     return getContainer().getGraphics();
334   }
335 
preferenceChanged(View child, boolean width, boolean height)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 
getBreakWeight(int axis, float pos, float len)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 
breakView(int axis, int offset, float pos, float len)351   public View breakView(int axis, int offset, float pos, float len)
352   {
353     return this;
354   }
355 
356   /**
357    * @since 1.3
358    */
getViewIndex(int pos, Position.Bias b)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    */
insertUpdate(DocumentEvent ev, Shape shape, ViewFactory vf)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    */
removeUpdate(DocumentEvent ev, Shape shape, ViewFactory vf)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    */
changedUpdate(DocumentEvent ev, Shape shape, ViewFactory vf)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    */
updateChildren(DocumentEvent.ElementChange ec, DocumentEvent ev, ViewFactory vf)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    */
forwardUpdate(DocumentEvent.ElementChange ec, DocumentEvent ev, Shape shape, ViewFactory vf)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    */
forwardUpdateToView(View view, DocumentEvent ev, Shape shape, ViewFactory vf)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    */
updateLayout(DocumentEvent.ElementChange ec, DocumentEvent ev, Shape shape)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    */
modelToView(int pos, Shape a, Position.Bias b)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    */
modelToView(int p1, Position.Bias b1, int p2, Position.Bias b2, Shape a)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    */
modelToView(int pos, Shape a)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    */
viewToModel(float x, float y, Shape a, Position.Bias[] b)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    */
viewToModel(float x, float y, Shape a)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    */
dump()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    */
dump(int indent)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    */
getNextVisualPositionFrom(int pos, Position.Bias b, Shape a, int d, Position.Bias[] biasRet)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 }
882