// (c)1998, Carl Burch. This may not be redistributed
import java.awt.*;
import java.util.Vector;
import java.awt.event.AdjustmentEvent;
import java.awt.event.AdjustmentListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;

class ClickableCanvasText extends Canvas {
	protected ClickableTextArea parent;
	protected boolean changed = false;

	public ClickableCanvasText(ClickableTextArea p) { parent = p; }

	public void setChanged() { changed = true; }

	public void paint(Graphics g) {
		Dimension size = this.getSize();
		if(changed) {
			g.clearRect(0, 0, size.width, size.height);
			changed = false;
		}
		FontMetrics f = g.getFontMetrics();
		g.drawRect(0, 0, size.width - 1, size.height - 1);
		int y = f.getMaxAscent() + ClickableTextArea.BORDER_WID;
		int x = -parent.getOffsX() + ClickableTextArea.BORDER_WID;
		for(int i = parent.getOffsY(); y < size.height; i++) {
			String line = parent.getLine(i);
			if(line == null) break;
			g.drawString(line, x, y);
			y += f.getHeight();
		}
	}

	public int getLine(int y) {
		Dimension size = this.getSize();
		FontMetrics f = this.getGraphics().getFontMetrics();
		int cur_y = ClickableTextArea.BORDER_WID;
		for(int i = parent.getOffsY(); y < size.height; i++) {
			int next_y = cur_y + f.getHeight();
			if(parent.getLine(i) == null) return i - 1;
			if(y >= cur_y && y < next_y) return i;
			cur_y = next_y;
		}
		return parent.getOffsY() + size.height - 1;
	}

	public int getChar(int which, int x) {
		FontMetrics f = this.getGraphics().getFontMetrics();
		int cur_x = -parent.getOffsX() + ClickableTextArea.BORDER_WID;
		String line = parent.getLine(which);
		for(int i = 0; i < line.length(); i++) {
			int next_x = cur_x + f.charWidth(line.charAt(i));
			if(x >= cur_x && x < next_x) return i;
			cur_x = next_x;
		}
		return line.length();
	}
}

