001/* CompositeView.java -- An abstract view that manages child views
002   Copyright (C) 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.Rectangle;
042import java.awt.Shape;
043
044import javax.swing.SwingConstants;
045
046/**
047 * An abstract base implementation of {@link View} that manages child
048 * <code>View</code>s.
049 *
050 * @author Roman Kennke (roman@kennke.org)
051 */
052public abstract class CompositeView
053  extends View
054{
055
056  /**
057   * The child views of this <code>CompositeView</code>.
058   */
059  private View[] children;
060
061  /**
062   * The number of child views.
063   */
064  private int numChildren;
065
066  /**
067   * The allocation of this <code>View</code> minus its insets. This is
068   * initialized in {@link #getInsideAllocation} and reused and modified in
069   * {@link #childAllocation(int, Rectangle)}.
070   */
071  private final Rectangle insideAllocation = new Rectangle();
072
073  /**
074   * The insets of this <code>CompositeView</code>. This is initialized
075   * in {@link #setInsets}.
076   */
077  private short top;
078  private short bottom;
079  private short left;
080  private short right;
081
082  /**
083   * Creates a new <code>CompositeView</code> for the given
084   * <code>Element</code>.
085   *
086   * @param element the element that is rendered by this CompositeView
087   */
088  public CompositeView(Element element)
089  {
090    super(element);
091    children = new View[0];
092    top = 0;
093    bottom = 0;
094    left = 0;
095    right = 0;
096  }
097
098  /**
099   * Loads the child views of this <code>CompositeView</code>. This method
100   * is called from {@link #setParent} to initialize the child views of
101   * this composite view.
102   *
103   * @param f the view factory to use for creating new child views
104   *
105   * @see #setParent
106   */
107  protected void loadChildren(ViewFactory f)
108  {
109    if (f != null)
110      {
111        Element el = getElement();
112        int count = el.getElementCount();
113        View[] newChildren = new View[count];
114        for (int i = 0; i < count; ++i)
115          {
116            Element child = el.getElement(i);
117            View view = f.create(child);
118            newChildren[i] = view;
119          }
120        // I'd have called replace(0, getViewCount(), newChildren) here
121        // in order to replace all existing views. However according to
122        // Harmony's tests this is not what the RI does.
123        replace(0, 0, newChildren);
124      }
125  }
126
127  /**
128   * Sets the parent of this <code>View</code>.
129   * In addition to setting the parent, this calls {@link #loadChildren}, if
130   * this <code>View</code> does not already have its children initialized.
131   *
132   * @param parent the parent to set
133   */
134  public void setParent(View parent)
135  {
136    super.setParent(parent);
137    if (parent != null && numChildren == 0)
138      loadChildren(getViewFactory());
139  }
140
141  /**
142   * Returns the number of child views.
143   *
144   * @return the number of child views
145   */
146  public int getViewCount()
147  {
148    return numChildren;
149  }
150
151  /**
152   * Returns the child view at index <code>n</code>.
153   *
154   * @param n the index of the requested child view
155   *
156   * @return the child view at index <code>n</code>
157   */
158  public View getView(int n)
159  {
160    return children[n];
161  }
162
163  /**
164   * Replaces child views by some other child views. If there are no views to
165   * remove (<code>length == 0</code>), the result is a simple insert, if
166   * there are no children to add (<code>view == null</code>) the result
167   * is a simple removal.
168   *
169   * @param offset the start offset from where to remove children
170   * @param length the number of children to remove
171   * @param views the views that replace the removed children
172   */
173  public void replace(int offset, int length, View[] views)
174  {
175    // Make sure we have an array. The Harmony testsuite indicates that we
176    // have to do something like this.
177    if (views == null)
178      views = new View[0];
179
180    // First we set the parent of the removed children to null.
181    int endOffset = offset + length;
182    for (int i = offset; i < endOffset; ++i)
183      {
184        if (children[i].getParent() == this)
185          children[i].setParent(null);
186        children[i] = null;
187      }
188
189    // Update the children array.
190    int delta = views.length - length;
191    int src = offset + length;
192    int numMove = numChildren - src;
193    int dst = src + delta;
194    if (numChildren + delta > children.length)
195      {
196        // Grow array.
197        int newLength = Math.max(2 * children.length, numChildren + delta);
198        View[] newChildren = new View[newLength];
199        System.arraycopy(children, 0, newChildren, 0, offset);
200        System.arraycopy(views, 0, newChildren, offset, views.length);
201        System.arraycopy(children, src, newChildren, dst, numMove);
202        children = newChildren;
203      }
204    else
205      {
206        // Patch existing array.
207        System.arraycopy(children, src, children, dst, numMove);
208        System.arraycopy(views, 0, children, offset, views.length);
209      }
210    numChildren += delta;
211
212    // Finally we set the parent of the added children to this.
213    for (int i = 0; i < views.length; ++i)
214      views[i].setParent(this);
215  }
216
217  /**
218   * Returns the allocation for the specified child <code>View</code>.
219   *
220   * @param index the index of the child view
221   * @param a the allocation for this view
222   *
223   * @return the allocation for the specified child <code>View</code>
224   */
225  public Shape getChildAllocation(int index, Shape a)
226  {
227    Rectangle r = getInsideAllocation(a);
228    childAllocation(index, r);
229    return r;
230  }
231
232  /**
233   * Maps a position in the document into the coordinate space of the View.
234   * The output rectangle usually reflects the font height but has a width
235   * of zero.
236   *
237   * @param pos the position of the character in the model
238   * @param a the area that is occupied by the view
239   * @param bias either {@link Position.Bias#Forward} or
240   *        {@link Position.Bias#Backward} depending on the preferred
241   *        direction bias. If <code>null</code> this defaults to
242   *        <code>Position.Bias.Forward</code>
243   *
244   * @return a rectangle that gives the location of the document position
245   *         inside the view coordinate space
246   *
247   * @throws BadLocationException if <code>pos</code> is invalid
248   * @throws IllegalArgumentException if b is not one of the above listed
249   *         valid values
250   */
251  public Shape modelToView(int pos, Shape a, Position.Bias bias)
252    throws BadLocationException
253  {
254    boolean backward = bias == Position.Bias.Backward;
255    int testpos = backward ? Math.max(0, pos - 1) : pos;
256
257    Shape ret = null;
258    if (! backward || testpos >= getStartOffset())
259      {
260        int childIndex = getViewIndexAtPosition(testpos);
261        if (childIndex != -1 && childIndex < getViewCount())
262          {
263            View child = getView(childIndex);
264            if (child != null && testpos >= child.getStartOffset()
265                && testpos < child.getEndOffset())
266              {
267                Shape childAlloc = getChildAllocation(childIndex, a);
268                if (childAlloc != null)
269                  {
270                    ret = child.modelToView(pos, childAlloc, bias);
271                    // Handle corner case.
272                    if (ret == null && child.getEndOffset() == pos)
273                      {
274                        childIndex++;
275                        if (childIndex < getViewCount())
276                          {
277                            child = getView(childIndex);
278                            childAlloc = getChildAllocation(childIndex, a);
279                            ret = child.modelToView(pos, childAlloc, bias);
280                          }
281                      }
282                  }
283              }
284          }
285      }
286
287    if (ret == null)
288      throw new BadLocationException("Position " + pos
289                                     + " is not represented by view.", pos);
290
291    return ret;
292  }
293
294  /**
295   * Maps a region in the document into the coordinate space of the View.
296   *
297   * @param p1 the beginning position inside the document
298   * @param b1 the direction bias for the beginning position
299   * @param p2 the end position inside the document
300   * @param b2 the direction bias for the end position
301   * @param a the area that is occupied by the view
302   *
303   * @return a rectangle that gives the span of the document region
304   *         inside the view coordinate space
305   *
306   * @throws BadLocationException if <code>p1</code> or <code>p2</code> are
307   *         invalid
308   * @throws IllegalArgumentException if b1 or b2 is not one of the above
309   *         listed valid values
310   */
311  public Shape modelToView(int p1, Position.Bias b1,
312                           int p2, Position.Bias b2, Shape a)
313    throws BadLocationException
314  {
315    // TODO: This is most likely not 100% ok, figure out what else is to
316    // do here.
317    return super.modelToView(p1, b1, p2, b2, a);
318  }
319
320  /**
321   * Maps coordinates from the <code>View</code>'s space into a position
322   * in the document model.
323   *
324   * @param x the x coordinate in the view space, x >= 0
325   * @param y the y coordinate in the view space, y >= 0
326   * @param a the allocation of this <code>View</code>
327   * @param b the bias to use
328   *
329   * @return the position in the document that corresponds to the screen
330   *         coordinates <code>x, y</code> >= 0
331   */
332  public int viewToModel(float x, float y, Shape a, Position.Bias[] b)
333  {
334    if (x >= 0 && y >= 0)
335      {
336        Rectangle r = getInsideAllocation(a);
337        View view = getViewAtPoint((int) x, (int) y, r);
338        return view.viewToModel(x, y, r, b);
339      }
340    return 0;
341  }
342
343  /**
344   * Returns the next model location that is visible in eiter north / south
345   * direction or east / west direction. This is used to determine the placement
346   * of the caret when navigating around the document with the arrow keys. This
347   * is a convenience method for {@link #getNextNorthSouthVisualPositionFrom}
348   * and {@link #getNextEastWestVisualPositionFrom}.
349   *
350   * @param pos
351   *          the model position to start search from
352   * @param b
353   *          the bias for <code>pos</code>
354   * @param a
355   *          the allocated region for this view
356   * @param direction
357   *          the direction from the current position, can be one of the
358   *          following:
359   *          <ul>
360   *          <li>{@link SwingConstants#WEST}</li>
361   *          <li>{@link SwingConstants#EAST}</li>
362   *          <li>{@link SwingConstants#NORTH}</li>
363   *          <li>{@link SwingConstants#SOUTH}</li>
364   *          </ul>
365   * @param biasRet
366   *          the bias of the return value gets stored here
367   * @return the position inside the model that represents the next visual
368   *         location
369   * @throws BadLocationException
370   *           if <code>pos</code> is not a valid location inside the document
371   *           model
372   * @throws IllegalArgumentException
373   *           if <code>direction</code> is invalid
374   */
375  public int getNextVisualPositionFrom(int pos, Position.Bias b, Shape a,
376                                       int direction, Position.Bias[] biasRet)
377    throws BadLocationException
378  {
379    int retVal = -1;
380    switch (direction)
381      {
382      case SwingConstants.WEST:
383      case SwingConstants.EAST:
384        retVal = getNextEastWestVisualPositionFrom(pos, b, a, direction,
385                                                   biasRet);
386        break;
387      case SwingConstants.NORTH:
388      case SwingConstants.SOUTH:
389        retVal = getNextNorthSouthVisualPositionFrom(pos, b, a, direction,
390                                                     biasRet);
391        break;
392      default:
393        throw new IllegalArgumentException("Illegal value for direction.");
394      }
395    return retVal;
396  }
397
398  /**
399   * Returns the index of the child view that represents the specified
400   * model location.
401   *
402   * @param pos the model location for which to determine the child view index
403   * @param b the bias to be applied to <code>pos</code>
404   *
405   * @return the index of the child view that represents the specified
406   *         model location
407   */
408  public int getViewIndex(int pos, Position.Bias b)
409  {
410    if (b == Position.Bias.Backward)
411      pos -= 1;
412    int i = -1;
413    if (pos >= getStartOffset() && pos < getEndOffset())
414      i = getViewIndexAtPosition(pos);
415    return i;
416  }
417
418  /**
419   * Returns <code>true</code> if the specified point lies before the
420   * given <code>Rectangle</code>, <code>false</code> otherwise.
421   *
422   * &quot;Before&quot; is typically defined as being to the left or above.
423   *
424   * @param x the X coordinate of the point
425   * @param y the Y coordinate of the point
426   * @param r the rectangle to test the point against
427   *
428   * @return <code>true</code> if the specified point lies before the
429   *         given <code>Rectangle</code>, <code>false</code> otherwise
430   */
431  protected abstract boolean isBefore(int x, int y, Rectangle r);
432
433  /**
434   * Returns <code>true</code> if the specified point lies after the
435   * given <code>Rectangle</code>, <code>false</code> otherwise.
436   *
437   * &quot;After&quot; is typically defined as being to the right or below.
438   *
439   * @param x the X coordinate of the point
440   * @param y the Y coordinate of the point
441   * @param r the rectangle to test the point against
442   *
443   * @return <code>true</code> if the specified point lies after the
444   *         given <code>Rectangle</code>, <code>false</code> otherwise
445   */
446  protected abstract boolean isAfter(int x, int y, Rectangle r);
447
448  /**
449   * Returns the child <code>View</code> at the specified location.
450   *
451   * @param x the X coordinate
452   * @param y the Y coordinate
453   * @param r the inner allocation of this <code>BoxView</code> on entry,
454   *        the allocation of the found child on exit
455   *
456   * @return the child <code>View</code> at the specified location
457   */
458  protected abstract View getViewAtPoint(int x, int y, Rectangle r);
459
460  /**
461   * Computes the allocation for a child <code>View</code>. The parameter
462   * <code>a</code> stores the allocation of this <code>CompositeView</code>
463   * and is then adjusted to hold the allocation of the child view.
464   *
465   * @param index the index of the child <code>View</code>
466   * @param a the allocation of this <code>CompositeView</code> before the
467   *        call, the allocation of the child on exit
468   */
469  protected abstract void childAllocation(int index, Rectangle a);
470
471  /**
472   * Returns the child <code>View</code> that contains the given model
473   * position. The given <code>Rectangle</code> gives the parent's allocation
474   * and is changed to the child's allocation on exit.
475   *
476   * @param pos the model position to query the child <code>View</code> for
477   * @param a the parent allocation on entry and the child allocation on exit
478   *
479   * @return the child view at the given model position
480   */
481  protected View getViewAtPosition(int pos, Rectangle a)
482  {
483    View view = null;
484    int i = getViewIndexAtPosition(pos);
485    if (i >= 0 && i < getViewCount() && a != null)
486      {
487        view = getView(i);
488        childAllocation(i, a);
489      }
490    return view;
491  }
492
493  /**
494   * Returns the index of the child <code>View</code> for the given model
495   * position.
496   *
497   * @param pos the model position for whicht the child <code>View</code> is
498   *        queried
499   *
500   * @return the index of the child <code>View</code> for the given model
501   *         position
502   */
503  protected int getViewIndexAtPosition(int pos)
504  {
505    // We have a 1:1 mapping of elements to views here, so we forward
506    // this to the element.
507    Element el = getElement();
508    return el.getElementIndex(pos);
509  }
510
511  /**
512   * Returns the allocation that is given to this <code>CompositeView</code>
513   * minus this <code>CompositeView</code>'s insets.
514   *
515   * Also this translates from an immutable allocation to a mutable allocation
516   * that is typically reused and further narrowed, like in
517   * {@link #childAllocation}.
518   *
519   * @param a the allocation given to this <code>CompositeView</code>
520   *
521   * @return the allocation that is given to this <code>CompositeView</code>
522   *         minus this <code>CompositeView</code>'s insets or
523   *         <code>null</code> if a was <code>null</code>
524   */
525  protected Rectangle getInsideAllocation(Shape a)
526  {
527    if (a == null)
528      return null;
529
530    // Try to avoid allocation of Rectangle here.
531    Rectangle alloc = a instanceof Rectangle ? (Rectangle) a : a.getBounds();
532
533    // Initialize the inside allocation rectangle. This is done inside
534    // a synchronized block in order to avoid multiple threads creating
535    // this instance simultanously.
536    Rectangle inside = insideAllocation;
537    inside.x = alloc.x + getLeftInset();
538    inside.y = alloc.y + getTopInset();
539    inside.width = alloc.width - getLeftInset() - getRightInset();
540    inside.height = alloc.height - getTopInset() - getBottomInset();
541    return inside;
542  }
543
544  /**
545   * Sets the insets defined by attributes in <code>attributes</code>. This
546   * queries the attribute keys {@link StyleConstants#SpaceAbove},
547   * {@link StyleConstants#SpaceBelow}, {@link StyleConstants#LeftIndent} and
548   * {@link StyleConstants#RightIndent} and calls {@link #setInsets} to
549   * actually set the insets on this <code>CompositeView</code>.
550   *
551   * @param attributes the attributes from which to query the insets
552   */
553  protected void setParagraphInsets(AttributeSet attributes)
554  {
555    top = (short) StyleConstants.getSpaceAbove(attributes);
556    bottom = (short) StyleConstants.getSpaceBelow(attributes);
557    left = (short) StyleConstants.getLeftIndent(attributes);
558    right = (short) StyleConstants.getRightIndent(attributes);
559  }
560
561  /**
562   * Sets the insets of this <code>CompositeView</code>.
563   *
564   * @param t the top inset
565   * @param l the left inset
566   * @param b the bottom inset
567   * @param r the right inset
568   */
569  protected void setInsets(short t, short l, short b, short r)
570  {
571    top = t;
572    left = l;
573    bottom = b;
574    right = r;
575  }
576
577  /**
578   * Returns the left inset of this <code>CompositeView</code>.
579   *
580   * @return the left inset of this <code>CompositeView</code>
581   */
582  protected short getLeftInset()
583  {
584    return left;
585  }
586
587  /**
588   * Returns the right inset of this <code>CompositeView</code>.
589   *
590   * @return the right inset of this <code>CompositeView</code>
591   */
592  protected short getRightInset()
593  {
594    return right;
595  }
596
597  /**
598   * Returns the top inset of this <code>CompositeView</code>.
599   *
600   * @return the top inset of this <code>CompositeView</code>
601   */
602  protected short getTopInset()
603  {
604    return top;
605  }
606
607  /**
608   * Returns the bottom inset of this <code>CompositeView</code>.
609   *
610   * @return the bottom inset of this <code>CompositeView</code>
611   */
612  protected short getBottomInset()
613  {
614    return bottom;
615  }
616
617  /**
618   * Returns the next model location that is visible in north or south
619   * direction.
620   * This is used to determine the
621   * placement of the caret when navigating around the document with
622   * the arrow keys.
623   *
624   * @param pos the model position to start search from
625   * @param b the bias for <code>pos</code>
626   * @param a the allocated region for this view
627   * @param direction the direction from the current position, can be one of
628   *        the following:
629   *        <ul>
630   *        <li>{@link SwingConstants#NORTH}</li>
631   *        <li>{@link SwingConstants#SOUTH}</li>
632   *        </ul>
633   * @param biasRet the bias of the return value gets stored here
634   *
635   * @return the position inside the model that represents the next visual
636   *         location
637   *
638   * @throws BadLocationException if <code>pos</code> is not a valid location
639   *         inside the document model
640   * @throws IllegalArgumentException if <code>direction</code> is invalid
641   */
642  protected int getNextNorthSouthVisualPositionFrom(int pos, Position.Bias b,
643                                                    Shape a, int direction,
644                                                    Position.Bias[] biasRet)
645    throws BadLocationException
646  {
647    // TODO: It is unknown to me how this method has to be implemented and
648    // there is no specification telling me how to do it properly. Therefore
649    // the implementation was done for cases that are known.
650    //
651    // If this method ever happens to act silly for your particular case then
652    // it is likely that it is a cause of not knowing about your case when it
653    // was implemented first. You are free to fix the behavior.
654    //
655    // Here are the assumptions that lead to the implementation:
656    // If direction is NORTH chose the View preceding the one that contains the
657    // offset 'pos' (imagine the views are stacked on top of each other where
658    // the top is 0 and the bottom is getViewCount()-1.
659    // Consecutively when the direction is SOUTH the View following the one
660    // the offset 'pos' lies in is questioned.
661    //
662    // This limitation is described as PR 27345.
663    int index = getViewIndex(pos, b);
664    View v = null;
665
666    if (index == -1)
667      return pos;
668
669    switch (direction)
670    {
671      case NORTH:
672        // If we cannot calculate a proper offset return the one that was
673        // provided.
674        if (index <= 0)
675          return pos;
676
677        v = getView(index - 1);
678        break;
679      case SOUTH:
680        // If we cannot calculate a proper offset return the one that was
681        // provided.
682        if (index >= getViewCount() - 1)
683          return pos;
684
685        v = getView(index + 1);
686        break;
687      default:
688          throw new IllegalArgumentException();
689    }
690
691    return v.getNextVisualPositionFrom(pos, b, a, direction, biasRet);
692  }
693
694  /**
695   * Returns the next model location that is visible in east or west
696   * direction.
697   * This is used to determine the
698   * placement of the caret when navigating around the document with
699   * the arrow keys.
700   *
701   * @param pos the model position to start search from
702   * @param b the bias for <code>pos</code>
703   * @param a the allocated region for this view
704   * @param direction the direction from the current position, can be one of
705   *        the following:
706   *        <ul>
707   *        <li>{@link SwingConstants#EAST}</li>
708   *        <li>{@link SwingConstants#WEST}</li>
709   *        </ul>
710   * @param biasRet the bias of the return value gets stored here
711   *
712   * @return the position inside the model that represents the next visual
713   *         location
714   *
715   * @throws BadLocationException if <code>pos</code> is not a valid location
716   *         inside the document model
717   * @throws IllegalArgumentException if <code>direction</code> is invalid
718   */
719  protected int getNextEastWestVisualPositionFrom(int pos, Position.Bias b,
720                                                  Shape a, int direction,
721                                                  Position.Bias[] biasRet)
722    throws BadLocationException
723  {
724    // TODO: It is unknown to me how this method has to be implemented and
725    // there is no specification telling me how to do it properly. Therefore
726    // the implementation was done for cases that are known.
727    //
728    // If this method ever happens to act silly for your particular case then
729    // it is likely that it is a cause of not knowing about your case when it
730    // was implemented first. You are free to fix the behavior.
731    //
732    // Here are the assumptions that lead to the implementation:
733    // If direction is EAST increase the offset by one and ask the View to
734    // which that index belong to calculate the 'next visual position'.
735    // If the direction is WEST do the same with offset 'pos' being decreased
736    // by one.
737    // This behavior will fail in a right-to-left or bidi environment!
738    //
739    // This limitation is described as PR 27346.
740    int index;
741
742    View v = null;
743
744    switch (direction)
745    {
746      case EAST:
747        index = getViewIndex(pos + 1, b);
748        // If we cannot calculate a proper offset return the one that was
749        // provided.
750        if (index == -1)
751          return pos;
752
753        v  = getView(index);
754        break;
755      case WEST:
756        index = getViewIndex(pos - 1, b);
757        // If we cannot calculate a proper offset return the one that was
758        // provided.
759        if (index == -1)
760          return pos;
761
762        v  = getView(index);
763        break;
764      default:
765        throw new IllegalArgumentException();
766    }
767
768    return v.getNextVisualPositionFrom(pos,
769                                       b,
770                                       a,
771                                       direction,
772                                       biasRet);
773  }
774
775  /**
776   * Determines if the next view in horinzontal direction is located to
777   * the east or west of the view at position <code>pos</code>. Usually
778   * the <code>View</code>s are laid out from the east to the west, so
779   * we unconditionally return <code>false</code> here. Subclasses that
780   * support bidirectional text may wish to override this method.
781   *
782   * @param pos the position in the document
783   * @param bias the bias to be applied to <code>pos</code>
784   *
785   * @return <code>true</code> if the next <code>View</code> is located
786   *         to the EAST, <code>false</code> otherwise
787   */
788  protected boolean flipEastAndWestAtEnds(int pos, Position.Bias bias)
789  {
790    return false;
791  }
792}