home *** CD-ROM | disk | FTP | other *** search
/ Chip 1998 November / Chip_1998-11_cd.bin / tema / Cafe / jfc.bin / ParagraphView.java < prev    next >
Text File  |  1998-02-26  |  32KB  |  864 lines

  1. /*
  2.  * @(#)ParagraphView.java    1.40 98/02/01
  3.  * 
  4.  * Copyright (c) 1997 Sun Microsystems, Inc. All Rights Reserved.
  5.  * 
  6.  * This software is the confidential and proprietary information of Sun
  7.  * Microsystems, Inc. ("Confidential Information").  You shall not
  8.  * disclose such Confidential Information and shall use it only in
  9.  * accordance with the terms of the license agreement you entered into
  10.  * with Sun.
  11.  * 
  12.  * SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF THE
  13.  * SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
  14.  * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
  15.  * PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR ANY DAMAGES
  16.  * SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING
  17.  * THIS SOFTWARE OR ITS DERIVATIVES.
  18.  * 
  19.  */
  20. package com.sun.java.swing.text;
  21.  
  22. import java.util.Vector;
  23. import java.util.Properties;
  24. import java.awt.*;
  25. import com.sun.java.swing.event.*;
  26.  
  27. /**
  28.  * View of a simple line-wrapping paragraph that supports
  29.  * multiple fonts, colors, components, icons, etc.  It is
  30.  * basically a vertical box with a margin around it.  The 
  31.  * contents of the box are a bunch of rows which are special 
  32.  * horizontal boxes.  This view creates a collection of
  33.  * views that represent the child elements of the paragraph 
  34.  * element.  Each of these views are placed into a row 
  35.  * directly if they will fit, otherwise the <code>breakView</code>
  36.  * method is called to try and carve the view into pieces
  37.  * that fit.
  38.  *
  39.  * @author  Timothy Prinzing
  40.  * @author  Scott Violet
  41.  * @version 1.40 02/01/98
  42.  * @see     View
  43.  */
  44. public class ParagraphView extends BoxView implements TabExpander {
  45.  
  46.     /**
  47.      * Constructs a ParagraphView for the given element.
  48.      *
  49.      * @param elem the element that this view is responsible for
  50.      */
  51.     public ParagraphView(Element elem) {
  52.     super(elem, View.Y_AXIS);
  53.     AttributeSet attr = elem.getAttributes();
  54.     setParagraphInsets(attr);
  55.     justification = StyleConstants.getAlignment(attr);
  56.     lineSpacing = (int) StyleConstants.getLineSpacing(attr);
  57.     layoutSpan = Integer.MAX_VALUE;
  58.     }
  59.  
  60.     /**
  61.      * Loads all of the children to initialize the view.
  62.      * This is called by the <code>setParent</code> method.
  63.      * This is reimplemented to not load any children directly
  64.      * (as they are created in the process of formatting).
  65.      * This does create views to represent the child elements,
  66.      * but they are placed into a pool that is used in the 
  67.      * process of formatting.
  68.      *
  69.      * @param f the view factory
  70.      */
  71.     protected void loadChildren(ViewFactory f) {
  72.         layoutPool = new Vector();
  73.         Element e = getElement();
  74.         int n = e.getElementCount();
  75.         for (int i = 0; i < n; i++) {
  76.             layoutPool.addElement(f.create(e.getElement(i)));
  77.         }
  78.     }
  79.  
  80.     /**
  81.      * Fetches the child view that represents the given position in
  82.      * the model.  This is implemented to walk through the children
  83.      * looking for a range that contains the given position.  In this
  84.      * view the children do not have a one to one mapping with the
  85.      * child elements (ie. the children are actually rows that
  86.      * represent a portion of the element this view represents).
  87.      *
  88.      * @param pos  the search position 
  89.      * @param a  the allocation to the box on entry, and the
  90.      *   allocation of the view containing the position on exit
  91.      * @returns  the view representing the given position, or 
  92.      *   null if there isn't one
  93.      */
  94.     protected View getViewAtPosition(int pos, Rectangle a) {
  95.         int n = getViewCount();
  96.         for (int i = 0; i < n; i++) {
  97.             View v = getView(i);
  98.             int p0 = v.getStartOffset();
  99.             int p1 = v.getEndOffset();
  100.             if ((pos >= p0) && (pos < p1)) {
  101.                 // it's in this view.
  102.                 childAllocation(i, a);
  103.                 return v;
  104.             }
  105.         }
  106.         return null;
  107.     }
  108.  
  109.     /**
  110.      * Lays out the children.  If the layout span has changed,
  111.      * the rows are rebuilt.  The superclass functionality
  112.      * is called after checking and possibly rebuilding the
  113.      * rows.  If the height has changed, the 
  114.      * <code> preferenceChanged</code> method is called
  115.      * on the parent since the vertical preference is 
  116.      * rigid.
  117.      *
  118.      * @param width  the width to lay out against.  This is
  119.      *   the width inside of the inset area.
  120.      * @param height the height to lay out against (not used
  121.      *   by paragraph, but used by the superclass).  This
  122.      *   is the height inside of the inset area.
  123.      */
  124.     protected void layout(int width, int height) {
  125.         if (layoutSpan != width) {
  126.             int oldHeight = (int) getPreferredSpan(View.Y_AXIS);
  127.             rebuildRows(width);
  128.             int newHeight = (int) getPreferredSpan(View.Y_AXIS);
  129.             if (oldHeight != newHeight) {
  130.                 View p = getParent();
  131.                 p.preferenceChanged(this, false, true);
  132.             }
  133.         }
  134.  
  135.         // do normal box layout
  136.         super.layout(width, height);
  137.     }
  138.  
  139.     /** 
  140.      * Does a a full layout on this View.  This causes all of 
  141.      * the rows (child views) to be rebuilt to match the given 
  142.      * span of the given allocation.
  143.      *
  144.      * @param span  the length to layout against.
  145.      */
  146.     void rebuildRows(int span) {
  147.         layoutSpan = span;
  148.         int p0 = getStartOffset(); 
  149.         int p1 = getEndOffset();
  150.         removeAll();
  151.         while(p0 < p1) {
  152.             int old = p0;
  153.             // PENDING(prinz) The old rows should be reused and
  154.             // new ones created only if needed... and discarded
  155.             // only if not needed.
  156.             Row row = new Row(getElement());
  157.             append(row);
  158.  
  159.             // layout the row to the current span
  160.             layoutRow(row, p0);
  161.             p0 = row.getEndOffset();
  162.             if (p0 <= old) {
  163.                 throw new StateInvariantError("infinite loop in formatting");
  164.             }
  165.         }
  166.     }
  167.  
  168.     /**
  169.      * Creates a row of views that will fit within the 
  170.      * current layout span.
  171.      * 
  172.      * @param row the row to fill in with views.  This is assumed
  173.      *   to be empty on entry.
  174.      * @param pos  The current position in the children of
  175.      *   this views element from which to start.  
  176.      */
  177.     void layoutRow(Row row, int pos) {
  178.         int x = tabBase;
  179.         int spanLeft = layoutSpan;
  180.         int end = getEndOffset();
  181.         while (pos < end  && spanLeft > 0) {
  182.             View v = createView(pos);
  183.             int chunkSpan;
  184.             if (v instanceof TabableView) {
  185.                 chunkSpan = (int) ((TabableView)v).getTabbedSpan(x, this);
  186.             } else {
  187.                 chunkSpan = (int) v.getPreferredSpan(View.X_AXIS);
  188.             }
  189.             spanLeft -= chunkSpan;
  190.             x += chunkSpan;
  191.             row.append(v);
  192.             pos = v.getEndOffset();
  193.         }
  194.         if (spanLeft < 0) {
  195.             // This row is too long and needs to be adjusted.
  196.             adjustRow(row, layoutSpan);
  197.         } else if (row.getViewCount() == 0) {
  198.         // Impossible spec... put in whatever is left.
  199.             View v = createView(pos);
  200.         row.append(v);
  201.     }
  202.     }
  203.  
  204.     /**
  205.      * Adjust the given row if possible to fit within the
  206.      * layout span.  By default this will try to find the 
  207.      * highest break weight possible nearest the end of
  208.      * the row.  If a forced break is encountered, the
  209.      * break will be positioned there.
  210.      * 
  211.      * @param r the row to adjust to the current layout
  212.      *  span.
  213.      * @param desiredSpan the current layout span
  214.      */
  215.     protected void adjustRow(Row r, int desiredSpan) {
  216.         int n = r.getViewCount();
  217.         int span = 0;
  218.         int bestWeight = BadBreakWeight;
  219.         int bestSpan = 0;
  220.         int bestIndex = -1;
  221.         int bestOffset = 0;
  222.         View v;
  223.         for (int i = 0; i < n; i++) {
  224.             v = r.getView(i);
  225.             int spanLeft = desiredSpan - span;
  226.             int w = v.getBreakWeight(X_AXIS, span, spanLeft);
  227.             if (w >= bestWeight) {
  228.                 bestWeight = w;
  229.                 bestIndex = i;
  230.                 bestSpan = span;
  231.                 if (w >= ForcedBreakWeight) {
  232.                     // it's a forced break, so there is
  233.                     // no point in searching further.
  234.                     break;
  235.                 }
  236.             }
  237.             span += v.getPreferredSpan(X_AXIS);
  238.         }
  239.         if (bestIndex < 0) {
  240.             // there is nothing that can be broken, leave
  241.             // it in it's current state.
  242.             return;
  243.         }
  244.  
  245.         // Break the best candidate view, and patch up the row.
  246.         int spanLeft = desiredSpan - bestSpan;
  247.         v = r.getView(bestIndex);
  248.         v = v.breakView(X_AXIS, v.getStartOffset(), bestSpan, spanLeft);
  249.         View[] va = new View[1];
  250.         va[0] = v;
  251.         r.replace(bestIndex, n - bestIndex, va);
  252.     }
  253.  
  254.     /**
  255.      * Creates a view that can be used to represent the
  256.      * current chunk.  This is either the entire view from
  257.      * the pool of views being formatted, or it's the 
  258.      * remaining portion of the view
  259.      */
  260.     View createView(int pos) {
  261.         int childIndex = getElement().getElementIndex(pos);
  262.         View v = (View) layoutPool.elementAt(childIndex);
  263.         if (pos == v.getStartOffset()) {
  264.             // return the entire view
  265.             return v;
  266.         }
  267.  
  268.         // return the remaining portion
  269.         v = v.createFragment(pos, v.getEndOffset());
  270.         return v;
  271.     }
  272.  
  273.     // --- TabExpander methods ------------------------------------------
  274.  
  275.     /**
  276.      * Returns the next tab stop position given a reference position.
  277.      * This view implements the tab coordinate system, and calls
  278.      * <code>getTabbedSpan</code> on the logical children in the process 
  279.      * of layout to determine the desired span of the children.  The
  280.      * logical children can delegate their tab expansion upward to
  281.      * the paragraph which knows how to expand tabs. 
  282.      * <code>LabelView</code> is an example of a view that delegates
  283.      * it's tab expansion needs upward to the paragraph.
  284.      * <p>
  285.      * This is implemented to try and locate a <code>TabSet</code>
  286.      * in the paragraph element's attribute set.  If one can be
  287.      * found, it's settings will be used, otherwise a default expansion
  288.      * will be provided.  The base location for for tab expansion
  289.      * is the left inset from the paragraphs most recent allocation
  290.      * (which is what the layout of the children is based upon).
  291.      *
  292.      * @param x the position
  293.      * @param tabOffset the position within the text stream
  294.      *   that the tab occurred at.
  295.      * @return the trailing end of the tab expansion
  296.      * @see TabSet
  297.      * @see TabStop
  298.      * @see LabelView
  299.      */
  300.     public float nextTabStop(float x, int tabOffset) {
  301.         x -= tabBase;
  302.         TabSet tabs = StyleConstants.getTabSet
  303.             (getElement().getAttributes());
  304.  
  305.         if(tabs == null) {
  306.             // a tab every 72 pixels.
  307.             return (float)(tabBase + (((int)x / 72 + 1) * 72));
  308.         }
  309.         TabStop tab = tabs.getTabAfter(x + .01f);
  310.         if(tab == null) {
  311.             // no tab, do a default of 5 pixels.
  312.             // Should this cause a wrapping of the line?
  313.             return tabBase + x + 5.0f;
  314.         }
  315.         int alignment = tab.getAlignment();
  316.         int offset;
  317.         switch(alignment) {
  318.         default:
  319.         case TabStop.ALIGN_LEFT:
  320.             // Simple case, left tab.
  321.             return tabBase + tab.getPosition();
  322.         case TabStop.ALIGN_BAR:
  323.             // PENDING: what does this mean?
  324.             return tabBase + tab.getPosition();
  325.         case TabStop.ALIGN_RIGHT:
  326.         case TabStop.ALIGN_CENTER:
  327.             offset = findOffsetToCharactersInString(tabChars,
  328.                                                     tabOffset + 1);
  329.             break;
  330.         case TabStop.ALIGN_DECIMAL:
  331.             offset = findOffsetToCharactersInString(tabDecimalChars,
  332.                                                     tabOffset + 1);
  333.             break;
  334.         }
  335.         if (offset == -1) {
  336.             offset = getEndOffset();
  337.         }
  338.         float charsSize = getPartialSize(tabOffset + 1, offset);
  339.         switch(alignment) {
  340.         case TabStop.ALIGN_RIGHT:
  341.         case TabStop.ALIGN_DECIMAL:
  342.             // right and decimal are treated the same way, the new
  343.             // position will be the location of the tab less the
  344.             // partialSize.
  345.             return tabBase + Math.max(x, tab.getPosition() - charsSize);
  346.         case TabStop.ALIGN_CENTER: 
  347.             // Similar to right, but half the partialSize.
  348.             return tabBase + Math.max(x, tab.getPosition() - charsSize / 2.0f);
  349.         }
  350.         // will never get here!
  351.         return x;
  352.     }
  353.  
  354.     /**
  355.      * Returns the size used by the views between <code>startOffset</code>
  356.      * and <code>endOffset</code>. This uses getPartialView to calculate the
  357.      * size if the child view implements the TabableView interface. If a 
  358.      * size is needed and a View does not implement the TabableView
  359.      * interface, the preferredSpan will be used.
  360.      */
  361.     protected float getPartialSize(int startOffset, int endOffset) {
  362.         float size = 0.0f;
  363.         int viewIndex;
  364.         int numViews = getViewCount();
  365.         View view;
  366.         int viewEnd;
  367.         int tempEnd;
  368.  
  369.         // Have to search layoutPool!
  370.         // PENDING: when ParagraphView supports breaking location
  371.         // into layoutPool will have to change!
  372.         viewIndex = getElement().getElementIndex(startOffset);
  373.         numViews = layoutPool.size();
  374.         while(startOffset < endOffset && viewIndex < numViews) {
  375.             view = (View) layoutPool.elementAt(viewIndex++);
  376.             viewEnd = view.getEndOffset();
  377.             tempEnd = Math.min(endOffset, viewEnd);
  378.             if(view instanceof TabableView)
  379.                 size += ((TabableView)view).getPartialSpan(startOffset, tempEnd);
  380.             else if(startOffset == view.getStartOffset() &&
  381.                     tempEnd == view.getEndOffset())
  382.                 size += view.getPreferredSpan(View.X_AXIS);
  383.             else
  384.                 // PENDING: should we handle this better?
  385.                 return 0.0f;
  386.             startOffset = viewEnd;
  387.         }
  388.         return size;
  389.     }
  390.  
  391.     /**
  392.      * Finds the next character in the document with a character in
  393.      * <code>string</code>, starting at offset <code>start</code>. If
  394.      * there are no characters found, -1 will be returned.
  395.      */
  396.     protected int findOffsetToCharactersInString(char[] string,
  397.                                                  int start) {
  398.         int stringLength = string.length;
  399.         int end = getEndOffset();
  400.         Segment seg = new Segment();
  401.         try {
  402.             getDocument().getText(start, end - start, seg);
  403.         } catch (BadLocationException ble) {
  404.             return -1;
  405.         }
  406.         for(int counter = seg.offset, maxCounter = seg.offset + seg.count;
  407.             counter < maxCounter; counter++) {
  408.             char currentChar = seg.array[counter];
  409.             for(int subCounter = 0; subCounter < stringLength;
  410.                 subCounter++) {
  411.                 if(currentChar == string[subCounter])
  412.                     return counter - seg.offset + start;
  413.             }
  414.         }
  415.         // No match.
  416.         return -1;
  417.     }
  418.  
  419.     // ---- View methods ----------------------------------------------------
  420.  
  421.     /**
  422.      * Renders using the given rendering surface and area on that
  423.      * surface.  This is implemented to delgate to the superclass
  424.      * after stashing the base coordinate for tab calculations.
  425.      *
  426.      * @param g the rendering surface to use
  427.      * @param a the allocated region to render into
  428.      * @see View#paint
  429.      */
  430.     public void paint(Graphics g, Shape a) {
  431.         Rectangle alloc = a.getBounds();
  432.         tabBase = alloc.x + getLeftInset();
  433.         super.paint(g, a);
  434.     }
  435.  
  436.     /**
  437.      * Determines the preferred span for this view along an
  438.      * axis.  For the paragraph it's whatever it was formatted
  439.      * to along the x axis and whatever the box calculation is
  440.      * for the y axis.
  441.      *
  442.      * @param axis may be either X_AXIS or Y_AXIS
  443.      * @returns  the span the view would like to be rendered into.
  444.      *           Typically the view is told to render into the span
  445.      *           that is returned, although there is no guarantee.  
  446.      *           The parent may choose to resize or break the view.
  447.      * @exception IllegalArgumentException for an invalid axis
  448.      */
  449.     public float getPreferredSpan(int axis) {
  450.         switch (axis) {
  451.         case View.X_AXIS:
  452.             int cheapSpan = (int) Math.min((long) layoutSpan + 
  453.                                            (long) getLeftInset() + 
  454.                                            (long) getRightInset(),
  455.                                            Integer.MAX_VALUE);
  456.             return (layoutSpan != Integer.MAX_VALUE) ? 
  457.                 cheapSpan : super.getPreferredSpan(axis);
  458.         case View.Y_AXIS:
  459.             return super.getPreferredSpan(axis);
  460.         default:
  461.             throw new IllegalArgumentException("Invalid axis: " + axis);
  462.         }
  463.     }
  464.  
  465.     /**
  466.      * Determines the desired alignment for this view along an
  467.      * axis.  This is implemented to give the alignment to the
  468.      * center of the first row along the y axis, and the default
  469.      * along the x axis.
  470.      *
  471.      * @param axis may be either X_AXIS or Y_AXIS
  472.      * @returns the desired alignment.  This should be a value
  473.      *   between 0.0 and 1.0 where 0 indicates alignment at the
  474.      *   origin and 1.0 indicates alignment to the full span
  475.      *   away from the origin.  An alignment of 0.5 would be the
  476.      *   center of the view.
  477.      */
  478.     public float getAlignment(int axis) {
  479.         switch (axis) {
  480.         case View.Y_AXIS:
  481.             int paragraphSpan = (int) getPreferredSpan(View.Y_AXIS);
  482.             View v = getView(0);
  483.             int rowSpan = (int) v.getPreferredSpan(View.Y_AXIS);
  484.             float a = (paragraphSpan != 0) ? ((float)(rowSpan / 2)) / paragraphSpan : 0;
  485.             return a;
  486.         default:
  487.             return super.getAlignment(axis);
  488.         }
  489.     }
  490.  
  491.     /**
  492.      * Gets the resize weight.  A value of 0 or less is not resizable.
  493.      *
  494.      * @param axis may be either X_AXIS or Y_AXIS
  495.      * @return the weight
  496.      * @exception IllegalArgumentException for an invalid axis
  497.      */
  498.     public int getResizeWeight(int axis) {
  499.         switch (axis) {
  500.         case View.X_AXIS:
  501.             return 1;
  502.         case View.Y_AXIS:
  503.             return 0;
  504.         default:
  505.             throw new IllegalArgumentException("Invalid axis: " + axis);
  506.         }
  507.     }
  508.  
  509.     /**
  510.      * Breaks this view on the given axis at the given length.<p>
  511.      * ParagraphView instances are breakable along the Y_AXIS only, and only if
  512.      * <code>len</code> is after the first line.
  513.      *
  514.      * @param axis may be either X_AXIS or Y_AXIS
  515.      * @param len specifies where a potential break is desired
  516.      *  along the given axis
  517.      * @param a the current allocation of the view
  518.      * @return the fragment of the view that represents the
  519.      *  given span, if the view can be broken.  If the view
  520.      *  doesn't support breaking behavior, the view itself is
  521.      *  returned.
  522.      * @see View#breakView
  523.      */
  524.     public View breakView(int axis, float len, Shape a) {
  525.         if(axis == View.Y_AXIS) {
  526.             if(a != null) {
  527.                 Rectangle alloc = a.getBounds();
  528.                 setSize(alloc.width, alloc.height);
  529.             }
  530.             // Determine what row to break on.
  531.  
  532.             // PENDING(prinz) add break support
  533.             return this;
  534.         }
  535.         return this;
  536.     }
  537.  
  538.     /**
  539.      * ParagraphView instances are breakable along the Y_AXIS only, and 
  540.      * only if <code>len</code> is after the first row.  If the length
  541.      * is less than one row, a value of BadBreakWeight is returned.
  542.      *
  543.      * @param axis may be either X_AXIS or Y_AXIS
  544.      * @param len specifies where a potential break is desired
  545.      * @return a value indicating the attractiveness of breaking here
  546.      * @see View#getBreakWeight
  547.      */
  548.     public int getBreakWeight(int axis, float len) {
  549.         if(axis == View.Y_AXIS) {
  550.             // PENDING(prinz) make this return a reasonable value
  551.             // when paragraph breaking support is re-implemented.
  552.             // If less than one row, bad weight value should be 
  553.             // returned.
  554.             //return GoodBreakWeight;
  555.             return BadBreakWeight;
  556.         }
  557.         return BadBreakWeight;
  558.     }
  559.  
  560.     /**
  561.      * Gives notification that something was inserted into the document
  562.      * in a location that this view is responsible for.
  563.      *
  564.      * @param changes the change information from the associated document
  565.      * @param a the current allocation of the view
  566.      * @param f the factory to use to rebuild if the view has children
  567.      * @see View#insertUpdate
  568.      */
  569.     public void insertUpdate(DocumentEvent changes, Shape a, ViewFactory f) {
  570.         // update the pool of logical children
  571.         Element elem = getElement();
  572.         DocumentEvent.ElementChange ec = changes.getChange(elem);
  573.         if (ec != null) {
  574.             // the structure of this element changed.
  575.             updateLogicalChildren(ec, f);
  576.         }
  577.  
  578.         // find and forward if there is anything there to 
  579.         // forward to.  If children were removed then there was
  580.         // a replacement of the removal range and there is no
  581.         // need to forward.
  582.         if (ec == null || (ec.getChildrenRemoved().length == 0)) {
  583.             int pos = changes.getOffset();
  584.             int index = elem.getElementIndex(pos);
  585.             View v = (View) layoutPool.elementAt(index);
  586.             v.insertUpdate(changes, null, f);
  587.         }
  588.  
  589.         // force layout, should do something more intelligent about
  590.         // incurring damage and triggering a new layout.  This is just
  591.         // about as brute force as it can get.
  592.         layoutSpan = Integer.MAX_VALUE;
  593.         preferenceChanged(null, false, true);
  594.         Rectangle alloc = getInsideAllocation(a);
  595.         if (alloc != null) {
  596.             layout((int) alloc.width, (int) alloc.height);
  597.             Component host = getContainer();
  598.             host.repaint(alloc.x, alloc.y, alloc.width, alloc.height);
  599.         }
  600.     }
  601.  
  602.     /**
  603.      * Update the logical children to reflect changes made 
  604.      * to the element this view is responsible.  This updates
  605.      * the pool of views used for layout (ie. the views 
  606.      * representing the child elements of the element this
  607.      * view is responsible for).  This is called by the 
  608.      * <code>insertUpdate, removeUpdate, and changeUpdate</code>
  609.      * methods.
  610.      */
  611.     void updateLogicalChildren(DocumentEvent.ElementChange ec, ViewFactory f) {
  612.         int index = ec.getIndex();
  613.         Element[] removedElems = ec.getChildrenRemoved();
  614.         for (int i = 0; i < removedElems.length; i++) {
  615.         View v = (View) layoutPool.elementAt(index);
  616.         v.setParent(null);
  617.             layoutPool.removeElementAt(index);
  618.         }
  619.         Element[] addedElems = ec.getChildrenAdded();
  620.         for (int i = 0; i < addedElems.length; i++) {
  621.             layoutPool.insertElementAt(f.create(addedElems[i]),
  622.                                        index + i);
  623.         }
  624.     }
  625.  
  626.     /**
  627.      * Gives notification that something was removed from the document
  628.      * in a location that this view is responsible for.
  629.      *
  630.      * @param changes the change information from the associated document
  631.      * @param a the current allocation of the view
  632.      * @param f the factory to use to rebuild if the view has children
  633.      * @see View#removeUpdate
  634.      */
  635.     public void removeUpdate(DocumentEvent changes, Shape a, ViewFactory f) {
  636.         // update the pool of logical children
  637.         Element elem = getElement();
  638.         DocumentEvent.ElementChange ec = changes.getChange(elem);
  639.         if (ec != null) {
  640.             // the structure of this element changed.
  641.             updateLogicalChildren(ec, f);
  642.         }
  643.  
  644.         // find and forward if there is anything there to 
  645.         // forward to.  If children were added then there was
  646.         // a replacement of the removal range and there is no
  647.         // need to forward.
  648.         if (ec == null || (ec.getChildrenAdded().length == 0)) {
  649.             int pos = changes.getOffset();
  650.             int index = elem.getElementIndex(pos);
  651.             View v = (View) layoutPool.elementAt(index);
  652.             v.removeUpdate(changes, null, f);
  653.         }
  654.  
  655.         // force layout, should do something more intelligent about
  656.         // incurring damage and triggering a new layout.
  657.         layoutSpan = Integer.MAX_VALUE;
  658.         preferenceChanged(null, false, true);
  659.         if (a != null) {
  660.             Rectangle alloc = getInsideAllocation(a);
  661.             layout((int) alloc.width, (int) alloc.height);
  662.             Component host = getContainer();
  663.             host.repaint(alloc.x, alloc.y, alloc.width, alloc.height);
  664.         }
  665.     }
  666.  
  667.     /**
  668.      * Gives notification from the document that attributes were changed
  669.      * in a location that this view is responsible for.
  670.      *
  671.      * @param changes the change information from the associated document
  672.      * @param a the current allocation of the view
  673.      * @param f the factory to use to rebuild if the view has children
  674.      * @see View#changedUpdate
  675.      */
  676.     public void changedUpdate(DocumentEvent changes, Shape a, ViewFactory f) {
  677.         // update any property settings stored
  678.         AttributeSet attr = getElement().getAttributes();
  679.         setParagraphInsets(attr);
  680.         justification = StyleConstants.getAlignment(attr);
  681.         lineSpacing = (int) StyleConstants.getLineSpacing(attr);
  682.  
  683.         // update the pool of logical children
  684.         Element elem = getElement();
  685.         DocumentEvent.ElementChange ec = changes.getChange(elem);
  686.         if (ec != null) {
  687.             // the structure of this element changed.
  688.             updateLogicalChildren(ec, f);
  689.         }
  690.  
  691.         // forward to the logical children
  692.         int p0 = changes.getOffset();
  693.         int p1 = p0 + changes.getLength();
  694.         int index0 = elem.getElementIndex(p0);
  695.         int index1 = elem.getElementIndex(p1 - 1);
  696.         for (int i = index0; i <= index1; i++) {
  697.             View v = (View) layoutPool.elementAt(i);
  698.             v.changedUpdate(changes, null, f);
  699.         }
  700.  
  701.         // force layout, should do something more intelligent about
  702.         // incurring damage and triggering a new layout.
  703.         layoutSpan = Integer.MAX_VALUE;
  704.         preferenceChanged(null, false, true);
  705.         if (a != null) {
  706.             Rectangle alloc = getInsideAllocation(a);
  707.             layout((int) alloc.width, (int) alloc.height);
  708.             Component host = getContainer();
  709.             host.repaint(alloc.x, alloc.y, alloc.width, alloc.height);
  710.         }
  711.     }
  712.  
  713.     // --- variables -----------------------------------------------
  714.  
  715.     private int justification;
  716.     private int lineSpacing;
  717.  
  718.     /**
  719.      * Used by the TabExpander functionality to determine
  720.      * where to base the tab calculations.  This is basically
  721.      * the location of the left inset.
  722.      */
  723.     private int tabBase;
  724.  
  725.     /**
  726.      * Used by the layout process.  The span holds the
  727.      * length that has been formatted to. 
  728.      */
  729.     private int layoutSpan;
  730.  
  731.     /**
  732.      * These are the views that represent the child elements
  733.      * of the element this view represents.  These are not
  734.      * directly children of this view.  These are either 
  735.      * placed into the rows directly or used for the purpose
  736.      * of breaking into smaller chunks.
  737.      */
  738.     private Vector layoutPool;
  739.     
  740.     /** Used for searching for a tab. */
  741.     static char[] tabChars;
  742.     /** Used for searching for a tab or decimal character. */
  743.     static char[] tabDecimalChars;
  744.  
  745.     static {
  746.         tabChars = new char[1];
  747.         tabChars[0] = '\t';
  748.         tabDecimalChars = new char[2];
  749.         tabDecimalChars[0] = '\t';
  750.         tabDecimalChars[1] = '.';
  751.     }
  752.  
  753.     /**
  754.      * Internally created view that has the purpose of holding
  755.      * the views that represent the children of the paragraph
  756.      * that have been arranged in rows.
  757.      */
  758.     class Row extends BoxView {
  759.  
  760.         Row(Element elem) {
  761.             super(elem, View.X_AXIS);
  762.         }
  763.  
  764.         /**
  765.          * This is reimplemented to do nothing since the
  766.          * paragraph fills in the row with its needed
  767.          * children.
  768.          */
  769.         protected void loadChildren(ViewFactory f) {
  770.         }
  771.  
  772.         public float getAlignment(int axis) {
  773.             if (axis == View.X_AXIS) {
  774.                 switch (justification) {
  775.                 case StyleConstants.ALIGN_LEFT:
  776.                     return 0;
  777.                 case StyleConstants.ALIGN_RIGHT:
  778.                     return 1;
  779.                 case StyleConstants.ALIGN_CENTER:
  780.                 case StyleConstants.ALIGN_JUSTIFIED:
  781.                     return 0.5f;
  782.                 }
  783.             }
  784.             return super.getAlignment(axis);
  785.         }
  786.  
  787.         /**
  788.          * Provides a mapping from the document model coordinate space
  789.          * to the coordinate space of the view mapped to it.  This is
  790.          * implemented to let the superclass find the position along 
  791.          * the major axis and the allocation of the row is used 
  792.          * along the minor axis, so that even though the children 
  793.          * are different heights they all get the same caret height.
  794.          *
  795.          * @param pos the position to convert
  796.          * @param a the allocated region to render into
  797.          * @return the bounding box of the given position
  798.          * @exception BadLocationException  if the given position does not represent a
  799.          *   valid location in the associated document
  800.          * @see View#modelToView
  801.          */
  802.         public Shape modelToView(int pos, Shape a) throws BadLocationException {
  803.             Rectangle r = a.getBounds();
  804.             int height = r.height;
  805.             int y = r.y;
  806.             Shape loc = super.modelToView(pos, a);
  807.             r = loc.getBounds();
  808.             r.height = height;
  809.             r.y = y;
  810.             return r;
  811.         }
  812.  
  813.         /**
  814.          * Range represented by a row in the paragraph is only
  815.          * a subset of the total range of the paragraph element.
  816.          * @see View#getRange
  817.          */
  818.         public int getStartOffset() {
  819.             int n = getViewCount();
  820.             if (n > 0) {
  821.                 View v = getView(0);
  822.                 return v.getStartOffset();
  823.             }
  824.             return -1;
  825.         }
  826.  
  827.         public int getEndOffset() {
  828.             int n = getViewCount();
  829.             if (n > 0) {
  830.                 View v = getView(n-1);
  831.                 return v.getEndOffset();
  832.             }
  833.             return -1;
  834.         }
  835.  
  836.         /**
  837.          * Fetches the child view that represents the given position in
  838.          * the model.  This is implemented to walk through the children
  839.          * looking for a range that contains the given position.
  840.          * @param pos  The search position 
  841.          * @param a  The allocation to the box on entry, and the
  842.          *   allocation of the view containing the position on exit.
  843.          * @returns  The view representing the given position, or 
  844.          *   null if there isn't one.
  845.          */
  846.         protected View getViewAtPosition(int pos, Rectangle a) {
  847.             int n = getViewCount();
  848.             for (int i = 0; i < n; i++) {
  849.                 View v = getView(i);
  850.                 int p0 = v.getStartOffset();
  851.                 int p1 = v.getEndOffset();
  852.                 if ((pos >= p0) && (pos < p1)) {
  853.                     // it's in this view.
  854.                     this.childAllocation(i, a);
  855.                     return v;
  856.                 }
  857.             }
  858.             return null;
  859.         }
  860.  
  861.     }
  862.  
  863. }
  864.