home *** CD-ROM | disk | FTP | other *** search
Java Source | 1996-08-14 | 17.2 KB | 613 lines |
- /*---------------------------------------------------------------------------
-
- @(#)WordWrapHack.java 0.00 28-Dec-95
-
- A prototype of a scrolling, word-wrapping text area.
- Originally written by Rick Hall and released into the public domain.
-
- Authors:
-
- rphall Rick Hall
- jlee James Lee
-
- Version Ident:
-
- $Header$
-
- History:
-
- 0.00 28-Dec-95 rphall Initial Creation
- 0.00 01-Mar-95 jlee List's clear() has a bug, replaced it with delItem() and modified
- list width handling.
-
- ---------------------------------------------------------------------------*/
-
- package pj.awt;
-
- import java.awt.Canvas;
- import java.awt.Color;
- import java.awt.Container;
- import java.awt.Dimension;
- import java.awt.Event;
- import java.awt.Font;
- import java.awt.FontMetrics;
- import java.awt.Graphics;
- import java.awt.List;
- import java.awt.Scrollbar;
- import java.util.NoSuchElementException;
- import java.util.StringTokenizer;
- import java.util.Vector;
-
- /**
- * A prototype of a scrolling, word-wrapping text area.
- *
- * @see java.awt.List
- * @version 0.00 28-Dec-95
- * @author rphall
- */
- public class WordWrapHack extends List
- {
-
- // --- Instance variables
-
- // Pristine, unwrapped input text
- private String strText;
-
- // Input text broken into word-wrapped lines
- private String[] astrLine;
-
- // Flag that indicates whether selection feature of List is enabled
- private boolean bSelectionEnabled;
-
- // Flag that indicates whether astrLine should be recalc'd
- private boolean bWrapped;
-
- // Cached value of WordWrapHack width, including scrollbar
- private int cachedWidth;
-
- // Cached value of WordWrapHack height.
- private int cachedHeight;
-
- // Cached, estimated width of a vertical scrollbar
- private int scrollbarWidth;
-
-
- // --- Public constructors
-
- /**
- * Construct an empty WordWrapHack.
- */
- public WordWrapHack()
- {
- this("");
- }
-
- /**
- * Construct a WordWrapHack with some text.
- *
- * @param text The text that is initially displayed.
- */
- public WordWrapHack(String text)
- {
- super();
-
- setBackground(Color.white);
-
- strText = new String(text);
- astrLine = null;
- bSelectionEnabled = false;
- bWrapped = false;
- cachedWidth = 0;
- scrollbarWidth = 0;
-
- } // WordWrapHack(String)
-
- // --- Public operations
-
- /**
- * Set the text displayed by a WordWrapHack.
- *
- * @param text The new text for a WordWrapHack.
- */
- public synchronized void setText(String text)
- {
- strText = text;
- wrap();
- }
-
- /**
- * Clear the text displayed by a WordWrapHack.
- * Equivalent to:
- * <pre>
- * setText("");
- * </pre>
- * @see setText
- *
- */
- public synchronized void clearText()
- {
- setText("");
- }
-
- /**
- * Return the text displayed by a WordWrapHack.
- */
- public synchronized String getText()
- {
- return strText;
- }
-
- /**
- * Set whether selection features of List are enabled.
- *
- * @param bEnable true enables selection feature
- * @see java.awt.List
- */
- public synchronized void setSelectionEnabled(boolean bEnable)
- {
- bSelectionEnabled = bEnable;
- }
-
- /**
- * Return whether selection features of List are enabled.
- *
- * @see java.awt.List
- */
- public synchronized boolean getSelectionEnabled()
- {
- return bSelectionEnabled;
- }
-
- /**
- * Reshape a WordWrapHack:
- * Hand off operation to super, then rewrap text if width has changes.
- *
- * @see java.awt.List#reshape
- * @see java.awt.Component#reshape
- */
- public synchronized void reshape(int x, int y, int width, int height)
- {
- super.reshape(x,y,width,height);
-
- cachedHeight = height;
-
- if ( cachedWidth != width || bWrapped == false )
- {
- cachedWidth = width;
- wrap();
- }
-
- } // reshape
-
- /**
- * Set the font used by a WordWrapHack:
- * Signal an internal flag to rewrap, then hand off operation to super.
- *
- * @see java.awt.Component#setFont
- */
- public synchronized void setFont(Font f)
- {
- super.setFont(f);
- wrap();
- }
-
- /**
- * Handle event:
- * If the event is LIST_SELECT, but list selection is disenabled,
- * immediately deselect this selection. Otherwise let super handle it.
- */
- public boolean handleEvent(Event evt)
- {
- switch (evt.id)
- {
- case Event.LIST_SELECT:
- if (!bSelectionEnabled) super.deselect(getSelectedIndex());
- else super.handleEvent(evt);
- return true;
- default:
- return super.handleEvent(evt);
- } // switch
- } // handleEvent
-
- /**
- * Selects the item at the specified index if the selection
- * features of List are enabled.
- *
- * @param index the position of the item to select
- * @see java.awt.list#select
- */
- public synchronized void select(int index)
- {
- if (bSelectionEnabled)
- super.select(index);
- }
-
- // --- Private operations
-
- // Wrap strText into astrLine
- private void wrap()
- {
- scrollbarWidth = 25;
-
- bWrapped = false;
- if ( cachedWidth==0 )
- return;
-
- Font f = getFont();
- if ( f==null )
- return;
-
- FontMetrics fm = getFontMetrics(f);
- if ( fm == null )
- return;
-
- try {
- Wrapper w = new Wrapper(fm);
- int width = cachedWidth - scrollbarWidth;
-
- if ( strText.length() != 0 )
- {
- astrLine = w.formatToArray( strText, width );
-
- if ( cachedHeight >= astrLine.length * fm.getHeight() )
- {
- width += scrollbarWidth;
- astrLine = w.formatToArray( strText, width );
- }
- }
-
- resynch();
- bWrapped=true;
- }
- catch (WordWrapHackError e)
- {
- System.out.println("Debug WordWrapHack: error invoking Wrapper.");
- }
-
- } // wrap
-
-
- // Resynchronize List items with astrLine
- private void resynch()
- {
- while ( countItems() > 0 )
- for ( int i = 0; i < countItems(); i++ )
- delItem( i );
-
- if ( astrLine != null )
- for (int i=0; i<astrLine.length; i++)
- addItem(astrLine[i]);
- }
-
- // Return an arbitrary width for a left margin
- private int getLeftMargin(FontMetrics unused)
- {
- /* Obsolete implementation
- int indent = fm.charWidth(' ');
- if (cachedWidth - indent < 0) indent = 0;
- return indent;
- */
- return 0;
- }
-
- // Estimate the width of for a right text margin;
- // i.e. for a vertical scrollbar.
- // Algorithm is guarenteed unreliable and platform-dependent.
- private int getRightMargin(FontMetrics unused)
- {
- /* Obsolete implementation
- int indent;
- float fudgeFactor = (float)2.0;
-
- if (scrollbarWidth <= 0)
- {
- Scrollbar sb = new Scrollbar(Scrollbar.VERTICAL);
- scrollbarWidth = sb.minimumSize().width;
- if (scrollbarWidth < 0) scrollbarWidth = 0;
- }
-
- indent = (int)(fudgeFactor * scrollbarWidth);
- if (cachedWidth - indent < 0)
- indent = 0;
-
- return indent;
- */
- return 0;
-
- } // getRightMargin
-
-
- }; // WordWrapHack
-
- /**
- * A utility that wraps unbroken text into lines.
- *
- * @version 0.00 29-Dec-95
- * @author rphall
- */
- class Wrapper
- {
-
- // --- Class variables
-
- /**
- * Default set of line-delimiting characters
- */
- public static final char[] NEWLINE = {'\r','\n'};
-
- /**
- * Default set of word-delimiting characters
- */
- public static final char[] WORDBRK = {'\t',' ','-'};
-
-
-
- private static final String CR = "\r";
- private static final String LF = "\n";
- private static final String CRLF = CR + LF;
- private static final boolean bReturnTokens = true;
-
-
- // --- Instance variables
-
- private FontMetrics fontMetrics;
- private String[] astrLineDelim;
- private String[] astrWordDelim;
- private String strDelim;
-
-
- // --- Public constructors
-
- /**
- * Constructs a Wrapper with default line and word delimiters
- *
- * @param fm The metrics of the font used for text display
- */
- public Wrapper(FontMetrics fm)
- {
- this(fm,NEWLINE,WORDBRK);
- }
-
- /**
- * Constructs a Wrapper with custom line and word delimiters
- *
- * @param fm The metrics of the font used for text display
- * @param lineDelim An array of custom line delimiters
- * @param wordDelim An array of custom word delimiters
- */
- public Wrapper(FontMetrics fm, char[] lineDelim, char[] wordDelim)
- {
- fontMetrics = fm;
- astrLineDelim = new String[lineDelim.length];
-
- for (int i=0; i<lineDelim.length; i++)
- astrLineDelim[i] = new String(lineDelim,i,1);
- astrWordDelim = new String[wordDelim.length];
-
- for (int i=0; i<wordDelim.length; i++)
- astrWordDelim[i] = new String(wordDelim,i,1);
- strDelim = new String(lineDelim) + new String(wordDelim);
- }
-
- // --- Public operations
-
- /**
- * Throws an WordWrapHackError if predicate is false.
- * The right way to implement assert is to import Assertable
- * and ImplementationError from
- * <b><a href="http://g.oswego.edu/dl/classes/collections/index.html">
- * Collection Classes</a></b>
- *
- * @param predicate Boolean expression to be checked.
- * @see collections.ImplementationError
- * @see collections.Assertable
- */
- public void assert(boolean predicate)
- throws WordWrapHackError
- {
- if (!predicate) throw new WordWrapHackError();
- }
-
- /**
- * Formats unbroken text into lines.
- * Returns a string array.
- * Each string of the returned array is a line.
- * Line delimiters are stripped from the unbroken text.
- * The sequence "\r\n" is treated as a single line delimiter.
- * Throws an implementation error if input text is empty.
- *
- * @param strRawText The input text, length > 0
- * @param linewidth The desired line width
- */
- public String[] formatToArray(String strRawText, int linewidth)
- throws WordWrapHackError
- {
- // Precondition
- assert( strRawText.length() != 0 );
-
- // Replace CRLF with LF
- String text = strRawText;
- int nextCRLF = text.indexOf(CRLF);
- int nextSubstr = 0;
-
- while ( nextCRLF != -1 )
- {
- nextSubstr = text.length() - CRLF.length();
- if ( nextSubstr == 0 )
- {
- text = text.substring(0,nextCRLF) + LF;
- assert( text.indexOf(CRLF) == -1 );
- }
- else
- {
- text = text.substring(0,nextCRLF) + LF
- + text.substring(nextCRLF + CRLF.length());
- }
-
- nextCRLF = text.indexOf(CRLF);
- } // while CRLF
-
- // Format into lines
- String[] strings;
- if ( text.length() == 0 )
- {
- strings = new String[1];
- strings[0] = "";
- }
- else
- {
- Vector vStrings = new Vector();
- String[] astrFmt = formatLine(text,linewidth);
- vStrings.addElement(astrFmt[0]);
- while ( astrFmt[1].length() > 0 )
- {
- astrFmt = formatLine(astrFmt[1],linewidth);
- vStrings.addElement(astrFmt[0]);
- }
- strings = new String[vStrings.size()];
- for ( int i=0; i<strings.length; i++ )
- strings[i] = (String)vStrings.elementAt(i);
-
- } // formatToArray
-
- return strings;
-
- } // formatToStringArray
-
- // --- Private operations
-
- /*
- * Returns a line and prunes the line from the input text.
- * Line delimiters are stripped from the line. This operation will
- * treat the sequence "\r\n" as two line delimiters, rather than one.
- * If this is undesirable, replace all CRLF sequences by single LF
- * sequences before invoking this operation.
- *
- * @return String[0] An extracted line
- * @return String[1] The input text pruned of the extracted line.
- */
- private String[] formatLine(
- String text, // In: text from which to extract a line
- // Out: text minus the extracted line
- int desiredWidth) // In: the desired width of a line
- throws WordWrapHackError
- {
-
- // Check for pathology
- assert( text.equals("") == false );
- assert( desiredWidth > 0 );
-
- //Compensate for right scroll bar
- desiredWidth -= 5;
-
- StringTokenizer st = new StringTokenizer( text, strDelim, bReturnTokens );
-
- String line = ""; // The extracted line
- String strToken = null; // A new token from text
- int currentWidth = 0; // The current width of the extracted line
- int checkWidth = 0; // The currentWidth plus a new token width
- int numLineChars = 0; // The number of chars in current line
-
- TokenLoop:
- for (int i=0; ; i++)
- {
- // Implementation note: countTokens() does not seem to be
- // working correctly. Hence this loop continues until an
- // exception occurs, rather than politely using countTokens
- // to limit iterations.
- try {
- strToken = st.nextToken();
- }
- catch (NoSuchElementException e)
- {
- break TokenLoop;
- }
-
- // Line delimiters reset a line length calc
- for (int j=0; j<astrLineDelim.length; j++)
- if ( strToken.equals(astrLineDelim[j]) )
- {
- numLineChars += strToken.length();
- break TokenLoop;
- }
-
- // Check desiredWidth with the addition of the new token
- checkWidth = currentWidth + fontMetrics.stringWidth(strToken);
-
- // If the checked width is less than a line width,
- // add the new token to the current line and continue.
- if ( checkWidth < desiredWidth )
- {
- line += strToken;
- currentWidth = checkWidth;
- numLineChars += strToken.length();
- continue TokenLoop;
- }
-
- // If the checked width is longer than a line width,
- // check if the new token is a word delimiter. Extra
- // word delimiters are OK at the end of a line.
- for (int j=0; j<astrWordDelim.length; j++)
- if ( strToken.equals(astrWordDelim[j]) )
- {
- line += strToken;
- currentWidth = checkWidth;
- numLineChars += strToken.length();
- continue TokenLoop;
- }
-
- // Avoid pathology by making sure the current line has
- // at least one token.
- if ( currentWidth == 0 )
- {
- line += strToken;
- currentWidth = checkWidth;
- numLineChars += strToken.length();
- continue TokenLoop;
- }
-
- // If the checked width is longer than a line width,
- // and the current line has at least one token, the line
- // is finished. The new token starts the returned text.
- break TokenLoop;
-
- } // for TokenLoop
-
- assert( numLineChars > 0 );
- assert( numLineChars <= text.length() );
- assert( line.length() <= text.length() );
-
- if ( numLineChars == text.length() )
- text = "";
- else
- text = text.substring(numLineChars);
-
- String[] strs = new String[2];
- strs[0] = line;
- strs[1] = text;
-
- return strs;
-
- } // formatLine
-
-
-
- } // Wrapper
-
- /**
- * An internal implementation error
- *
- * @version 0.00 29-Dec-95
- * @author rphall
- */
- class WordWrapHackError extends Error
- {
-
- // --- Public constructor
-
- public WordWrapHackError()
- {
- super("WordWrapHack implementation error");
- }
- }
-