home *** CD-ROM | disk | FTP | other *** search
/ The Net: Ultimate Internet Guide / WWLCD1.ISO / pc / java / un2maiq4 / pjjava / src / pj / awt / wordwraphack.java < prev    next >
Encoding:
Java Source  |  1996-08-14  |  17.2 KB  |  613 lines

  1. /*---------------------------------------------------------------------------
  2.  
  3.     @(#)WordWrapHack.java  0.00 28-Dec-95
  4.  
  5.         A prototype of a scrolling, word-wrapping text area.
  6.         Originally written by Rick Hall and released into the public domain.
  7.  
  8.     Authors:
  9.  
  10.         rphall   Rick Hall
  11.         jlee     James Lee
  12.  
  13.     Version Ident:
  14.  
  15.         $Header$
  16.  
  17.     History:
  18.  
  19.         0.00 28-Dec-95  rphall   Initial Creation
  20.         0.00 01-Mar-95  jlee     List's clear() has a bug, replaced it with delItem() and modified
  21.                                  list width handling.
  22.  
  23. ---------------------------------------------------------------------------*/
  24.  
  25. package pj.awt;
  26.  
  27. import java.awt.Canvas;
  28. import java.awt.Color;
  29. import java.awt.Container;
  30. import java.awt.Dimension;
  31. import java.awt.Event;
  32. import java.awt.Font;
  33. import java.awt.FontMetrics;
  34. import java.awt.Graphics;
  35. import java.awt.List;
  36. import java.awt.Scrollbar;
  37. import java.util.NoSuchElementException;
  38. import java.util.StringTokenizer;
  39. import java.util.Vector;
  40.  
  41. /**
  42.  * A prototype of a scrolling, word-wrapping text area.
  43.  *
  44.  * @see java.awt.List
  45.  * @version 0.00 28-Dec-95
  46.  * @author rphall
  47. */
  48. public class WordWrapHack extends List
  49.     {
  50.  
  51.     // --- Instance variables
  52.  
  53.     // Pristine, unwrapped input text
  54.     private String strText;
  55.  
  56.     // Input text broken into word-wrapped lines
  57.     private String[] astrLine;
  58.  
  59.     // Flag that indicates whether selection feature of List is enabled
  60.     private boolean bSelectionEnabled;
  61.  
  62.     // Flag that indicates whether astrLine should be recalc'd
  63.     private boolean bWrapped;
  64.  
  65.     // Cached value of WordWrapHack width, including scrollbar
  66.     private int cachedWidth;
  67.  
  68.     // Cached value of WordWrapHack height.
  69.     private int cachedHeight;
  70.  
  71.     // Cached, estimated width of a vertical scrollbar
  72.     private int scrollbarWidth;
  73.  
  74.  
  75.     // --- Public constructors
  76.  
  77.     /**
  78.      * Construct an empty WordWrapHack.
  79.     */
  80.     public WordWrapHack()
  81.         {
  82.         this("");
  83.         }
  84.  
  85.     /**
  86.      * Construct a WordWrapHack with some text.
  87.      *
  88.      * @param text     The text that is initially displayed.
  89.     */
  90.     public WordWrapHack(String text)
  91.         {
  92.         super();
  93.  
  94.         setBackground(Color.white);
  95.  
  96.         strText = new String(text);
  97.         astrLine = null;
  98.         bSelectionEnabled = false;
  99.         bWrapped = false;
  100.         cachedWidth = 0;
  101.         scrollbarWidth = 0;
  102.  
  103.         } // WordWrapHack(String)
  104.  
  105.     // --- Public operations
  106.  
  107.     /**
  108.      * Set the text displayed by a WordWrapHack.
  109.      *
  110.      * @param text     The new text for a WordWrapHack.
  111.     */
  112.     public synchronized void setText(String text)
  113.         {
  114.         strText = text;
  115.         wrap();
  116.         }
  117.  
  118.     /**
  119.      * Clear the text displayed by a WordWrapHack.
  120.      * Equivalent to:
  121.      * <pre>
  122.      *      setText("");
  123.      * </pre>
  124.      * @see setText
  125.      *
  126.     */
  127.     public synchronized void clearText()
  128.         { 
  129.         setText(""); 
  130.         }
  131.  
  132.     /**
  133.      * Return the text displayed by a WordWrapHack.
  134.     */
  135.     public synchronized String getText()
  136.         { 
  137.         return strText; 
  138.         }
  139.  
  140.     /**
  141.      * Set whether selection features of List are enabled.
  142.      *
  143.      * @param  bEnable     true enables selection feature
  144.      * @see java.awt.List
  145.     */
  146.     public synchronized void setSelectionEnabled(boolean bEnable)
  147.         { 
  148.         bSelectionEnabled = bEnable; 
  149.         }
  150.  
  151.     /**
  152.      * Return whether selection features of List are enabled.
  153.      *
  154.      * @see java.awt.List
  155.     */
  156.     public synchronized boolean getSelectionEnabled()
  157.         { 
  158.         return bSelectionEnabled; 
  159.         }
  160.  
  161.     /**
  162.      * Reshape a WordWrapHack:
  163.      * Hand off operation to super, then rewrap text if width has changes.
  164.      *
  165.      * @see java.awt.List#reshape
  166.      * @see java.awt.Component#reshape
  167.     */
  168.     public synchronized void reshape(int x, int y, int width, int height)
  169.         {
  170.         super.reshape(x,y,width,height);
  171.  
  172.         cachedHeight = height;
  173.  
  174.         if ( cachedWidth != width || bWrapped == false )
  175.             {
  176.             cachedWidth  = width;
  177.             wrap();
  178.             }
  179.  
  180.         } // reshape
  181.  
  182.     /**
  183.      * Set the font used by a WordWrapHack:
  184.      * Signal an internal flag to rewrap, then hand off operation to super.
  185.      *
  186.      * @see java.awt.Component#setFont
  187.     */
  188.     public synchronized void setFont(Font f)
  189.         {
  190.         super.setFont(f);
  191.         wrap();
  192.         }
  193.  
  194.     /**
  195.      * Handle event:
  196.      * If the event is LIST_SELECT, but list selection is disenabled,
  197.      * immediately deselect this selection.  Otherwise let super handle it.
  198.      */
  199.     public boolean handleEvent(Event evt)
  200.         {
  201.         switch (evt.id)
  202.             {
  203.             case Event.LIST_SELECT:
  204.                 if (!bSelectionEnabled) super.deselect(getSelectedIndex());
  205.                 else super.handleEvent(evt);
  206.                 return true;
  207.             default:
  208.                 return super.handleEvent(evt);
  209.                 } // switch
  210.         } // handleEvent
  211.  
  212.     /**
  213.      * Selects the item at the specified index if the selection
  214.      * features of List are enabled.
  215.      *
  216.      * @param index the position of the item to select
  217.      * @see java.awt.list#select
  218.      */
  219.     public synchronized void select(int index)
  220.         {
  221.         if (bSelectionEnabled) 
  222.             super.select(index);
  223.         }
  224.  
  225.     // --- Private operations
  226.  
  227.     // Wrap strText into astrLine
  228.     private void wrap()
  229.         {
  230.         scrollbarWidth = 25;
  231.  
  232.         bWrapped = false;
  233.         if ( cachedWidth==0 ) 
  234.             return;
  235.  
  236.         Font f = getFont();
  237.         if ( f==null ) 
  238.             return;
  239.  
  240.         FontMetrics fm = getFontMetrics(f);
  241.         if ( fm == null ) 
  242.             return;
  243.  
  244.         try {
  245.             Wrapper w = new Wrapper(fm);
  246.             int width = cachedWidth - scrollbarWidth;
  247.  
  248.             if ( strText.length() != 0 )
  249.                 {
  250.                 astrLine = w.formatToArray( strText, width );
  251.  
  252.                 if ( cachedHeight >= astrLine.length * fm.getHeight() )
  253.                     {
  254.                     width += scrollbarWidth;
  255.                     astrLine = w.formatToArray( strText, width );
  256.                     }
  257.                 }
  258.  
  259.             resynch();
  260.             bWrapped=true;
  261.             }
  262.         catch (WordWrapHackError e)
  263.             {
  264.             System.out.println("Debug WordWrapHack: error invoking Wrapper.");
  265.             }
  266.  
  267.         } // wrap
  268.  
  269.  
  270.     // Resynchronize List items with astrLine
  271.     private void resynch()
  272.         {
  273.         while ( countItems() > 0 )
  274.             for ( int i = 0; i < countItems(); i++ )
  275.                 delItem( i );
  276.         
  277.         if ( astrLine != null )
  278.             for (int i=0; i<astrLine.length; i++)
  279.                 addItem(astrLine[i]);
  280.         }
  281.  
  282.     // Return an arbitrary width for a left margin
  283.     private int getLeftMargin(FontMetrics unused)
  284.         {
  285.         /* Obsolete implementation
  286.         int indent = fm.charWidth(' ');
  287.         if (cachedWidth - indent < 0) indent = 0;
  288.         return indent;
  289.         */
  290.         return 0;
  291.         }
  292.  
  293.     // Estimate the width of for a right text margin;
  294.     // i.e. for a vertical scrollbar.
  295.     // Algorithm is guarenteed unreliable and platform-dependent.
  296.     private int getRightMargin(FontMetrics unused)
  297.         {
  298.         /* Obsolete implementation
  299.         int indent;
  300.         float fudgeFactor = (float)2.0;
  301.  
  302.         if (scrollbarWidth <= 0)
  303.             {
  304.             Scrollbar sb = new Scrollbar(Scrollbar.VERTICAL);
  305.             scrollbarWidth = sb.minimumSize().width;
  306.             if (scrollbarWidth < 0) scrollbarWidth = 0;
  307.             }
  308.  
  309.         indent = (int)(fudgeFactor * scrollbarWidth);
  310.         if (cachedWidth - indent < 0) 
  311.             indent = 0;
  312.  
  313.         return indent;
  314.         */
  315.         return 0;
  316.  
  317.         } // getRightMargin
  318.  
  319.     
  320.     }; // WordWrapHack
  321.  
  322. /**
  323.  * A utility that wraps unbroken text into lines.
  324.  *
  325.  * @version    0.00 29-Dec-95
  326.  * @author     rphall
  327. */
  328. class Wrapper
  329.     {
  330.  
  331.     // --- Class variables
  332.  
  333.     /**
  334.      * Default set of line-delimiting characters
  335.     */
  336.     public static final char[] NEWLINE = {'\r','\n'};
  337.  
  338.     /**
  339.      * Default set of word-delimiting characters
  340.     */
  341.     public static final char[] WORDBRK = {'\t',' ','-'};
  342.  
  343.  
  344.    
  345.     private static final String  CR             = "\r";
  346.     private static final String  LF             = "\n";
  347.     private static final String  CRLF           = CR + LF;
  348.     private static final boolean bReturnTokens  = true;
  349.  
  350.  
  351.     // --- Instance variables
  352.  
  353.     private FontMetrics fontMetrics;
  354.     private String[]    astrLineDelim;
  355.     private String[]    astrWordDelim;
  356.     private String      strDelim;
  357.  
  358.  
  359.     // --- Public constructors
  360.  
  361.     /**
  362.      * Constructs a Wrapper with default line and word delimiters
  363.      *
  364.      * @param fm   The metrics of the font used for text display
  365.     */
  366.     public Wrapper(FontMetrics fm)
  367.         { 
  368.         this(fm,NEWLINE,WORDBRK); 
  369.         }
  370.  
  371.     /**
  372.      * Constructs a Wrapper with custom line and word delimiters
  373.      *
  374.      * @param fm           The metrics of the font used for text display
  375.      * @param lineDelim    An array of custom line delimiters
  376.      * @param wordDelim    An array of custom word delimiters
  377.     */
  378.     public Wrapper(FontMetrics fm, char[] lineDelim, char[] wordDelim)
  379.         {
  380.         fontMetrics = fm;
  381.         astrLineDelim = new String[lineDelim.length];
  382.  
  383.         for (int i=0; i<lineDelim.length; i++)
  384.             astrLineDelim[i] = new String(lineDelim,i,1);
  385.         astrWordDelim = new String[wordDelim.length];
  386.  
  387.         for (int i=0; i<wordDelim.length; i++)
  388.             astrWordDelim[i] = new String(wordDelim,i,1);
  389.         strDelim = new String(lineDelim) + new String(wordDelim);
  390.         }
  391.  
  392.     // --- Public operations
  393.  
  394.     /**
  395.      * Throws an WordWrapHackError if predicate is false.
  396.      * The right way to implement assert is to import Assertable
  397.      * and ImplementationError from
  398.      * <b><a href="http://g.oswego.edu/dl/classes/collections/index.html">
  399.      * Collection Classes</a></b>
  400.      *
  401.      * @param predicate    Boolean expression to be checked.
  402.      * @see collections.ImplementationError
  403.      * @see collections.Assertable
  404.     */
  405.     public void assert(boolean predicate)
  406.         throws WordWrapHackError
  407.         { 
  408.         if (!predicate) throw new WordWrapHackError();
  409.         }
  410.  
  411.     /**
  412.      * Formats unbroken text into lines.
  413.      * Returns a string array.
  414.      * Each string of the returned array is a line.
  415.      * Line delimiters are stripped from the unbroken text.
  416.      * The sequence "\r\n" is treated as a single line delimiter.
  417.      * Throws an implementation error if input text is empty.
  418.      *
  419.      * @param strRawText   The input text, length > 0
  420.      * @param linewidth    The desired line width
  421.     */
  422.     public String[] formatToArray(String strRawText, int linewidth)
  423.         throws WordWrapHackError
  424.         {
  425.         // Precondition
  426.         assert( strRawText.length() != 0 );
  427.  
  428.         // Replace CRLF with LF
  429.         String text    = strRawText;
  430.         int nextCRLF   = text.indexOf(CRLF);
  431.         int nextSubstr = 0;
  432.  
  433.         while ( nextCRLF != -1 )
  434.             {
  435.             nextSubstr = text.length() - CRLF.length();
  436.             if ( nextSubstr == 0 )
  437.                 {
  438.                 text = text.substring(0,nextCRLF) + LF;
  439.                 assert( text.indexOf(CRLF) == -1 );
  440.                 }
  441.             else
  442.                 {
  443.                 text = text.substring(0,nextCRLF) + LF
  444.                         + text.substring(nextCRLF + CRLF.length());
  445.                 }
  446.  
  447.             nextCRLF  = text.indexOf(CRLF);
  448.             } // while CRLF
  449.  
  450.         // Format into lines
  451.         String[] strings;
  452.         if ( text.length() == 0 )
  453.             {
  454.             strings = new String[1];
  455.             strings[0] = "";
  456.             }
  457.         else
  458.             {
  459.             Vector vStrings = new Vector();
  460.             String[] astrFmt = formatLine(text,linewidth);
  461.             vStrings.addElement(astrFmt[0]);
  462.             while ( astrFmt[1].length() > 0 )
  463.                 {
  464.                 astrFmt = formatLine(astrFmt[1],linewidth);
  465.                 vStrings.addElement(astrFmt[0]);
  466.                 }
  467.             strings = new String[vStrings.size()];
  468.             for ( int i=0; i<strings.length; i++ )
  469.                 strings[i] = (String)vStrings.elementAt(i);
  470.  
  471.             } // formatToArray
  472.  
  473.         return strings;
  474.  
  475.         } // formatToStringArray
  476.  
  477.     // --- Private operations
  478.  
  479.     /*
  480.      * Returns a line and prunes the line from the input text.
  481.      * Line delimiters are stripped from the line.  This operation will
  482.      * treat the sequence "\r\n" as two line delimiters, rather than one.
  483.      * If this is undesirable, replace all CRLF sequences by single LF
  484.      * sequences before invoking this operation.
  485.      *
  486.      * @return String[0]   An extracted line
  487.      * @return String[1]   The input text pruned of the extracted line.
  488.     */
  489.     private String[] formatLine(
  490.             String text,        // In:  text from which to extract a line
  491.                                 // Out: text minus the extracted line
  492.             int desiredWidth)   // In:  the desired width of a line
  493.         throws WordWrapHackError
  494.         {
  495.  
  496.         // Check for pathology
  497.         assert( text.equals("") == false );
  498.         assert( desiredWidth > 0 );
  499.  
  500.         //Compensate for right scroll bar
  501.         desiredWidth -= 5;
  502.  
  503.         StringTokenizer st = new StringTokenizer( text, strDelim, bReturnTokens );
  504.  
  505.         String line       = "";   // The extracted line
  506.         String strToken   = null; // A new token from text
  507.         int currentWidth  = 0;    // The current width of the extracted line
  508.         int checkWidth    = 0;    // The currentWidth plus a new token width
  509.         int numLineChars  = 0;    // The number of chars in current line
  510.  
  511.         TokenLoop:
  512.         for (int i=0; ; i++)
  513.             {
  514.             // Implementation note: countTokens() does not seem to be
  515.             // working correctly.  Hence this loop continues until an
  516.             // exception occurs, rather than politely using countTokens
  517.             // to limit iterations.
  518.             try {
  519.                 strToken = st.nextToken();
  520.                 }
  521.             catch (NoSuchElementException e)
  522.                 {
  523.                 break TokenLoop;
  524.                 }
  525.  
  526.             // Line delimiters reset a line length calc
  527.             for (int j=0; j<astrLineDelim.length; j++)
  528.                 if ( strToken.equals(astrLineDelim[j]) )
  529.                     {
  530.                     numLineChars += strToken.length();
  531.                     break TokenLoop;
  532.                     }
  533.  
  534.             // Check desiredWidth with the addition of the new token
  535.             checkWidth = currentWidth + fontMetrics.stringWidth(strToken);
  536.  
  537.             // If the checked width is less than a line width,
  538.             // add the new token to the current line and continue.
  539.             if ( checkWidth < desiredWidth )
  540.                 {
  541.                 line += strToken;
  542.                 currentWidth = checkWidth;
  543.                 numLineChars += strToken.length();
  544.                 continue TokenLoop;
  545.                 }
  546.  
  547.             // If the checked width is longer than a line width,
  548.             // check if the new token is a word delimiter.  Extra
  549.             // word delimiters are OK at the end of a line.
  550.             for (int j=0; j<astrWordDelim.length; j++)
  551.                 if ( strToken.equals(astrWordDelim[j]) )
  552.                     {
  553.                     line += strToken;
  554.                     currentWidth = checkWidth;
  555.                     numLineChars += strToken.length();
  556.                     continue TokenLoop;
  557.                     }
  558.  
  559.             // Avoid pathology by making sure the current line has
  560.             // at least one token.
  561.             if ( currentWidth == 0 )
  562.                 {
  563.                 line += strToken;
  564.                 currentWidth = checkWidth;
  565.                 numLineChars += strToken.length();
  566.                 continue TokenLoop;
  567.                 }
  568.  
  569.             // If the checked width is longer than a line width,
  570.             // and the current line has at least one token, the line
  571.             // is finished. The new token starts the returned text.
  572.             break TokenLoop;
  573.  
  574.             } // for TokenLoop
  575.  
  576.         assert( numLineChars > 0 );
  577.         assert( numLineChars <= text.length() );
  578.         assert( line.length() <= text.length() );
  579.  
  580.         if ( numLineChars == text.length() )
  581.             text = "";
  582.         else
  583.             text = text.substring(numLineChars);
  584.  
  585.         String[] strs = new String[2];
  586.         strs[0] = line;
  587.         strs[1] = text;
  588.  
  589.         return strs;
  590.  
  591.         } // formatLine
  592.  
  593.     
  594.  
  595.     } // Wrapper
  596.  
  597. /**
  598.  * An internal implementation error
  599.  *
  600.  * @version    0.00 29-Dec-95
  601.  * @author     rphall
  602. */
  603. class WordWrapHackError extends Error
  604.     {
  605.  
  606.     // --- Public constructor
  607.  
  608.     public WordWrapHackError()
  609.         { 
  610.         super("WordWrapHack implementation error"); 
  611.         }
  612.     }
  613.