/*
 * TextBox.java
 *
 * written July 1997 by Andreas "Ludi" Ludwig
 *
 * Permission to use, copy, modify, and distribute this source code
 * and its documentation for NON-COMMERCIAL purposes and without
 * fee is hereby granted provided that this copyright notice
 * appears in all copies.
 *
 */

import java.awt.*;
import java.util.*;

/**
 * TextBox is a multiline area for displaying, but not editing text.
 * It provides auto-wrapping and adds a vertical scrollbar if
 * necessary. Multiple blanks are swallowed but line feeds can be
 * used to force line breaks.
 *
 * @version 1.0, 19 Jul 97
 * @author Andreas Ludwig
 */

public class TextBox extends Panel {

   private String text;
   private String [] token;     // words and linefeeds of the text
   private Insets insets;       // insets for text surrounding
   private boolean ignoreLineFeeds;
   private int lineSpacing;     // additional line spacing
   private int top;             // top line to display
   private Scrollbar scrollbar;


   /**
    * Constructs an empty TextBox.
    */
   public TextBox() {
      this("", false);
   }

   /**
    * Constructs a TextBox for the specified text;
    * linefeed characters in the text are used by default.
    * @param text the input String
    */
   public TextBox(String text) {
      this(text, false);
   }

   /**
    * Constructs a TextBox for the specified text and
    * optionally uses linefeed characters in the text.
    * @param text the input String
    * @param ignoreLineFeeds flag whether or not to take notice of \n's
    */
   public TextBox(String text, boolean ignoreLineFeeds) {
      this.text = text;
      this.ignoreLineFeeds = ignoreLineFeeds;
      insets = new Insets(0, 3, 0, 3);
      setLayout(new BorderLayout());
      tokenize();
   }

   /**
    * Tokenize the text into words and single linefeeds.
    */
   private void tokenize() {
      Vector v = new Vector();
      StringTokenizer tok = new StringTokenizer(text, " \t\r");          
      while (tok.hasMoreTokens()) {
         String str = tok.nextToken();
         int i = 0, j = str.indexOf('\n'); // separate \n's "manually"
         while (j >= 0) {
            if (j > i)
               v.addElement(str.substring(i, j));
            v.addElement("\n");
            j = str.indexOf('\n', i = j + 1);
         }
         if (i < str.length())
            v.addElement(str.substring(i));           
      }
      v.copyInto(token = new String[v.size()]);
      // note: Vector.copyInto is not statically type-safe
   }

   /**
    * Wrap the text.
    * @param g graphics context used to measure fonts and draw the text
    * @param top top of visible area
    * @param right width of unusable area at the right (scrollbar)
    * @param draw false if the text is just to be measured, not to be drawn
    * @return returns the total height needed (for scrolling)
    */
   private int layoutText(Graphics g, int top, int right, boolean draw) {
      FontMetrics fm = g.getFontMetrics();
      int fontHeight = fm.getHeight();
      int blankWidth = fm.charWidth(' ');
      int wrapWidth = size().width - insets.right - right;
      int curX = insets.left;
      int curY = insets.top - top + fontHeight;
      for (int i = 0; i < token.length; ++i) {
         String w = token[i];
         if (w.equals("\n")) {
 	    if (!ignoreLineFeeds) {
               curX = insets.left;
               curY += fontHeight + lineSpacing;
            }
         } else {
            int curLen = fm.stringWidth(w);
            if (curX == insets.left) {
               if (draw)
                  g.drawString(w, curX, curY);
               curX += curLen;
            } else if (curX + blankWidth + curLen > wrapWidth) {
              curX = insets.left;
              curY += fontHeight + lineSpacing;
              if (draw)
                 g.drawString(w, curX, curY);
              curX += curLen;
            } else {
               if (draw)
                  g.drawString(" " + w, curX, curY);
               curX += curLen + blankWidth;
            }
         }
      }
      return curY + top + insets.bottom;
   }

   /*
    * We draw directly into the panel as if we were a lightweight
    * component. Not very politcally correct, but otherwise we would
    * need an extra class extending Canvas.
    */
   public synchronized void paint(Graphics g) { 
      int maxHeight = size().height;
      int bottom = layoutText(g, 0, 0, false);  // how high w/o scrollbar?
      if (bottom > maxHeight) { // we need a scrollbar 
 	 if (scrollbar == null) { // we need a new one
	    top = 0;
            scrollbar = new Scrollbar(Scrollbar.VERTICAL);
            add("East", scrollbar);
	    layout(); // add it NOW
         }
      } else {
 	 if (scrollbar != null) {
            remove(scrollbar); // we need the scrollbar no longer
            scrollbar = null;
            top = 0;
            layout(); // remove it NOW
         }
      }
      if (scrollbar != null) {
         bottom = layoutText(g, top, scrollbar.size().width, true); 
         top = Math.min(top, bottom - maxHeight); 
         scrollbar.setValues(top, maxHeight, 0, bottom - maxHeight);
      } else
         layoutText(g, top, 0, true);
   }

   /**
    * Sets the text for this TextBox to the specified text.
    * @param text the text to wrap
    * @see #getText
    */
   public void setText(String text) {
      this.text = text;
      tokenize();
      repaint();
   }

   /**
    * Gets the text of this TextBox.
    * @see #setText
    */
   public String getText() {
      return text;
   }

   /**
    * Sets the insets of this TextBox. They define additional space
    * left between the text and the borders of the component.
    * The default Insets are (0, 3, 0, 3).
    * @param ins the new insets
    * @see #setText
    */
   public void setInsets(Insets ins) {
      insets.left = ins.left;
      insets.top = ins.top;
      insets.right = ins.right;
      insets.bottom = ins.bottom;
      repaint();
   }

   /**
    * Sets the additional line spacing to stretch the text a bit.
    * The default lineSpacing is +0.
    * @param additionalPixels the new line spacing
    */
   public void setLineSpacing(int additionalPixels) {
      lineSpacing = additionalPixels;
      repaint();
   }

   public boolean handleEvent(Event e) {
      switch(e.id) {
         case Event.SCROLL_LINE_DOWN:
         case Event.SCROLL_LINE_UP:
         case Event.SCROLL_PAGE_DOWN:
         case Event.SCROLL_PAGE_UP:
         case Event.SCROLL_ABSOLUTE:
	    if (scrollbar.getValue() != top) {
               top = scrollbar.getValue();
               repaint();
	    }
            return true;
         default:
            return false;
      }
   }
}