public class ClickableTextArea extends Panel
		implements AdjustmentListener, MouseListener, ComponentListener {
	public static final int BORDER_WID = 3;
	public static final int RT_WIDTH = 23;
	protected Vector listeners = new Vector();

	protected Scrollbar hbar;
	protected Scrollbar vbar;
	protected ClickableCanvasText canvas;

	protected Vector lines = new Vector();
	int max_width = 1;

	int offs_x = 0; // in pixels
	int offs_y = 0; // in lines
	int height = 1; // in lines
	int width = 1; // in pixels

	public ClickableTextArea() {
		// create the graphics region
		GridBagLayout layout = new GridBagLayout();
		GridBagConstraints c = new GridBagConstraints();
		c.fill = GridBagConstraints.BOTH;
		this.setLayout(layout);
		canvas = new ClickableCanvasText(this);
		canvas.addMouseListener(this);
		canvas.addComponentListener(this);
		c.gridx = 0; c.gridy = 0;
		c.weightx = 1.0; c.weighty = 1.0;
		layout.setConstraints(canvas, c);
		this.add(canvas);
		hbar = new Scrollbar(Scrollbar.HORIZONTAL);
		hbar.addAdjustmentListener(this);
		c.gridx = 0; c.gridy = 1;
		c.weightx = 1.0; c.weighty = 0.0;
		layout.setConstraints(hbar, c);
		this.add(hbar);
		vbar = new Scrollbar(Scrollbar.VERTICAL);
		vbar.addAdjustmentListener(this);
		c.gridx = 1; c.gridy = 0;
		c.weightx = 0.0; c.weighty = 1.0;
		layout.setConstraints(vbar, c);
		this.add(vbar);

		// initialize the text data
		lines.addElement("");
	}

	public int getOffsX() { return offs_x; }
	public int getOffsY() { return offs_y; }
	public String getLine(int which) {
		if(which >= lines.size()) return null;
		else return (String) lines.elementAt(which);
	}

	public void componentMoved(ComponentEvent e) { }
	public void componentShown(ComponentEvent e) { }
	public void componentHidden(ComponentEvent e) { }
	public void componentResized(ComponentEvent e) {
		// determine how many lines fit vertically
		int line_height = canvas.getGraphics().getFontMetrics().getHeight();
		height = (canvas.getSize().height - 2 * BORDER_WID) / line_height;
		width = canvas.getSize().width - 2 * BORDER_WID;

		sizeBars();
	}

	protected void sizeBars() {
		hbar.setValues(offs_x, width, 0, width >= max_width ? 1 : max_width);
		hbar.setBlockIncrement(max_width <= 1 ? 1 : max_width / 2);

		vbar.setValues(offs_y, height, 0, lines.size());
		vbar.setBlockIncrement(height == 1 ? 1 : height - 1);
	}

	public void paint(Graphics g) {
		canvas.paint(canvas.getGraphics());
	}

	public void addActionListener(TextAreaActionListener l) {
		listeners.addElement(l);
	}

	public void adjustmentValueChanged(AdjustmentEvent e) {
		if(e.getSource() == hbar) {
			offs_x = e.getValue();
			this.update(canvas.getGraphics());
		} else if(e.getSource() == vbar) {
			offs_y = e.getValue();
			this.update(canvas.getGraphics());
		}
	}


	public void mousePressed(MouseEvent e) { }
	public void mouseReleased(MouseEvent e) { }
	public void mouseEntered(MouseEvent e) { }
	public void mouseExited(MouseEvent e) { }
	public void mouseClicked(MouseEvent e) {
		int which = canvas.getLine(e.getY());
		int pos = canvas.getChar(which, e.getX());
		for(int i = 0; i < listeners.size(); i++) {
			((TextAreaActionListener) listeners.elementAt(i)).actionHeard(this, which, pos);
		}
	}

	public void setText(String s) {
		lines = new Vector();
		lines.addElement("");
		max_width = 1;
		offs_y = 0;
		offs_x = 0;
		canvas.setChanged();
		appendText(s);
	}
	public void appendText(String s) {
		int last_pos = 0;
		boolean changed = false;
		boolean draw_again = (lines.size() - 1 >= offs_y
								&& lines.size() - 1 < offs_y + height);
		for(int i = 0; i < s.length(); i++) {
			if(s.charAt(i) == '\n') {
				String old_line = getLine(lines.size() - 1);
				String new_line = old_line + s.substring(last_pos, i);
				lines.setElementAt(new_line, lines.size() - 1);
				int wid = canvas.getGraphics().getFontMetrics().stringWidth(new_line) + RT_WIDTH;
				if(wid > max_width) max_width = wid;
				lines.addElement("");
				last_pos = i + 1;
				changed = true;
			}
		}
		String old_line = getLine(lines.size() - 1);
		String new_line = old_line + s.substring(last_pos);
		lines.setElementAt(new_line, lines.size() - 1);
		int wid = canvas.getGraphics().getFontMetrics().stringWidth(new_line) + RT_WIDTH;
		if(wid > max_width) { changed = true; max_width = wid; }
		if(changed) sizeBars();
		if(draw_again) this.update(this.getGraphics());
	}
	public void setLine(int line, String str) {
		if(line >= lines.size()) return;
		replaceText(str, line, 0, getLine(line).length());
	}
	public void replaceText(String str, int line, int start, int end) {
		if(line >= lines.size()) return;
		String old_line = getLine(line);
		boolean draw_again = (line >= offs_y && line < offs_y + height);
		lines.setElementAt(old_line.substring(0, start) + str
			+ old_line.substring(end), line);
		int wid = canvas.getGraphics().getFontMetrics().stringWidth(getLine(line)) + RT_WIDTH;
		if(wid > max_width) {
			max_width = wid;
			sizeBars();
		}
		if(draw_again) {
			canvas.setChanged();
			this.update(this.getGraphics());
		}
	}
}
