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

  1. /*
  2.  * @(#)PlainView.java    1.46 98/02/05
  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.  * Implements View interface for a simple multi-line text view 
  29.  * that has text in one font and color.  The view represents each
  30.  * child element as a line of text.
  31.  *
  32.  * @author  Timothy Prinzing
  33.  * @version 1.46 02/05/98
  34.  * @see     View
  35.  */
  36. public class PlainView extends View implements TabExpander {
  37.  
  38.     /**
  39.      * Constructs a new view wrapped on an element.
  40.      *
  41.      * @param elem the element
  42.      */
  43.     public PlainView(Element elem) {
  44.         super(elem);
  45.         lineBuffer = new Segment();
  46.     }
  47.  
  48.     /**
  49.      * Returns the tab size set for the document, defaulting to 8.
  50.      *
  51.      * @return the tab size
  52.      */
  53.     protected int getTabSize() {
  54.         Integer i = (Integer) getDocument().getProperty(PlainDocument.tabSizeAttribute);
  55.         int size = (i != null) ? i.intValue() : 8;
  56.         return size;
  57.     }
  58.  
  59.     /**
  60.      * Returns the max number of characters per line set for the document,
  61.      *
  62.      * @return the max number
  63.      */
  64.     protected int getLineLimit() {
  65.         Integer lineLimit = (Integer) getDocument().getProperty(PlainDocument.lineLimitAttribute);
  66.         if(lineLimit == null) {
  67.             int width = 0;
  68.  
  69.             int totalLines = getElement().getElementCount();
  70.             for(int i = 0; i < totalLines; i++) {
  71.                 Element line = getElement().getElement(i);
  72.                 int p0 = line.getStartOffset();
  73.                 int p1 = line.getEndOffset();
  74.                 if(p1 - p0 > width) {
  75.                     width = p1 - p0;
  76.                 }
  77.             }
  78.             lineLimit = new Integer(width);
  79.             getDocument().putProperty(PlainDocument.lineLimitAttribute, lineLimit);
  80.         }
  81.         return lineLimit.intValue();
  82.     }
  83.  
  84.     /**
  85.      * Renders a line of text, suppressing whitespace at the end
  86.      * and exanding any tabs.  This is implemented to make calls
  87.      * to the methods <code>drawUnselectedText</code> and 
  88.      * <code>drawSelectedText</code> so that the way selected and 
  89.      * unselected text are rendered can be customized.
  90.      *
  91.      * @param lineIndex the line to draw
  92.      * @param g the graphics context
  93.      * @param x the starting X position
  94.      * @param y the starting Y position
  95.      * @see #drawUnselectedText
  96.      * @see #drawSelectedText
  97.      */
  98.     protected void drawLine(int lineIndex, Graphics g, int x, int y) {
  99.         try {
  100.             Element line = getElement().getElement(lineIndex);
  101.             int p0 = line.getStartOffset();
  102.             int p1 = line.getEndOffset();
  103.             p1 = Math.min(getDocument().getLength(), p1);
  104.             if (sel0 == sel1) {
  105.                 // no selection
  106.                 drawUnselectedText(g, x, y, p0, p1);
  107.             } else if ((p0 >= sel0 && p0 <= sel1) && (p1 >= sel0 && p1 <= sel1)) {
  108.                 drawSelectedText(g, x, y, p0, p1);
  109.             } else if (sel0 >= p0 && sel0 <= p1) {
  110.                 if (sel1 >= p0 && sel1 <= p1) {
  111.                     x = drawUnselectedText(g, x, y, p0, sel0);
  112.                     x = drawSelectedText(g, x, y, sel0, sel1);
  113.                     drawUnselectedText(g, x, y, sel1, p1);
  114.                 } else {
  115.                     x = drawUnselectedText(g, x, y, p0, sel0);
  116.                     drawSelectedText(g, x, y, sel0, p1);
  117.                 }
  118.             } else if (sel1 >= p0 && sel1 <= p1) {
  119.                 x = drawSelectedText(g, x, y, p0, sel1);
  120.                 drawUnselectedText(g, x, y, sel1, p1);
  121.             } else {
  122.                 drawUnselectedText(g, x, y, p0, p1);
  123.             }
  124.         } catch (BadLocationException e) {
  125.             throw new StateInvariantError("Can't render line: " + lineIndex);
  126.         }
  127.     }
  128.  
  129.     /**
  130.      * Renders the given range in the model as normal unselected
  131.      * text.  
  132.      *
  133.      * @param g the graphics context
  134.      * @param x the starting X coordinate
  135.      * @param y the starting Y coordinate
  136.      * @param p0 the beginning position in the model
  137.      * @param p1 the ending position in the model
  138.      * @returns the location of the end of the range
  139.      * @exception BadLocationException if the range is invalid
  140.      */
  141.     protected int drawUnselectedText(Graphics g, int x, int y, 
  142.                                      int p0, int p1) throws BadLocationException {
  143.         g.setColor(unselected);
  144.         Document doc = getDocument();
  145.         doc.getText(p0, p1 - p0, lineBuffer);
  146.         return Utilities.drawTabbedText(lineBuffer, x, y, g, this, p0);
  147.     }
  148.  
  149.     /**
  150.      * Renders the given range in the model as selected text.  This
  151.      * is implemented to render the text in the color specified in
  152.      * the hosting component.  It assumes the highlighter will render
  153.      * the selected background.
  154.      *
  155.      * @param g the graphics context
  156.      * @param x the starting X coordinate
  157.      * @param y the starting Y coordinate
  158.      * @param p0 the beginning position in the model
  159.      * @param p1 the ending position in the model
  160.      * @returns the location of the end of the range.
  161.      * @exception BadLocationException if the range is invalid
  162.      */
  163.     protected int drawSelectedText(Graphics g, int x, 
  164.                                    int y, int p0, int p1) throws BadLocationException {
  165.         g.setColor(selected);
  166.         Document doc = getDocument();
  167.         doc.getText(p0, p1 - p0, lineBuffer);
  168.         return Utilities.drawTabbedText(lineBuffer, x, y, g, this, p0);
  169.     }
  170.  
  171.     /**
  172.      * Gives access to a buffer that can be used to fetch 
  173.      * text from the associated document.
  174.      *
  175.      * @returns the buffer
  176.      */
  177.     protected final Segment getLineBuffer() {
  178.         return lineBuffer;
  179.     }
  180.  
  181.     final void updateMetrics() {
  182.     Component host = getContainer();
  183.     Font f = host.getFont();
  184.     metrics = host.getFontMetrics(f);
  185.     int columnWidth = metrics.charWidth('m');
  186.     width = getLineLimit() * columnWidth;
  187.     tabSize = getTabSize() * columnWidth;
  188.     }
  189.  
  190.     // ---- View methods ----------------------------------------------------
  191.  
  192.     /**
  193.      * Sets the parent of the view.
  194.      * The parent calls this on the child to tell it who its
  195.      * parent is.  If this is null, the view has been removed
  196.      * and we need to remove the associated component from its
  197.      * parent.  This is used here to determine what the hosting
  198.      * component is.
  199.      *
  200.      * @param p the parent view
  201.      */
  202.     public void setParent(View p) {
  203.         super.setParent(p);
  204.         host = (JTextComponent) getContainer();
  205.     }
  206.  
  207.     /**
  208.      * Determines the preferred span for this view along an
  209.      * axis.
  210.      *
  211.      * @param axis may be either X_AXIS or Y_AXIS
  212.      * @returns  the span the view would like to be rendered into.
  213.      *           Typically the view is told to render into the span
  214.      *           that is returned, although there is no guarantee.  
  215.      *           The parent may choose to resize or break the view.
  216.      */
  217.     public float getPreferredSpan(int axis) {
  218.     updateMetrics();
  219.         switch (axis) {
  220.         case View.X_AXIS:
  221.             return width;
  222.         case View.Y_AXIS:
  223.             return getElement().getElementCount() * metrics.getHeight();
  224.         default:
  225.             throw new IllegalArgumentException("Invalid axis: " + axis);
  226.         }
  227.     }
  228.  
  229.     /**
  230.      * Renders using the given rendering surface and area on that surface.
  231.      * The view may need to do layout and create child views to enable
  232.      * itself to render into the given allocation.
  233.      *
  234.      * @param g the rendering surface to use
  235.      * @param a the allocated region to render into
  236.      *
  237.      * @see View#paint
  238.      */
  239.     public void paint(Graphics g, Shape a) {
  240.         Rectangle alloc = a.getBounds();
  241.         tabBase = alloc.x;
  242.         g.setFont(host.getFont());
  243.         sel0 = host.getSelectionStart();
  244.         sel1 = host.getSelectionEnd();
  245.         unselected = (host.isEnabled()) ? 
  246.             host.getForeground() : host.getDisabledTextColor();
  247.     Caret c = host.getCaret();
  248.         selected = c.isSelectionVisible() ? host.getSelectedTextColor() : unselected;
  249.     updateMetrics();
  250.  
  251.         // If the lines are clipped then we don't expend the effort to
  252.         // try and paint them.  Since all of the lines are the same height
  253.         // with this object, determination of what lines need to be repainted
  254.         // is quick.
  255.         Rectangle clip = g.getClipBounds();
  256.         int fontHeight = metrics.getHeight();
  257.         int heightBelow = (alloc.y + alloc.height) - (clip.y + clip.height);
  258.         int linesBelow = Math.max(0, heightBelow / fontHeight);
  259.         int heightAbove = clip.y - alloc.y;
  260.         int linesAbove = Math.max(0, heightAbove / fontHeight);
  261.         int linesTotal = alloc.height / fontHeight;
  262.  
  263.         // update the visible lines
  264.         Rectangle lineArea = lineToRect(a, linesAbove);
  265.         int y = lineArea.y + metrics.getAscent();
  266.         int x = lineArea.x;
  267.         Element map = getElement();
  268.         int endLine = Math.min(map.getElementCount(), linesTotal - linesBelow);
  269.         for (int line = linesAbove; line < endLine; line++) {
  270.             drawLine(line, g, x, y);
  271.             y += fontHeight;
  272.         }
  273.     }
  274.  
  275.     /**
  276.      * Desired span has changed. 
  277.      *
  278.      * @param child the child view
  279.      * @param width true if the width preference has changed
  280.      * @param height true if the height preference has changed
  281.      * @see com.sun.java.swing.JComponent#revalidate
  282.      */
  283.     public void preferenceChanged(View child, boolean width, boolean height) {
  284.     getDocument().putProperty(PlainDocument.lineLimitAttribute, null);       
  285.     super.preferenceChanged(child, width, height);
  286.     }
  287.  
  288.     /**
  289.      * Provides a mapping from the document model coordinate space
  290.      * to the coordinate space of the view mapped to it.
  291.      *
  292.      * @param pos the position to convert
  293.      * @param a the allocated region to render into
  294.      * @return the bounding box of the given position
  295.      * @exception BadLocationException  if the given position does not
  296.      *   represent a valid location in the associated document
  297.      * @see View#modelToView
  298.      */
  299.     public Shape modelToView(int pos, Shape a) throws BadLocationException {
  300.         // line coordinates
  301.         Document doc = getDocument();
  302.         Element map = getElement();
  303.         int lineIndex = map.getElementIndex(pos);
  304.         Rectangle lineArea = lineToRect(a, lineIndex);
  305.         tabBase = lineArea.x;
  306.         
  307.         // determine span from the start of the line
  308.         tabBase = lineArea.x;
  309.         Element line = map.getElement(lineIndex);
  310.         int p0 = line.getStartOffset();
  311.         doc.getText(p0, pos - p0, lineBuffer);
  312.         int xOffs = Utilities.getTabbedTextWidth(lineBuffer, metrics, 0, this, p0);
  313.  
  314.         // fill in the results and return
  315.         lineArea.x += xOffs;
  316.         lineArea.width = 1;
  317.         lineArea.height = metrics.getHeight();
  318.         return lineArea;
  319.     }
  320.  
  321.     /**
  322.      * Provides a mapping from the view coordinate space to the logical
  323.      * coordinate space of the model.
  324.      *
  325.      * @param fx the X coordinate
  326.      * @param fy the Y coordinate
  327.      * @param a the allocated region to render into
  328.      * @return the location within the model that best represents the
  329.      *  given point in the view
  330.      * @see View#viewToModel
  331.      */
  332.     public int viewToModel(float fx, float fy, Shape a) {
  333.         Rectangle alloc = a.getBounds();
  334.         Document doc = getDocument();
  335.         int x = (int) fx;
  336.         int y = (int) fy;
  337.         if (y < alloc.y) {
  338.             // above the area covered by this icon, so the the position
  339.             // is assumed to be the start of the coverage for this view.
  340.             return getStartOffset();
  341.         } else if (y > alloc.y + alloc.height) {
  342.             // below the area covered by this icon, so the the position
  343.             // is assumed to be the end of the coverage for this view.
  344.             return getEndOffset() - 1;
  345.         } else {
  346.             // positioned within the coverage of this view vertically,
  347.             // so we figure out which line the point corresponds to.
  348.             // if the line is greater than the number of lines contained, then
  349.             // simply use the last line as it represents the last possible place
  350.             // we can position to.
  351.             Element map = doc.getDefaultRootElement();
  352.             int lineIndex = Math.abs((y - alloc.y) / metrics.getHeight() );
  353.             if (lineIndex >= map.getElementCount()) {
  354.                 return getEndOffset() - 1;
  355.             }
  356.             Element line = map.getElement(lineIndex);
  357.             if (x < alloc.x) {
  358.                 // point is to the left of the line
  359.                 return line.getStartOffset();
  360.             } else if (x > alloc.x + alloc.width) {
  361.                 // point is to the right of the line
  362.                 return line.getEndOffset() - 1;
  363.             } else {
  364.                 // Determine the offset into the text
  365.                 try {
  366.                     int p0 = line.getStartOffset();
  367.                     int p1 = line.getEndOffset() - 1;
  368.                     doc.getText(p0, p1 - p0, lineBuffer);
  369.                     tabBase = alloc.x;
  370.                     int offs = p0 + Utilities.getTabbedTextOffset(lineBuffer, metrics,
  371.                                                                   tabBase, x, this, p0);
  372.                     return offs;
  373.                 } catch (BadLocationException e) {
  374.                     // should not happen
  375.                     return -1;
  376.                 }
  377.             }
  378.         }
  379.     }    
  380.  
  381.     /**
  382.      * Gives notification that something was inserted into the document
  383.      * in a location that this view is responsible for.
  384.      *
  385.      * @param changes the change information from the associated document
  386.      * @param a the current allocation of the view
  387.      * @param f the factory to use to rebuild if the view has children
  388.      * @see View#insertUpdate
  389.      */
  390.     public void insertUpdate(DocumentEvent changes, Shape a, ViewFactory f) {
  391.     updateDamage(changes, a, f);
  392.     }
  393.  
  394.     /**
  395.      * Gives notification that something was removed from the document
  396.      * in a location that this view is responsible for.
  397.      *
  398.      * @param changes the change information from the associated document
  399.      * @param a the current allocation of the view
  400.      * @param f the factory to use to rebuild if the view has children
  401.      * @see View#removeUpdate
  402.      */
  403.     public void removeUpdate(DocumentEvent changes, Shape a, ViewFactory f) {
  404.         updateDamage(changes, a, f);
  405.     }
  406.  
  407.     /**
  408.      * Gives notification from the document that attributes were changed
  409.      * in a location that this view is responsible for.
  410.      *
  411.      * @param changes the change information from the associated document
  412.      * @param a the current allocation of the view
  413.      * @param f the factory to use to rebuild if the view has children
  414.      * @see View#changedUpdate
  415.      */
  416.     public void changedUpdate(DocumentEvent changes, Shape a, ViewFactory f) {
  417.         updateDamage(changes, a, f);
  418.     }
  419.  
  420.     // --- TabExpander methods ------------------------------------------
  421.  
  422.     /**
  423.      * Returns the next tab stop position after a given reference position.
  424.      * This implementation does not support things like centering so it
  425.      * ignores the tabOffset argument.
  426.      *
  427.      * @param x the current position
  428.      * @param tabOffset the position within the text stream
  429.      *   that the tab occurred at.
  430.      * @return the tab stop, measured in points
  431.      */
  432.     public float nextTabStop(float x, int tabOffset) {
  433.         int ntabs = (((int) x) - tabBase) / tabSize;
  434.         return (ntabs + 1) * tabSize;
  435.     }
  436.  
  437.     
  438.     // --- local methods ------------------------------------------------
  439.  
  440.     /* 
  441.      * We can damage the line that begins the range to cover
  442.      * the case when the insert is only on one line.  If lines
  443.      * are added or removed we will damage the whole view.
  444.      */
  445.     void updateDamage(DocumentEvent changes, Shape a, ViewFactory f) {
  446.         if (host.isShowing()) {
  447.         updateMetrics();
  448.             Element elem = getElement();
  449.             DocumentEvent.ElementChange ec = changes.getChange(elem);
  450.             
  451.             Element[] added = (ec != null) ? ec.getChildrenAdded() : null;
  452.             Element[] removed = (ec != null) ? ec.getChildrenRemoved() : null;
  453.             if (((added != null) && (added.length > 0)) || 
  454.                 ((removed != null) && (removed.length > 0))) {
  455.                 preferenceChanged(null, true, true);
  456.                 host.repaint();
  457.             } else {
  458.                 preferenceChanged(null, true, false);
  459.                 Element map = getElement();
  460.                 int line = map.getElementIndex(changes.getOffset());
  461.                 damageLineRange(line, line, a, host);
  462.             }
  463.         }
  464.     }
  465.  
  466.     private void damageLineRange(int line0, int line1, Shape a, Component host) {
  467.         if (a != null) {
  468.             Rectangle area0 = lineToRect(a, line0);
  469.             Rectangle area1 = lineToRect(a, line1);
  470.             if ((area0 != null) && (area1 != null)) {
  471.                 Rectangle damage = area0.union(area1);
  472.                 host.repaint(damage.x, damage.y, damage.width, damage.height);
  473.             } else {
  474.                 host.repaint();
  475.             }
  476.         }
  477.     }
  478.  
  479.     private Rectangle lineToRect(Shape a, int line) {
  480.         Rectangle r = null;
  481.         if (metrics != null) {
  482.             Rectangle alloc = a.getBounds();
  483.             r = new Rectangle(alloc.x, alloc.y + (line * metrics.getHeight()),
  484.                               alloc.width, metrics.getHeight());
  485.         }
  486.         return r;
  487.     }
  488.  
  489.     // --- member variables -----------------------------------------------
  490.  
  491.     /**
  492.      * Font metrics for the currrent font.
  493.      */
  494.     protected FontMetrics metrics;
  495.  
  496.     Segment lineBuffer;
  497.     int width;
  498.     int tabSize;
  499.     int tabBase;
  500.     JTextComponent host;
  501.     
  502.     int sel0;
  503.     int sel1;
  504.     Color unselected;
  505.     Color selected;
  506.     
  507. }
  508.