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

  1. /*
  2.  * @(#)WrappedPlainView.java    1.9 98/01/29
  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 plain text (text with only one font and color)
  29.  * that does line-wrapping.  This view expects that it's
  30.  * associated element has child elements that represent
  31.  * the lines it should be wrapping.  It is implemented
  32.  * as a vertical box that contains logical line views.
  33.  * The logical line views are nested classes that render
  34.  * the logical line as multiple physical line if the logical
  35.  * line is too wide to fit within the allocation.  The
  36.  * line views draw upon the outer class for it's state
  37.  * to reduce their memory requirements.
  38.  * <p>
  39.  * The line views do all of their rendering through the
  40.  * <code>drawLine</code> method which in turn does all of
  41.  * it's rendering through the <code>drawSelectedText</code>
  42.  * and <code>drawUnselectedText</code> methods.  This 
  43.  * enables subclasses to easily specialize the rendering
  44.  * without concern for the layout aspects.
  45.  *
  46.  * @author  Timothy Prinzing
  47.  * @version 1.9 01/29/98
  48.  * @see     View
  49.  */
  50. public class WrappedPlainView extends BoxView implements TabExpander {
  51.  
  52.     public WrappedPlainView(Element elem) {
  53.     super(elem, Y_AXIS);
  54.     lineBuffer = new Segment();
  55.     layoutWidth = Short.MAX_VALUE;
  56.     }
  57.  
  58.     /**
  59.      * Returns the tab size set for the document, defaulting to 8.
  60.      *
  61.      * @return the tab size
  62.      */
  63.     protected int getTabSize() {
  64.         Integer i = (Integer) getDocument().getProperty(PlainDocument.tabSizeAttribute);
  65.         int size = (i != null) ? i.intValue() : 8;
  66.         return size;
  67.     }
  68.  
  69.     /**
  70.      * Renders a line of text, suppressing whitespace at the end
  71.      * and exanding any tabs.  This is implemented to make calls
  72.      * to the methods <code>drawUnselectedText</code> and 
  73.      * <code>drawSelectedText</code> so that the way selected and 
  74.      * unselected text are rendered can be customized.
  75.      *
  76.      * @param p0 the starting document location to use
  77.      * @param p1 the ending document location to use
  78.      * @param g the graphics context
  79.      * @param x the starting X position
  80.      * @param y the starting Y position
  81.      * @see #drawUnselectedText
  82.      * @see #drawSelectedText
  83.      */
  84.     protected void drawLine(int p0, int p1, Graphics g, int x, int y) {
  85.         try {
  86.             p1 = Math.min(getDocument().getLength(), p1);
  87.             if (sel0 == sel1) {
  88.                 // no selection
  89.                 drawUnselectedText(g, x, y, p0, p1);
  90.             } else if ((p0 >= sel0 && p0 <= sel1) && (p1 >= sel0 && p1 <= sel1)) {
  91.                 drawSelectedText(g, x, y, p0, p1);
  92.             } else if (sel0 >= p0 && sel0 <= p1) {
  93.                 if (sel1 >= p0 && sel1 <= p1) {
  94.                     x = drawUnselectedText(g, x, y, p0, sel0);
  95.                     x = drawSelectedText(g, x, y, sel0, sel1);
  96.                     drawUnselectedText(g, x, y, sel1, p1);
  97.                 } else {
  98.                     x = drawUnselectedText(g, x, y, p0, sel0);
  99.                     drawSelectedText(g, x, y, sel0, p1);
  100.                 }
  101.             } else if (sel1 >= p0 && sel1 <= p1) {
  102.                 x = drawSelectedText(g, x, y, p0, sel1);
  103.                 drawUnselectedText(g, x, y, sel1, p1);
  104.             } else {
  105.                 drawUnselectedText(g, x, y, p0, p1);
  106.             }
  107.         } catch (BadLocationException e) {
  108.             throw new StateInvariantError("Can't render: " + p0 + "," + p1);
  109.         }
  110.     }
  111.  
  112.     /**
  113.      * Renders the given range in the model as normal unselected
  114.      * text.  
  115.      *
  116.      * @param g the graphics context
  117.      * @param x the starting X coordinate
  118.      * @param y the starting Y coordinate
  119.      * @param p0 the beginning position in the model
  120.      * @param p1 the ending position in the model
  121.      * @returns the location of the end of the range
  122.      * @exception BadLocationException if the range is invalid
  123.      */
  124.     protected int drawUnselectedText(Graphics g, int x, int y, 
  125.                                      int p0, int p1) throws BadLocationException {
  126.         g.setColor(unselected);
  127.         Document doc = getDocument();
  128.         doc.getText(p0, p1 - p0, lineBuffer);
  129.         return Utilities.drawTabbedText(lineBuffer, x, y, g, this, p0);
  130.     }
  131.  
  132.     /**
  133.      * Renders the given range in the model as selected text.  This
  134.      * is implemented to render the text in the color specified in
  135.      * the hosting component.  It assumes the highlighter will render
  136.      * the selected background.
  137.      *
  138.      * @param g the graphics context
  139.      * @param x the starting X coordinate
  140.      * @param y the starting Y coordinate
  141.      * @param p0 the beginning position in the model
  142.      * @param p1 the ending position in the model
  143.      * @returns the location of the end of the range.
  144.      * @exception BadLocationException if the range is invalid
  145.      */
  146.     protected int drawSelectedText(Graphics g, int x, 
  147.                                    int y, int p0, int p1) throws BadLocationException {
  148.         g.setColor(selected);
  149.         Document doc = getDocument();
  150.         doc.getText(p0, p1 - p0, lineBuffer);
  151.         return Utilities.drawTabbedText(lineBuffer, x, y, g, this, p0);
  152.     }
  153.  
  154.     /**
  155.      * Gives access to a buffer that can be used to fetch 
  156.      * text from the associated document.
  157.      *
  158.      * @returns the buffer
  159.      */
  160.     protected final Segment getLineBuffer() {
  161.         return lineBuffer;
  162.     }
  163.  
  164.     /**
  165.      * Loads all of the children to initialize the view.
  166.      * This is called by the <code>setParent</code> method.
  167.      * Subclasses can reimplement this to initialize their
  168.      * child views in a different manner.  The default
  169.      * implementation creates a child view for each 
  170.      * child element.
  171.      *
  172.      * @param f the view factory
  173.      */
  174.     protected void loadChildren(ViewFactory f) {
  175.         Element e = getElement();
  176.         int n = e.getElementCount();
  177.         if (n > 0) {
  178.             View[] added = new View[n];
  179.             for (int i = 0; i < n; i++) {
  180.                 added[i] = new WrappedLine(e.getElement(i));
  181.             }
  182.             replace(0, 0, added);
  183.         }
  184.     }
  185.  
  186.     /**
  187.      * Update the child views in response to a 
  188.      * document event.
  189.      */
  190.     void updateChildren(DocumentEvent e, Shape a) {
  191.         Element elem = getElement();
  192.         DocumentEvent.ElementChange ec = e.getChange(elem);
  193.         if (ec != null) {
  194.             // the structure of this element changed.
  195.             Element[] removedElems = ec.getChildrenRemoved();
  196.             Element[] addedElems = ec.getChildrenAdded();
  197.             View[] added = new View[addedElems.length];
  198.             for (int i = 0; i < addedElems.length; i++) {
  199.                 added[i] = new WrappedLine(addedElems[i]);
  200.             }
  201.             replace(ec.getIndex(), removedElems.length, added);
  202.  
  203.             // should damge a little more intelligently.
  204.             if (a != null) {
  205.                 preferenceChanged(null, true, true);
  206.                 getContainer().repaint();
  207.             }
  208.         }
  209.  
  210.     // update font metrics which may be used by the child views
  211.     updateMetrics();
  212.     }
  213.  
  214.     final void updateMetrics() {
  215.     Component host = getContainer();
  216.     Font f = host.getFont();
  217.     metrics = host.getFontMetrics(f);
  218.     tabSize = getTabSize() * metrics.charWidth('m');
  219.     }
  220.  
  221.     // --- TabExpander methods ------------------------------------------
  222.  
  223.     /**
  224.      * Returns the next tab stop position after a given reference position.
  225.      * This implementation does not support things like centering so it
  226.      * ignores the tabOffset argument.
  227.      *
  228.      * @param x the current position
  229.      * @param tabOffset the position within the text stream
  230.      *   that the tab occurred at.
  231.      * @return the tab stop, measured in points
  232.      */
  233.     public float nextTabStop(float x, int tabOffset) {
  234.         int ntabs = ((int) x) / tabSize;
  235.         return (ntabs + 1) * tabSize;
  236.     }
  237.  
  238.     
  239.     // --- View methods -------------------------------------
  240.  
  241.     /**
  242.      * Renders using the given rendering surface and area 
  243.      * on that surface.  This is implemented to stash the
  244.      * selection positions, selection colors, and font
  245.      * metrics for the nested lines to use.
  246.      *
  247.      * @param g the rendering surface to use
  248.      * @param a the allocated region to render into
  249.      *
  250.      * @see View#paint
  251.      */
  252.     public void paint(Graphics g, Shape a) {
  253.     Rectangle alloc = a.getBounds();
  254.     if (alloc.width != layoutWidth) {
  255.         layoutWidth = alloc.width;
  256.         preferenceChanged(null, true, true);
  257.     }
  258.     JTextComponent host = (JTextComponent) getContainer();
  259.     sel0 = host.getSelectionStart();
  260.     sel1 = host.getSelectionEnd();
  261.     unselected = (host.isEnabled()) ? 
  262.         host.getForeground() : host.getDisabledTextColor();
  263.     Caret c = host.getCaret();
  264.         selected = c.isSelectionVisible() ? host.getSelectedTextColor() : unselected;
  265.     updateMetrics();
  266.     g.setFont(host.getFont());
  267.  
  268.         // superclass paints the children
  269.         super.paint(g, a);
  270.     }
  271.  
  272.     /**
  273.      * Sets the size of the view.  If the size has changed, layout
  274.      * is redone.  The size is the full size of the view including
  275.      * the inset areas.
  276.      *
  277.      * @param width the width
  278.      * @param height the height
  279.      */
  280.     public void setSize(float width, float height) {
  281.     updateMetrics();
  282.     super.setSize(width, height);
  283.     }
  284.       
  285.     /**
  286.      * Determines the preferred span for this view along an
  287.      * axis.  This is implemented to provide the superclass
  288.      * behavior after first making sure that the current font
  289.      * metrics are cached (for the nested lines which use
  290.      * the metrics to determine the height of the potentially
  291.      * wrapped lines).
  292.      *
  293.      * @param axis may be either X_AXIS or Y_AXIS
  294.      * @returns  the span the view would like to be rendered into.
  295.      *           Typically the view is told to render into the span
  296.      *           that is returned, although there is no guarantee.  
  297.      *           The parent may choose to resize or break the view.
  298.      * @see View#getPreferredSpan
  299.      */
  300.     public float getPreferredSpan(int axis) {
  301.     updateMetrics();
  302.     return super.getPreferredSpan(axis);
  303.     }
  304.  
  305.     /**
  306.      * Gives notification that something was inserted into the 
  307.      * document in a location that this view is responsible for.
  308.      * This is implemented to simply update the children.
  309.      *
  310.      * @param e the change information from the associated document
  311.      * @param a the current allocation of the view
  312.      * @param f the factory to use to rebuild if the view has children
  313.      * @see View#insertUpdate
  314.      */
  315.     public void insertUpdate(DocumentEvent e, Shape a, ViewFactory f) {
  316.         updateChildren(e, a);
  317.  
  318.         Rectangle alloc = ((a != null) && isAllocationValid()) ? 
  319.             getInsideAllocation(a) : null;
  320.         int pos = e.getOffset();
  321.         View v = getViewAtPosition(pos, alloc);
  322.         if (v != null) {
  323.             v.insertUpdate(e, alloc, f);
  324.         }
  325.     }
  326.  
  327.     /**
  328.      * Gives notification that something was removed from the 
  329.      * document in a location that this view is responsible for.
  330.      * This is implemented to simply update the children.
  331.      *
  332.      * @param e the change information from the associated document
  333.      * @param a the current allocation of the view
  334.      * @param f the factory to use to rebuild if the view has children
  335.      * @see View#removeUpdate
  336.      */
  337.     public void removeUpdate(DocumentEvent e, Shape a, ViewFactory f) {
  338.         updateChildren(e, a);
  339.  
  340.         Rectangle alloc = ((a != null) && isAllocationValid()) ? 
  341.             getInsideAllocation(a) : null;
  342.         int pos = e.getOffset();
  343.         View v = getViewAtPosition(pos, alloc);
  344.         if (v != null) {
  345.             v.removeUpdate(e, alloc, f);
  346.         }
  347.     }
  348.  
  349.     /**
  350.      * Gives notification from the document that attributes were changed
  351.      * in a location that this view is responsible for.
  352.      *
  353.      * @param e the change information from the associated document
  354.      * @param a the current allocation of the view
  355.      * @param f the factory to use to rebuild if the view has children
  356.      * @see View#changedUpdate
  357.      */
  358.     public void changedUpdate(DocumentEvent e, Shape a, ViewFactory f) {
  359.         updateChildren(e, a);
  360.     }
  361.  
  362.     // --- variables -------------------------------------------
  363.  
  364.     FontMetrics metrics;
  365.     Segment lineBuffer;
  366.     int layoutWidth;
  367.     int tabSize;
  368.     
  369.     int sel0;
  370.     int sel1;
  371.     Color unselected;
  372.     Color selected;
  373.  
  374.  
  375.     /**
  376.      * Simple view of a line that wraps if it doesn't
  377.      * fit withing the horizontal space allocated.
  378.      * This class tries to be lightweight by 
  379.      * carrying little state of it's own and 
  380.      * sharing the state of the outer class 
  381.      * with it's sibblings.
  382.      */
  383.     class WrappedLine extends View {
  384.  
  385.         WrappedLine(Element elem) {
  386.             super(elem);
  387.         }
  388.  
  389.         /**
  390.          * Load the text buffer with the given range
  391.          * of text.  This is used by the fragments 
  392.          * broken off of this view as well as this 
  393.          * view itself.
  394.          */
  395.         final void loadText(int p0, int p1) {
  396.             try {
  397.                 Document doc = getDocument();
  398.                 doc.getText(p0, p1 - p0, lineBuffer);
  399.             } catch (BadLocationException bl) {
  400.                 throw new StateInvariantError("Can't get line text");
  401.             }
  402.         }
  403.  
  404.         /**
  405.          * Calculate the number of lines that will be rendered
  406.          * by logical line when it is wrapped.
  407.          */
  408.         final int calculateLineCount() {
  409.             int nlines = 0;
  410.             int p1 = getEndOffset();
  411.             for (int p0 = getStartOffset(); p0 < p1; ) {
  412.                 nlines += 1;
  413.                 loadText(p0, p1);
  414.                 int p = p0 + Utilities.getTabbedTextOffset(lineBuffer, metrics, 
  415.                                                            0, layoutWidth, 
  416.                                                            WrappedPlainView.this, p0);
  417.                 p0 = (p == p0) ? p1 : p;
  418.             }
  419.             return nlines;
  420.         }
  421.  
  422.         /**
  423.          * Determines the preferred span for this view along an
  424.          * axis.
  425.          *
  426.          * @param axis may be either X_AXIS or Y_AXIS
  427.          * @returns  the span the view would like to be rendered into.
  428.          *           Typically the view is told to render into the span
  429.          *           that is returned, although there is no guarantee.  
  430.          *           The parent may choose to resize or break the view.
  431.          * @see View#getPreferredSpan
  432.          */
  433.         public float getPreferredSpan(int axis) {
  434.             switch (axis) {
  435.             case View.X_AXIS:
  436.                 return layoutWidth;
  437.             case View.Y_AXIS:
  438.                 nlines = calculateLineCount();
  439.                 int h = nlines * metrics.getHeight();
  440.                 return h;
  441.             default:
  442.                 throw new IllegalArgumentException("Invalid axis: " + axis);
  443.             }
  444.         }
  445.  
  446.         /**
  447.          * Renders using the given rendering surface and area on that
  448.          * surface.  The view may need to do layout and create child
  449.          * views to enable itself to render into the given allocation.
  450.          *
  451.          * @param g the rendering surface to use
  452.          * @param a the allocated region to render into
  453.          * @see View#paint
  454.          */
  455.         public void paint(Graphics g, Shape a) {
  456.             Rectangle alloc = a.getBounds();
  457.             int y = alloc.y + metrics.getAscent();
  458.             int x = alloc.x;
  459.  
  460.             int p1 = getEndOffset();
  461.             for (int p0 = getStartOffset(); p0 < p1; ) {
  462.                 loadText(p0, p1);
  463.                 int p = p0 + Utilities.getTabbedTextOffset(lineBuffer, metrics, 
  464.                                                            x, layoutWidth, 
  465.                                                            WrappedPlainView.this, p0);
  466.                 drawLine(p0, p, g, x, y);
  467.                 
  468.                 p0 = (p == p0) ? p1 : p;
  469.                 y += metrics.getHeight();
  470.             }
  471.         }
  472.  
  473.         /**
  474.          * Provides a mapping from the document model coordinate space
  475.          * to the coordinate space of the view mapped to it.
  476.          *
  477.          * @param pos the position to convert
  478.          * @param a the allocated region to render into
  479.          * @return the bounding box of the given position is returned
  480.          * @exception BadLocationException  if the given position does not represent a
  481.          *   valid location in the associated document
  482.          * @see View#modelToView
  483.          */
  484.         public Shape modelToView(int pos, Shape a) throws BadLocationException {
  485.             Rectangle alloc = a.getBounds();
  486.             alloc.height = metrics.getHeight();
  487.             alloc.width = 1;
  488.             
  489.             int p1 = getEndOffset();
  490.             for (int p0 = getStartOffset(); p0 < p1; ) {
  491.                 loadText(p0, p1);
  492.                 int p = p0 + Utilities.getTabbedTextOffset(lineBuffer, metrics, 
  493.                                                            alloc.x, alloc.x + layoutWidth, 
  494.                                                            WrappedPlainView.this, p0);
  495.                 if ((pos >= p0) && (pos < p)) {
  496.                     // it's in this line
  497.                     loadText(p0, pos);
  498.                     alloc.x += Utilities.getTabbedTextWidth(lineBuffer, metrics, 
  499.                                                             alloc.x, 
  500.                                                             WrappedPlainView.this, p0);
  501.                     return alloc;
  502.                 }
  503.                 
  504.                 p0 = (p == p0) ? p1 : p;
  505.                 alloc.y += alloc.height;
  506.             }
  507.             throw new BadLocationException(null, pos);
  508.         }
  509.  
  510.         /**
  511.          * Provides a mapping from the view coordinate space to the logical
  512.          * coordinate space of the model.
  513.          *
  514.          * @param x the X coordinate
  515.          * @param y the Y coordinate
  516.          * @param a the allocated region to render into
  517.          * @return the location within the model that best represents the
  518.          *  given point in the view
  519.          * @see View#viewToModel
  520.          */
  521.         public int viewToModel(float fx, float fy, Shape a) {
  522.         Rectangle alloc = a.getBounds();
  523.         Document doc = getDocument();
  524.         int x = (int) fx;
  525.         int y = (int) fy;
  526.         if (y < alloc.y) {
  527.         // above the area covered by this icon, so the the position
  528.         // is assumed to be the start of the coverage for this view.
  529.         return getStartOffset();
  530.         } else if (y > alloc.y + alloc.height) {
  531.         // below the area covered by this icon, so the the position
  532.         // is assumed to be the end of the coverage for this view.
  533.         return getEndOffset() - 1;
  534.         } else {
  535.         // positioned within the coverage of this view vertically,
  536.         // so we figure out which line the point corresponds to.
  537.         // if the line is greater than the number of lines contained, then
  538.         // simply use the last line as it represents the last possible place
  539.         // we can position to.
  540.         alloc.height = metrics.getHeight();
  541.         int p1 = getEndOffset();
  542.         for (int p0 = getStartOffset(); p0 < p1; ) {
  543.             loadText(p0, p1);
  544.             int p = p0 + Utilities.getTabbedTextOffset(lineBuffer, metrics, 
  545.                                    alloc.x, alloc.x + layoutWidth, 
  546.                                    WrappedPlainView.this, p0);
  547.             if ((y >= alloc.y) && (y < (alloc.y + alloc.height))) {
  548.             // it's in this line
  549.             if (x < alloc.x) {
  550.                 // point is to the left of the line
  551.                 return p0;
  552.             } else if (x > alloc.x + alloc.width) {
  553.                 // point is to the right of the line
  554.                 return p;
  555.             } else {
  556.                 // Determine the offset into the text
  557.                 int n = Utilities.getTabbedTextOffset(lineBuffer, metrics, 
  558.                                     alloc.x, x, 
  559.                                     WrappedPlainView.this, p0);
  560.                 return Math.min(p0 + n, p1 - 1);
  561.             }
  562.             }
  563.             
  564.             p0 = (p == p0) ? p1 : p;
  565.             alloc.y += alloc.height;
  566.         }
  567.         return getEndOffset() - 1;
  568.         }
  569.     }
  570.  
  571.         public void insertUpdate(DocumentEvent e, Shape a, ViewFactory f) {
  572.             int nlines = calculateLineCount();
  573.             if (this.nlines != nlines) {
  574.                 this.nlines = nlines;
  575.                 WrappedPlainView.this.preferenceChanged(this, false, true);
  576.             } else if (a != null) {
  577.                 Component c = getContainer();
  578.                 Rectangle alloc = a.getBounds();
  579.                 c.repaint(alloc.x, alloc.y, alloc.width, alloc.height);
  580.             }
  581.         }
  582.  
  583.         public void removeUpdate(DocumentEvent e, Shape a, ViewFactory f) {
  584.             int nlines = calculateLineCount();
  585.             if (this.nlines != nlines) {
  586.                 this.nlines = nlines;
  587.                 WrappedPlainView.this.preferenceChanged(this, false, true);
  588.             } else if (a != null) {
  589.                 Component c = getContainer();
  590.                 Rectangle alloc = a.getBounds();
  591.                 c.repaint(alloc.x, alloc.y, alloc.width, alloc.height);
  592.             }
  593.         }
  594.  
  595.         // --- variables ---------------------------------------
  596.  
  597.         int nlines;
  598.     }
  599.     
  600. }
  601.  
  602.