/*
 Subcomponent (main part) of SmileyTextArea,
   which is intended to be part of Eteria IRC Client from Javier Kohen
 2001 written by Frank Bartels for Splendid Internet GmbH, Kiel, Germany
 
 						===O=V=E=R=V=I=E=W===
 						  (over this file)
 
 					   Constants & Variables
 							Constructor
 						   Private Utils
 						   Update & Paint
 							Mouse Events
 					 Component's User Interface
 	 Class Run  (a part of a line within attributes not changing)
   Class ContentLine  (a line of runs, is one or more lines on display)
 */
 
 package com.splendid.awtchat;
 
 import java.awt.*;
 import java.awt.event.*;
 import java.awt.image.ImageObserver;
 import java.util.*;
 
 import ar.com.jkohen.irc.MircMessage;
 import ar.com.jkohen.awt.NickInfoPopup;
 import ar.com.jkohen.awt.NewGraphics2D;
 import ar.com.jkohen.util.LinkProcess;
 
 
 public class SmileyTextAreaArea extends Canvas implements MouseListener, MouseMotionListener, AdjustmentListener, ComponentListener
 {
 
   //=======================================================================

   //					   Constants & Variables

   //=======================================================================

 
   private int bufferlen = 300; // number of lines saved by default

 							  // (virtual lines, i.e. lines independent of display line length)

 
   private int dimx = 300; // reflects actual component width (initalized in constructor)

   private int dimy = 200; // reflects actual component height (initalized in constructor)

   private int borderx = 4;
   private int bordery = 3;
   private int dimxv = 294; // view area

   private int dimyv = 196;
   int subsequentindent = 12; // indent for subsequent lines (inside one append with long string)

   Vector tabs;
 
   int SMILEY_DEFAULT_SIZE = 14;
 
   int paint_i = 0;
   int mode = SmileyTextArea.FAST; // FAST is default

   int oldSbValue = 0;
   boolean SB_WORKAROUND_ENABLED = false;
   Scrollbar sb = null;
   int sbValue = 0;
   int sbLength = 0;
   int offset = 0;
 	
   boolean inupdate = false;
   boolean inappend = false;
   boolean last_action_scrolling = false;
   boolean canBreak = true;
   boolean isSelectable = false;
 
   HyperlinkReceiver hyperlinkReceiver = null;
   CopyText copyText = null;
   Vector lines = null;
   SmileyTextArea sta = null;
   Dimension preferreddim; // reflects preferred size

 	
   // Color stuff

   static Color [] fixedColors = new Color[16]; // Init. in static block.

   private int urlColorIndex = 12;  // Standard url color

 
   private static final int bgColorIndex = 0; //16;  // background color index

   private static final int fgColorIndex = 1; //17;  // foreground color index

 
   Color backgroundColor;
   Color foregroundColor;
   Color mouseoverColor;
   Color selectedColor;
   
   // the default values are overwritten on construction

   private Font usedFonts[] = {null, null, null, null};
   private FontMetrics fmFonts[] = {null, null, null, null};
   private int fmSpaceFonts = 1;
   private int fmDescentFonts[] = {1, 1, 1, 1};
   private int fmAscentFonts[] = {1, 1, 1, 1};
   private int fmDY = 2;
   private int fmDescent = 1;
   private int fmAscent = 1;
   private int fmLeading = 0;
   
   // current settings of Graphics

   private int currentCombinedBoldItalic = 0;
   private boolean currentUnderlined = false;
   private int currentColorIndex = 0;
 
   private Vector contentLines; // instance variable for the list of ContentLines

   
   private Image img_buff;
   private Graphics gfx_buff;
   
   private NickInfoPopup nick_pop;
   private Point mouse_coords;
   private boolean focus = true;
   private int dateformat = 1;
   
   private static boolean has_window_focus = true;
   private static boolean area_animations = true;
 	
 
   //=======================================================================

   //							Constructor

   //=======================================================================

 
 
   public void adjustmentValueChanged(AdjustmentEvent e)
   {
   	setRawSbValue(e.getValue());
   }
   
   public static void enableAnimations(boolean b)
   {
   	area_animations = b;
   }
   
   public static void setWindowFocus(boolean b)
   {
   	has_window_focus = b;
   }
   
   public void focusGained()		
   {
 	focus = true;
 											// if above container gets the focus...

   	last_action_scrolling = false;			// ...redisplay, because hidden areas...

 	repaint();								// ...trash the view partially.

   }
 
   public void focusLost()
   {
 	focus = false;
   }
   
   public void componentResized(ComponentEvent e)
   {
   	dimensionInit();
   }
   
   public void componentHidden(ComponentEvent e)
   {
   }
   
   public void componentMoved(ComponentEvent e)
   {
   }
   
   public void componentShown(ComponentEvent e)
   {
 	dimensionInit();
 		
   	last_action_scrolling = false;
 	repaint();
   }
 
   public SmileyTextAreaArea(SmileyTextArea sta, Scrollbar sb, HyperlinkReceiver hr, CopyText cp, NickInfoPopup np)
   {
   	super();
 	this.sta = sta;
 	this.sb = sb;
 	hyperlinkReceiver = hr;
 	copyText = cp;
 	this.nick_pop = np;
 	fmDY = 1;
 
 	SB_WORKAROUND_ENABLED = ((new Scrollbar(Scrollbar.HORIZONTAL, 20, 10, 0, 20)).getValue() == 20);
 
 	backgroundColor = fixedColors[bgColorIndex];
 	super.setBackground(backgroundColor); // for consistency and against flickering

 	foregroundColor = fixedColors[fgColorIndex];
 	super.setForeground(foregroundColor); // for consistency only

 
 //	prepareFont(getFont());  getFont()==null at this point of time

 	prepareFont(sta.DEFAULT_FONT.getName(), sta.DEFAULT_FONT.getSize());
 
 	contentLines = new Vector();
 
 	sbLength = 0;
 	sbValue = 0;
 	correctSb();
 	sb.addAdjustmentListener(this);
 
 	//addFocusListener(this);				// Does not work since this does not get the keyboard focus... see focus_frame

 	addComponentListener(this);
 	dimensionInit();					// Initialize dimension by same means

 	addMouseListener(this);
 	addMouseMotionListener(this);
   }
 
   private Container focus_element = null;	// Sort of passive construction step...

   
   public void addNotify()
   {
   	super.addNotify();
 /*
 	if (focus_element == null)
 	{
 		focus_element = getContainer();
 		if (focus_element != null)
 			focus_element.addFocusListener(this);
 	}
 */
   }
   
   public Container getContainer()
   {
   	Container c = getParent();
 	do
 	{
 		if (c != sta && c instanceof java.awt.Container)
 			return ((java.awt.Container)c);
 	  	else
 			c = c.getParent();
 			
 	} while (c != null);
 	return null;
   }
   
   //=======================================================================

   //							Private Utils

   //=======================================================================

 
   private void correctSb()
   {
   	int len = dimyv / fmDY;
 	int scrollmax = sbLength - len;
 	if (scrollmax < 0)
 		scrollmax = 0;
 	if (sbValue > scrollmax)
 		sbValue = scrollmax; // needed for dimensionInit

 	if (sbValue < 0)
 		sbValue = 0;
 	int value = scrollmax - sbValue;
 	if (value < 0)
 		value = 0;
 //	sb.setValues(value * fmDY, len * fmDY, 0, (SB_WORKAROUND_ENABLED ? scrollmax : scrollmax + len) * fmDY);

 	sb.setValues(value, len, 0, (SB_WORKAROUND_ENABLED ? scrollmax : scrollmax + len));
   }
   
   // scrolling called from outside

   
   public void setSbTop()
   {
   	setRawSbValue(0);
   }
   
   protected void setRawSbValue(int value)
   {
 	// this synchonization is not very critical and it costs too much time:

 	// to be exact, it should be synchronized, because called from outside

 	
 //	offset = fmDY - (value % fmDY);

 	offset = 0;
 //	value = value / fmDY;

 	
 //	synchronized(this)

 //	{

 		int len = dimyv / fmDY;
 		int scrollmax = sbLength - len;
 		if (scrollmax < 0)
 			scrollmax = 0;
 		int sbValueOld = sbValue;
 		sbValue = scrollmax - value;
 		if (sbValue < 0)
 			sbValue = 0;
 //	}

 	if (sbValueOld != sbValue)
 	{
 		// (at end of paint())

 //		last_action_scrolling = true;

 		last_action_scrolling = false;
 
 		repaint();
 		correctSb();
 	}
   }
 
   private void prepareFont(String name, int size)
   {
   	usedFonts[0] = new Font(name, Font.PLAIN, size);
 	usedFonts[1] = new Font(name, Font.BOLD, size);
 	usedFonts[2] = new Font(name, Font.ITALIC, size);
 	usedFonts[3] = new Font(name, Font.BOLD | Font.ITALIC, size);
 	fmDY=0;
 	fmDescent=0;
 	fmAscent=0;
 	fmLeading=9999;
 	for (int i=0; i<4; i++)
 	{
 		FontMetrics fm=getFontMetrics(usedFonts[i]);
 		fmFonts[i]=fm;
 		fmSpaceFonts+=fm.stringWidth(" ");
 		fmDescentFonts[i]=fm.getMaxDescent();
 		fmAscentFonts[i]=fm.getMaxAscent();
 		//if (fmDY<fm.getHeight()) fmDY=fm.getHeight();				   // fmDY is max.(height)

 		if (fmDescent<fm.getMaxDescent()) fmDescent=fm.getMaxDescent(); // fmDescent is max.

 		if (fmAscent<fm.getMaxAscent()) fmAscent=fm.getMaxAscent();   // fmAscent is max.

 		if (fmLeading>fm.getLeading()) fmLeading=fm.getLeading();	 // fmLeading is min.

 	}
 	
 	if (fmLeading<0) fmLeading=0;
 	fmDY=fmDescent+fmAscent+fmLeading; // construction of abstract fm.getHeight() for all fonts

 	
 	fmSpaceFonts=(fmSpaceFonts+2)/4; // +2 in order to round; now fmSpaceFonts is mean value

 	
 	if (fmDY<fmDescent+fmAscent+fmLeading) // fmDY could be too small!

 	  fmDY=fmDescent+fmAscent+fmLeading;
 	  
 	if (fmDY<sta.maxSmileyHeight)
 		fmDY=sta.maxSmileyHeight;
 		
 	correctSb();
   }
 
   private synchronized void dimensionInit()
   {
 //  	(this) // must be synchronized, because called from outside

 //	{

 		Dimension d = getSize();
 		dimx = d.width;
 		dimy = d.height;
 		dimxv = dimx - borderx * 2;
 		dimyv = dimy - bordery * 2;
 		
 		sbLength = 0;
 		for (int i = contentLines.size() - 1; i &gt;= 0; i--)
 		{
 			ContentLine cl = (ContentLine)contentLines.elementAt(i);
 			cl.reconstruct();
 			sbLength += cl.lineDY;
 	  }
 	  if (sbValue &gt; sbLength)
 	  	sbValue = sbLength;
 	  correctSb();
 //	}

 	last_action_scrolling = false;
 	invalidate();
 	repaint();
   }
 
 /*
 	private Object[] getContentLine()
 	{
 		if (usedFonts[3] == null)
 			return(null);
   
 		p = new Point(p.x, p.y - offset);
 		int y0 = dimy - bordery + sbValue * fmDY;
    
 		if ((p.y > dimy - bordery) || (p.y < bordery))
 			return(null);
    
 		for (int i = contentLines.size() - 1; i >= 0; i--)
 		{
 			ContentLine cl = (ContentLine)contentLines.elementAt(i);
 			y0 -= cl.lineDY * fmDY;
 			if (p.y > y0)
 			{
 				Object o[] = new Object[] { cl, new Integer(y0) };
 				return(o);
 			}
 		}
 		return(null);
 	}
 */
 	private ContentLine getContentLine(Point p)
 	{
 		if (usedFonts[3] == null)
 			return(null);
   
 		p = new Point(p.x, p.y - offset);
 		int y0 = dimy - bordery + sbValue * fmDY;
    
 		if ((p.y > dimy - bordery) || (p.y < bordery))
 			return(null);
    
 		for (int i = contentLines.size() - 1; i &gt;= 0; i--)
 		{
 			ContentLine cl = (ContentLine)contentLines.elementAt(i);
 			y0 -= cl.lineDY * fmDY;
 			if (p.y &gt; y0)
 				return(cl);
 		}
 		return(null);
 	}
 	
 	private int getContentLineY(Point p)
 	{
 		if (usedFonts[3] == null)
 			return(0);
   
 		p = new Point(p.x, p.y - offset);
 		int y0 = dimy - bordery + sbValue * fmDY;
    
 		if ((p.y > dimy - bordery) || (p.y < bordery))
 			return(0);
    
 		for (int i = contentLines.size() - 1; i &gt;= 0; i--)
 		{
 			ContentLine cl = (ContentLine)contentLines.elementAt(i);
 			y0 -= cl.lineDY * fmDY;
 			if (p.y &gt; y0)
 				return(y0);
 		}
 		return(0);
 	}
 	
 	private Run getRun(Point p) // searching the run is analogous paint

 	{
 		p = new Point(p.x, p.y - offset);
  
 		ContentLine cl = getContentLine(p);
 		if (cl != null)
 		{
 			int y0 = getContentLineY(p) + fmLeading;
 		
 			// cl found, now search the run

 			int y = p.y - y0;
 	
 			for (Enumeration e = cl.runs.elements(); e.hasMoreElements(); )
 			{
 				Run run = (Run)e.nextElement();
 				int y2 = run.pixY0 + fmDY;
  
 				if (run.pixY0 == run.pixY1 && y < y2 && y > run.pixY1)  // only one line, check X0 and X1

 				{
 					if (p.x > run.pixX0 && p.x < run.pixX1) return(run);
 				}
 				else if (run.pixY0 != run.pixY1 && y < y2 && y < run.pixY1)  // line broken, first line, check X0

 				{
 					if (p.x > run.pixX0) return(run);
 				}
 				else if (run.pixY0 != run.pixY1 && y > y2 && y < run.pixY1)  // line broken, between first and last line

 				{
 					if (p.x > run.pixX0) return(run);
 				}
 				else if (run.pixY0 != run.pixY1 && y > y2 && y > run.pixY1 && y < run.pixY1 + y2)  // line broken, last line, check X1

 				{
 					if (p.x > run.pixX0 && p.x < run.pixX1) return(run);
 				}
 			}
 		}
   
 		return(null);
 	}
 
   private final void deleteFirstLine()
   {
   	if (contentLines.isEmpty())
 		return;
 	sbLength -= ((ContentLine)(contentLines.firstElement())).lineDY;
 	contentLines.removeElementAt(0);
 	if (paint_i > 0)
 		paint_i--;				// undo the effect of removeElementAt for paint if paint runs

 	if (sbValue > sbLength)
 		sbValue = sbLength; 	// not really nescessary...

 	correctSb();
   }
   //=======================================================================

   //						   Update & Paint

   //=======================================================================

 
 	private void createImageBuffer(int width, int height)
 	{
 		if (width < 1)
 			width = 1;
 		if (height < 1)
 			height = 1;
 			
 		this.img_buff = createImage(width, height);
 		if (gfx_buff != null)
 			gfx_buff.dispose();
 
 		if (img_buff != null)
 			gfx_buff = img_buff.getGraphics();
 		else
 			gfx_buff = getGraphics();
 	
 		if (NewGraphics2D.hasRenderingHints)
 			NewGraphics2D.setRendering(gfx_buff);
 	}
 	
 	
   public void update(Graphics g)
   {
   	inupdate = true;
 	paint(g);	// everything in paint, no clear, but no need to call from here (no effect)

 	inupdate = false;
   }
 
 	public synchronized void paint(Graphics g)
 	{
 		if (gfx_buff == null || img_buff.getWidth(this) != dimx || img_buff.getHeight(this) != dimy)
 		{
 			createImageBuffer(dimx, dimy);
 			paintBorder(g);
 		}
 			
 	  	if (usedFonts[3] == null)
 		{
 			Font f = getFont();
 			if (f == null)
 				return; // must have a font...

 			prepareFont(f.getName(), f.getSize());
 		}
 
 
 		int dyLines = sbValue - oldSbValue;
 		int dy = dyLines * fmDY;
 		oldSbValue = sbValue;
 		boolean scrolling = (Math.abs(dy) < (dimy * 2) / 3);
 		
 		if (!inupdate && !inappend)
 			scrolling = false; 	// scrolling only in update() or append()

 			
 		if (!last_action_scrolling)
 			scrolling = false; 	// scrolling only if scrolling or append

 		
 		if (mode == SmileyTextArea.FAST)
 			scrolling = false;	  // scrolling is possibly not safe

 	
 		if (!isShowing())
 			scrolling = false; 	// may be hidden by another container

 
 
 		if (scrolling && dy != 0)	
 		{
 			if (mode == SmileyTextArea.SMOOTH)
 			{
 				int step = (dy / fmDY);	// so far exact divison (rest == 0)

 				for (int yy = 0; Math.abs(yy) &lt; Math.abs(dy); yy += step)
 				{
 					if (yy != 0)
 					{
 						long time = System.currentTimeMillis() + 10;
 						while (System.currentTimeMillis() &lt; time)
 							Thread.yield();
 					}
 
 					if (dy &lt; 0)
 						gfx_buff.copyArea(2, -step + 2 + (yy - dy + step), dimx - 4, dimy - 4 + step + (dy - step), 0, step); // up

 					else
 						gfx_buff.copyArea(2, 2 + yy, dimx - 4, dimy - 4 - step - (dy - step), 0, step); // down

 
 					//area a littlebit too large (OK, but funny optics):

 	//				if (dy < 0)

 	//					g.copyArea(2, -step + 2, dimx - 4, dimy - 4 + step, 0, step); // up

 	//				else

 	//					g.copyArea(2, 2, dimx - 4, dimy - 4 - step, 0, step); // down

 					
 				
 					g.drawImage(img_buff, 0, 0, backgroundColor, this);
 				}
 			}
 			else
 			{
 				if (dy &lt; 0)
 					gfx_buff.copyArea(2, -dy + 2, dimx - 4, dimy - 4 + dy, 0, dy); // up

 				else
 					gfx_buff.copyArea(2, 2, dimx - 4, dimy - 4 - dy, 0, dy); // down

 			
 				g.drawImage(img_buff, 0, 0, backgroundColor, this);
 			}
 		}
 
 		if (bordery > 2)
 		{
 			gfx_buff.setColor(backgroundColor);
 			gfx_buff.fillRect(2, dimy - bordery, dimx - 4, bordery - 2);
 		}
 	
 		int lineRevY = -sbValue;
 		int maxLineRevY = (dimy - 4) / fmDY; // top

 		int minLineRevY = 0; // bottom

 
 		if (scrolling && dyLines &lt; 0)
 			maxLineRevY = -dyLines - 1;
 		
 		if (scrolling && dyLines >= 0)
 			minLineRevY = (dimy - 2 * bordery - 2 - dy) / fmDY;
 		
 		ContentLine pointed = null;
 		if (mouse_coords != null)
 			pointed = getContentLine(mouse_coords);
 			
 		Rectangle bounds = g.getClipBounds();
 		for (paint_i = contentLines.size() - 1; paint_i >= 0; paint_i--)
 		{
 		  	ContentLine cl = (ContentLine)contentLines.elementAt(paint_i);
 		  	
 		  	if (cl != null && cl == pointed)
 		  		cl.setMouseOver(true);
 		  	else
 		  		cl.setMouseOver(false);
 
 			if ((lineRevY + cl.lineDY) &lt; minLineRevY || lineRevY > maxLineRevY)
 			{
 				/*  below (skip)
 				** lineRevY + cl.lineDY < minLineRevY
 				**
 				** above (we are ready)
 				** lineRevY > maxLineRevY
 				*/
 
 				cl.enableAnimations(false);				
 				lineRevY += cl.lineDY;
 			}
 			else
 			{
 				cl.enableAnimations(true);
 				lineRevY = cl.paint(gfx_buff, lineRevY, bounds);
 			}
 		}
 
 	
 		if (!(scrolling && dyLines &lt;= 0)) // avoid clearing the top if scrolling up

 		{
 			int topdrawn = dimy - bordery - (lineRevY) * fmDY;
 			if (topdrawn > 2) // clear rest above textlines, if  there is a rest

 			{
 				gfx_buff.setColor(backgroundColor);
 				gfx_buff.fillRect(2, 2, dimx - 4, topdrawn);
 			}
 		}
 		
 
 		if (isShowing())
 		{
 			paintRollover(gfx_buff);
 			g.drawImage(img_buff, 0, 0, backgroundColor, this);
 		}
 
 		last_action_scrolling = true;
 	}
 
 	private void paintBorder(Graphics g)
 	{
 		gfx_buff.setColor(Color.gray);
 		int tempx1[] = {0, 0, dimx - 1};
 		int tempy1[] = {dimy - 1, 0, 0};
 		gfx_buff.drawPolyline(tempx1, tempy1, 3);
 		
 		gfx_buff.setColor(Color.black);
 		int tempx2[] = {1, 1, dimx - 2};
 		int tempy2[] = {dimy - 2, 1, 1};
 		gfx_buff.drawPolyline(tempx2, tempy2, 3);
 		
 		gfx_buff.setColor(Color.white);
 		int tempx3[] = {0, dimx - 1, dimx - 1};
 		int tempy3[] = {dimy - 1, dimy - 1, 0};
 		gfx_buff.drawPolyline(tempx3, tempy3, 3);
 		
 		gfx_buff.setColor(Color.lightGray);
 		int tempx4[] = {1, dimx - 2, dimx - 2};
 		int tempy4[] = {dimy - 2, dimy - 2, 1};
 		gfx_buff.drawPolyline(tempx4, tempy4, 3);
 		
 		gfx_buff.setClip(2, 2, dimx - 4, dimy - 4);
 	}
 	
 	private void paintRollover(Graphics g)
 	{
 		nick_pop.setVisible(false);
 		
 		if (mouse_coords != null)
 		{
 			String n = null;
 			ContentLine cl = getContentLine(mouse_coords);
 			if (cl != null)
 			{
 				cl.setMouseOver(true);
 				
 				n = cl.nick;
 				if (n != null)
 				{
 					nick_pop.setNick(n);
 					nick_pop.setVisible(true);
 					nick_pop.setLocation(new Point(mouse_coords.x + 32, mouse_coords.y + nick_pop.getSize().height));
 					nick_pop.paint(g);
 				}
 			}			
 		}
 	}
   
 	//=======================================================================

 	//							Mouse Events

 	//=======================================================================

 
 	public void mouseReleased(MouseEvent ev)
 	{
 	}
   
 	public void mousePressed(MouseEvent ev)
 	{
 		// This is the left click to select a line of text

 		
 		if ((ev.getModifiers() & MouseEvent.BUTTON3_MASK) == 0 && isSelectable)
 		{
 			ContentLine clicked_cl = getContentLine(ev.getPoint());
 			if (clicked_cl != null)
 				clicked_cl.setMouseOver(true);
 		
 			for (int i = 0; i < contentLines.size(); i++)
 			{
 				ContentLine cl = (ContentLine)contentLines.elementAt(i);
 				if (clicked_cl == cl)
 					cl.setSelected(!cl.isSelected());
 				else
 					cl.setSelected(false);
 			}
 		
 			last_action_scrolling = false;
 			repaint();
 		}
 
 		// This is the right click to get a cut-paste window

 				
 		if (ev.isPopupTrigger() || (ev.getModifiers() & MouseEvent.BUTTON3_MASK) != 0)
 		{
 			String s = "";
 			for (int i = 0; i < contentLines.size(); i++)
 			{
 				ContentLine cl = (ContentLine)contentLines.elementAt(i);
 				s = s + cl.text + "\n";
 			}
 			if (copyText != null)
 				copyText.addText(s);
 		}
 	}
   
 	public void mouseEntered(MouseEvent ev)
 	{
 		if (!has_window_focus || !isShowing())
 			return;
 			
 		mouse_coords = ev.getPoint();
 		last_action_scrolling = false;
 		repaint();
 	}
   
 	public void mouseExited(MouseEvent ev)
 	{
 		mouse_coords = null;
 		last_action_scrolling = false;
 		repaint();
 	}
 
 	public void mouseClicked(MouseEvent ev)
 	{
 		Run run = getRun(ev.getPoint());
 		if (run != null)
 		{
 			if (!run.getUrl().equals("") && hyperlinkReceiver != null)
 				hyperlinkReceiver.handleHyperlink(run.url);
 			
 			if (!run.getNick().equals("") && hyperlinkReceiver != null)
 				hyperlinkReceiver.handleNick(run.nick);
 		}
 	}
 
 	public void mouseMoved(MouseEvent ev)
 	{
 		if (!has_window_focus || !isShowing())
 			return;
 			
 		mouse_coords = ev.getPoint();
 		last_action_scrolling = false;
 		repaint();
 
 	  	Run run = getRun(mouse_coords);
 		if (run != null)
 		{
 			if (!(run.getUrl().equals("") && run.getNick().equals("")) && hyperlinkReceiver != null)
 			{
 				setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
 				return;
 			}
 		}
 	  	setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
 	}
 
 	public void mouseDragged(MouseEvent ev)
 	{
 	}
   
 
 
   //=======================================================================

   //						Component's User Interface

   //=======================================================================

 
 	public void append(String s, boolean interpretUrl)
 	{
 		addContent(s, interpretUrl, null);
 	}
 
 	public void append(String s, boolean interpretUrl, String n)
 	{
 		addContent(s, interpretUrl, n);
 	}
 	
 	public void clean()
 	{
 	  	contentLines.removeAllElements();
 		if (gfx_buff != null)
 		{
 			gfx_buff.dispose();
 			gfx_buff = null;
 		}
 		dimensionInit();
 	}
 	
   public void addContent(String s, boolean interpretUrl, String n)
   {
 	if (s == null || s.length() == 0)
 		s = "\n";
 	synchronized(this)
 	{
   		ContentLine cl = new ContentLine(s, interpretUrl, n);
   		cl.reconstruct();
 		contentLines.addElement(cl);
 		
 		sbLength += cl.lineDY;
 //		sbValue = 0;

 		if (oldSbValue == 0)
 			oldSbValue += cl.lineDY;
 		else
 			oldSbValue = -999;
 		correctSb();
 		
 		if (bufferlen >= 0 && contentLines.size() > bufferlen)
 			deleteFirstLine();
 			
 		if (sbValue != 0 || mode == SmileyTextArea.SAFE)
 		{
 			/* User has moved up scrollbar, so content's autoscroll must stop */
 			sbValue++;
 		}
 	}
 
 	repaint();
   }
   
   public void setMode(int mode)
   {
   	synchronized(this)
 	{
 		this.mode = mode;
 		last_action_scrolling = false;
 	}
 	repaint();
   }
   
   public void setBreaks(boolean canBreak)
   {
   	this.canBreak = canBreak;
   }
 
   public void addTab(int t)
   {
   	if (tabs == null)
   		tabs = new Vector();
   	tabs.addElement(new Integer(t));
   }
   
   public void setSubsequentIndent(int indent)
   {
   	synchronized(this)
 	{
 		subsequentindent = indent;
 		last_action_scrolling = false;
 	}
 	dimensionInit();
 	// repaint(); (included in dimensionInit())

   }
   
   public int getSubsequentIndent()
   {
   	return(subsequentindent);
   }
 
   public void setBufferlen(int bufferlen)
   {
   	synchronized(this)
 	{
 		this.bufferlen = bufferlen;
 		while (bufferlen >= 0 && contentLines.size() > bufferlen)
 			deleteFirstLine();
 	}
   }
   
   public int getBufferlen()
   {
   	return(bufferlen);
   }
 
   public void setBackground(Color c)
   {
   	synchronized(this)
 	{
 		fixedColors[bgColorIndex] = c;
 		backgroundColor = c;
 		super.setBackground(backgroundColor); // for consistency and against flickering

 		last_action_scrolling = false;
 	}
 	repaint();
   }
   
   public Color getBackground()
   { return(fixedColors[bgColorIndex]);
   }
   
   public void setForeground(Color c)
   { synchronized(this)
 	{ fixedColors[fgColorIndex]=c;
 	  foregroundColor=c;
 	  super.setForeground(foregroundColor); // for consistency only

 	  last_action_scrolling=false;
 	}
 	repaint();
   }
   
   public Color getForeground()
   {
 	return(fixedColors[fgColorIndex]);
   }
   
   public void setSelectedBackground(Color c)
   {
 	mouseoverColor = c;
   }
   
   public Color getSelectedBackground()
   {
 	return(mouseoverColor);
   }
 
 	public static void setColorPalette(Color palette[])
 	{
 		fixedColors = palette;
 	}
   
   public void setColorPaletteEntry(int n, Color c)
   {
   	synchronized(this)
 	{
 		fixedColors[(n % 16)] = c;
 		last_action_scrolling = false;
 	}
 	repaint();
   }
   
   public Color getColorPaletteEntry(int n)
   {
   	return(fixedColors[(n % 16)]);
   }
 
   public void changeFont(String name, int size)
   {
   	synchronized(this)
 	{
 		prepareFont(name, size);
 		last_action_scrolling = false;
 	}
 	dimensionInit();
 	// repaint(); (included in dimensionInit())

   }
   
   public Font getFont()
   {
 	return(usedFonts[0]);
   }
 
   //=======================================================================

   //	 Class Run  (a part of a line within attributes not changing)

   //=======================================================================

 
 	// === class Run represents a text fragment including attributes ===

 	
   private class Run implements Observer, ImageObserver
   { public String text = null;
 	public String url = null;
 	public String nick = null;
 	public boolean display = true;
 	public int pixX0 = 0; // Start(0)

 	public int pixY0 = 0;
 	public int pixX1 = 0; // End(1)

 	public int pixY1 = 0;
 	public int nbreaks = 0;
 	public Vector breaks = null; // if the Run splits at lineends the text indices are saved here

 	public int combinedBoldItalic = 0;
 	public boolean underlined = false;
 	public int fgColorIndexMine = fgColorIndex;
 	public int bgColorIndexMine = bgColorIndex;
 	private Image smiley = null;
 	private boolean animation = true;
 	private int repaint_x, repaint_y, repaint_w, repaint_h;
 
 	// Constructor (text)

 	
 	public Run(String atext, boolean bold, boolean italic, boolean underlined, int fgColorIndex, int bgColorIndex)
 	{
 		char c;
 		int len = atext.length();
 		text = "";
 		for (int i = 0; i < len; i++)
 		{
 			c = atext.charAt(i);
 			if (Character.isISOControl(c))
 				continue; // skip control characters

 			if (!Character.isDefined(c))
 				continue; // skip undefined characters

 			text += c;
 		}
 	  	this.combinedBoldItalic = bold ? (italic ? 3 : 1) : (italic ? 2 : 0);
 	  	this.underlined = underlined;
 	  	this.fgColorIndexMine = fgColorIndex;
 	  	this.bgColorIndexMine = bgColorIndex;
 	}
 	
 	// Constructor (smiley)

 	
 	public Run(String text, Image smiley, int bgColorIndex)
 	{
 		this.text = text;
 		this.smiley = smiley;
 		this.bgColorIndexMine = bgColorIndex;
 	}
 	
 	// if URL, you need this second construction step

 	
 	public void setUrl(String url)
 	{
 		this.url = url;
 		// preserve bold and italic and bgColorIndexMine

 		underlined = true;
 		fgColorIndexMine = urlColorIndex;
 	}
 
 	public void setNick(String nick)
 	{
 		this.nick = nick;
 	}
 	
 	public void setDisplay(boolean b)
 	{
 		this.display = b;
 	}
 	
 	public boolean display()
 	{
 		return(display);
 	}
 	
 	private int mySpaceIndex(String s, int i)
 	{
 		int len = s.length();
 	  	for (int j = i; j < len; j++)
 			if (Character.isSpaceChar(s.charAt(j)))
 				return(j);
 			
 	  	return(-1);
 	}
 
 	public int setXY(int newPixX, int newPixY, int line) // prepare the Run for display

 	{
 		String s = text;
 		int loopcount, lasti, i, x, snipped = 0;
 		
 	  	if (newPixX >= dimxv - fmSpaceFonts * 2) // break Run, needed for speed and smileys

 	  	{
 	  		newPixX = borderx + subsequentindent;
 			newPixY += fmDY;
 			line++;
 	  	}
 	  	
 	  	breaks = null;
 	  	nbreaks = 0;
 		pixX0 = newPixX;
 	  	pixY0 = newPixY;
 	  	if (smiley != null)
 	  	{
 	  		pixX1 = newPixX + smiley.getWidth(null);
 			pixY1 = newPixY;
 			if (pixX1 > dimx - borderx)
 			{
 				pixX0 = borderx + subsequentindent;
 		  		pixY0 += fmDY;
 		  		pixX1 = borderx + subsequentindent + smiley.getWidth(null);
 		  		pixY1 += fmDY;
 		  		line++;
 			}
 			return(line); // smiley case ready

 	  	}
 	  
 		pixX1 = newPixX + fmFonts[combinedBoldItalic].stringWidth(s);
 		pixY1 = newPixY;
 	  
 		if (smiley != null)
 			return(line); // do not break inside a smiley

 	  	
 		loopcount = 0;
 		while (pixX1 > dimx - borderx) // then must break line

 		{
 //			if (loopcount++ > 100)

 //				break; // avoid deadlocks (although there are none ;-)

 			
 			for (lasti = 0, i = 0; (i = mySpaceIndex(s, i)) >= 0; )
 			{
 				x = newPixX + fmFonts[combinedBoldItalic].stringWidth(s.substring(0, i));
 				if ( x > dimx - borderx)
 					break; // found proper break (the last one on actual line)

 				i++; // pos of next char after space

 //				while (i < s.length() && Character.isSpaceChar(s.charAt(i)))

 //					i++; // hop over spaces

 		 		 lasti = i;
 			}
 			
 			if (lasti == 0) // break complete s

 			{
 				if (newPixX <= borderx + subsequentindent) // already gone to the next line?

 				{
 					// the (spaceless) s does not fit on a line! break by force somewhere

 					for (lasti = 1, i = 1; i < s.length() - 1; i++)
 					{
 						x = newPixX + fmFonts[combinedBoldItalic].stringWidth(s.substring(0, i + 1));
 						if (x > dimx - borderx)
 						{
 							lasti = i;
 							break;
 						}
 					}
 				}
 				else
 				{
 					newPixX = borderx + subsequentindent;
 					if (pixY0 == pixY1 && breaks == null) // if break at beginning of run...

 					{
 						pixX0 = borderx + subsequentindent; // ...the beginning also must go on next line

 						pixY0 += fmDY;
 					}
 					pixX1 = newPixX + fmFonts[combinedBoldItalic].stringWidth(s);
 					pixY1 += fmDY;
 					line++;
 					continue; // retry with linebreak (almost nothing to do for this) before

 				}
 			}
 		
 			if (lasti > 0 && lasti < s.length()) // break line (can not use else!)

 			{
 				if (!canBreak)
 					return(line);
 				
 				if (breaks == null)
 					breaks = new Vector();
 				breaks.addElement(new Integer(lasti + snipped));
 				nbreaks++;
 				s = s.substring(lasti);
 				snipped += lasti;
 			}
 			
 	  
 			newPixX = borderx + subsequentindent;
 			pixX1 = newPixX + fmFonts[combinedBoldItalic].stringWidth(s);
 			pixY1 += fmDY;
 			line++;
 			
 			/* No more chars to process */
 			if (lasti >= s.length())
 				break;
 		}
 	  
 		return(line);
 	}
 
 	private void print(Graphics g, String s, int x, int y)
 	{
 	   	if (smiley != null)
 		{
 		  	int dy=smiley.getHeight(this);
 		  	if (dy==0)
 		  		dy=SMILEY_DEFAULT_SIZE;
 			int ascent=fmAscentFonts[combinedBoldItalic];
 			int descent=fmDescentFonts[combinedBoldItalic];
 			int yoff=-(ascent-descent+dy)/2; // put in center between ascent and descent

 			if (yoff+dy-1>descent) yoff=descent-dy+1;  // but not below descent

 
 			if (bgColorIndexMine!=bgColorIndex) // Not the normal background color->paint bg first

 			{
 				int len=smiley.getWidth(this); if (len==0) len=SMILEY_DEFAULT_SIZE;
 				g.setColor(fixedColors[bgColorIndexMine]);
 	   			g.fillRect(x, y-ascent, len, ascent + descent);
 								  // +1 because rectangle goes only to n-1 on bottom

 								  // -1 because from -ascent to descent (incl) is 1 too much

 		  		g.setColor(fixedColors[fgColorIndexMine]);
 			}
 
 			g.drawImage(smiley, x, y + yoff, this);
 			
 			/* Save image coordinates for later animations */
 			repaint_x = x;
 			repaint_y = y + yoff;
 			repaint_w = smiley.getWidth(this);
 			repaint_h = dy;
 	  	}
 	  	else
 	  	{
 	  	  	if (bgColorIndexMine!=bgColorIndex) // Not the normal background color->paint bg first

 	  	  	{
 	  	  		int len=fmFonts[combinedBoldItalic].stringWidth(s);
 	  			int ascent=fmAscentFonts[combinedBoldItalic];
 	  			g.setColor(fixedColors[bgColorIndexMine]);
 	  			g.fillRect(x, y-ascent, len, ascent + fmDescentFonts[combinedBoldItalic]);
 								  // +1 because rectangle goes only to n-1 on bottom

 								  // -1 because from -ascent to descent (incl) is 1 too much

 				g.setColor(fixedColors[fgColorIndexMine]);
 			}
 
 			g.drawString(s, x, y);
 
 		if (underlined) // underlined->add the line

 		{
 			int len=fmFonts[combinedBoldItalic].stringWidth(s);
 			int below=fmDescentFonts[combinedBoldItalic]/4;
 		  	if (below<1) below=1; // otherwise the underline line sticks to the font

 		  	// if (getItalic())

 		  	//   would like to subtract something from len, but I don't know how much

 		  	g.drawLine(x,y+below,x+len-1,y+below); // -1 makes it comaptible with fillRect len

 		  	if (getBold())
 				g.drawLine(x,y+below+1,x+len-1,y+below+1);
 		}
 	  }
 	}
 	
 	public void update(Observable obs, Object o)
 	{
 		if (o instanceof Boolean)
 			animation = ((Boolean)o).booleanValue(); 
 	}
 	
 	public boolean imageUpdate(Image img, int flags, int x, int y, int w, int h)
 	{
 		if (smiley != null && animation && area_animations && isShowing())
 		{
 			/* Repaint an animated image only if relevant */
 			last_action_scrolling = false;
 			repaint(repaint_x, repaint_y, repaint_w, repaint_h);
 			return(true);
 		}
 		return(false);
 	}
 	
 	public void paint(Graphics g, int y0, boolean force)
 	{
 		int x = pixX0;
 		int y = y0 + pixY0;
 		setMyGraphicsAttributes(g, force);
 		if (nbreaks == 0) // makes simple case fast

 		{
 			print(g, text, x, y);
 		}
 		else
 		{
 			int oldj = 0;
 			int j = 0;
 			for (int i = 0; i &lt;= nbreaks; i++)
 			{
 				if (i == nbreaks)
 					j = text.length();
 				else
 					j = ((Integer)(breaks.elementAt(i))).intValue();
 				print(g, text.substring(oldj, j), x, y);
 				x = borderx + subsequentindent;
 				y += fmDY;
 				oldj = j;
 			}
 	  	}
 	}
 
 	public void setMyGraphicsAttributes(Graphics g, boolean force)
 	{
 		if (currentCombinedBoldItalic != combinedBoldItalic || force)
 		{
 			g.setFont(usedFonts[combinedBoldItalic]);
 			currentCombinedBoldItalic = combinedBoldItalic;
 	  	}
 	  	if (currentColorIndex != fgColorIndexMine || force)
 	  	{
 	  		g.setColor(fixedColors[fgColorIndexMine]);
 			currentColorIndex = fgColorIndexMine;
 	  	}
 	  	// currentUnderlined != underlined does not matter for g

 	}
 
 	public String getUrl()
 	{
 		return(url == null ? "" : url);
 	}
 	
 	public String getNick()
 	{
 		return(nick == null ? "" : nick);
 	}
 	
 	public boolean getBold()
 	{
 		return((combinedBoldItalic & 1) == 1);
 	}
 
 	public boolean getItalic()
 	{
 		return((combinedBoldItalic & 2) == 2);
 	}
 	
 	public boolean getUnderlined()
 	{
 		return(underlined);
 	}
 
 	public Color getColor()
 	{
 		return(fixedColors[fgColorIndexMine]);
 	}
 
 	public Font getFont()
 	{
 		return(usedFonts[combinedBoldItalic]);
 	}
 
   } // ============ End of class Run ============

 
   //=======================================================================

   //  Class ContentLine  (a line of runs, is one or more lines on display)

   //=======================================================================

 
   private class ContentLine extends Observable // === class ContentLine represents an abstract line of text ===

   { public String text;
 	public Vector runs;
 	public int lineStart;
 	public int lineEnd;
 	public int lineDY=1;
 	public boolean mouse_over;
 	public boolean line_selected;
 	public String nick;
 	
 	// Constructor (parses string and produces runs)

 
 	public ContentLine(String s, boolean interpretUrl, String n)
 	{
 		boolean boldCurrent = false;
 		boolean italicCurrent = false;
 		boolean underlineCurrent = false;
 		int bgColorIndexCurrent = bgColorIndex;
 		int fgColorIndexCurrent = fgColorIndex;
 
 		if (s.length() == 0)
 			return; // empty line, no runs

 			
 		// runs.addElement(new Run(s,false,false,false,fgColorIndex,bgColorIndex));

 		//						(simple version (without parsing))

 		text = s;
 		nick = n;
 		runs = new Vector();
 
 	  int hlength = 0;
 	  int start = 0;
 	  int len = s.length();
 	  int nicklen = (nick != null ? nick.length() : 0);
 	  for (int i = 0; i &lt; len; i++)
 	  {
 	  	char ch = s.charAt(i);
 	  	
 		// find potential href's end and check for email

 /*
 		if (i > hrefend)
 		{
 			hrefend = findEndOfHref(s, i);
 			if (interpretUrl && hrefend < len && i < hrefend)
 			{
 				// is email address ?
 			
 			   if(s.charAt(hrefend) == '@')
 			   {
 			   		if (start < i)
 						runs.addElement(new Run(s.substring(start, i), boldCurrent, italicCurrent, underlineCurrent, fgColorIndexCurrent, bgColorIndexCurrent));
 						
    					int emailend = findEndOfHref(s, hrefend + 1);
    					String emailstr = s.substring(i, emailend);
    					Run run = new Run(emailstr, boldCurrent, italicCurrent, underlineCurrent, fgColorIndexCurrent, bgColorIndexCurrent);
    				
 				 	// add protocol mailto: to URL saved in run if missing so far
 				 	
 				 	if (emailstr.length() >= 7)
 				 	{
 						emailstr = "mailto:" + emailstr;
 				 	}
 				 	else
 				 	{
 				   		if (!emailstr.substring(0, 7).toUpperCase().equals("MAILTO:"))
 					 		emailstr = "mailto:" + emailstr;
 					}
 				 	run.setUrl(emailstr);
 				 	runs.addElement(run);
 				 	i = emailend - 1;
 				 	start = i + 1;
 				 	continue;
 			   }
 			}
 		}
 */
 		// check for href (including #channel)

 		LinkProcess lp = new LinkProcess(s.substring(i));
 		if (interpretUrl && lp.isLink())
 		{
 			if (start &lt; i)
 				runs.addElement(new Run(s.substring(start,i), boldCurrent, italicCurrent, underlineCurrent, fgColorIndexCurrent, bgColorIndexCurrent));
 			
 			hlength = lp.getLength();
 		  	String urlstr = s.substring(i, i + hlength);
 			Run run = new Run(urlstr, boldCurrent, italicCurrent, underlineCurrent, fgColorIndexCurrent, bgColorIndexCurrent);
 		  
 //			if (urlstr.length() >= 8 && urlstr.indexOf("://") == -1)

 //				if (urlstr.indexOf("@") > 0)

 //					urlstr = "mailto:" + urlstr;

 
 		  	run.setUrl(lp.getLink());
 		  	runs.addElement(run);
 		  	i += hlength;
 		  	start = i;
 		  	continue;	
 		}
 
 		// check for smileys

 		
 		if (sta.smileys != null)
 		{
 			int imax = i + sta.maxCodeLength;
 			if (imax > len)
 				imax = len;
 			String smax = s.substring(i, imax);
 			Image image;
 			while (smax.length() > 0)
 			{
 				if ((image = (Image)sta.smileys.get(smax)) != null)		// found (longest) smiley

 				{
 					if (start &lt; i)
 						runs.addElement(new Run(s.substring(start, i), boldCurrent, italicCurrent, underlineCurrent, fgColorIndexCurrent, bgColorIndexCurrent));
 					Run r = new Run(smax, image, bgColorIndexCurrent);
 					runs.addElement(r);
 					addObserver(r);		// add an observer to enable or disable animated images

 					i += smax.length() - 1;
 					start = i + 1;
 					break;
 				}
 				smax = smax.substring(0, smax.length() - 1);
 			}
 		}
 		
 		// check for control codes

 		
 		switch(ch)
 		{ case MircMessage.BELL:
 			Toolkit.getDefaultToolkit().beep();
 			break;
 		  case MircMessage.BOLD:
 		  case MircMessage.ITALIC:
 		  case MircMessage.UNDERLINE:
 		  case MircMessage.COLOR:
 		  case MircMessage.REVERSE:
 		  case MircMessage.RESET:		// is control code

 			if (start&lt;i)
 			  runs.addElement(new Run(s.substring(start,i), boldCurrent,italicCurrent,underlineCurrent, fgColorIndexCurrent,bgColorIndexCurrent));
 			start=i+1;
 			switch(ch)
 			{ case MircMessage.BOLD:
 				boldCurrent=!boldCurrent;
 				break;
 			  case MircMessage.ITALIC:
 				italicCurrent=!italicCurrent;
 				break;
 			  case MircMessage.UNDERLINE:
 				underlineCurrent=!underlineCurrent;
 				break;
 			  case MircMessage.COLOR:
 				// parse color(s) 0,0 ... 15,15 (... 99,99 mapped modulo 16)

 				{ int j,k;
 				  char c;
 				  int fgv=-1;
 				  int bgv=-1;
 				  for (j=i+1; j&lt;len && j&lt;=i+2; j++)
 				  { c=s.charAt(j);
 					if (!Character.isDigit(c)) break;
 					if (fgv&lt;0) fgv=0;
 					fgv=fgv*10+Character.digit(c,10);
 				  }
 				  if (j&lt;len && s.charAt(j)==',')
 				  { k=j;
 					for (j=k+1; j&lt;len && j&lt;=k+2; j++)
 					{ c=s.charAt(j);
 					  if (!Character.isDigit(c)) break;
 					  if (bgv&lt;0) bgv=0;
 					  bgv=bgv*10+Character.digit(c,10);
 					}
 				  }
 				  if (fgv>=0) fgColorIndexCurrent=fgv % 16;
 				  if (bgv>=0) bgColorIndexCurrent=bgv % 16;
 				  if (-1==fgv && -1==bgv)
 				  { fgColorIndexCurrent=fgColorIndex;
 					bgColorIndexCurrent=bgColorIndex;
 				  }
 				  i=j-1;
 				  start=i+1;
 				}
 				break;
 			  case MircMessage.REVERSE:
 				int saveci=bgColorIndexCurrent;
 				bgColorIndexCurrent=fgColorIndexCurrent;
 				fgColorIndexCurrent=saveci;
 				break;
 			  case MircMessage.RESET:
 				boldCurrent=false;
 				italicCurrent=false;
 				underlineCurrent=false;
 				bgColorIndexCurrent=bgColorIndex;
 				fgColorIndexCurrent=fgColorIndex;
 				break;
 			}
 			break;
 		}
 		
 		// Set a clickable run

 		if (n != null && (len - start >= nicklen))
 		{
 			if (s.substring(start, start + nicklen).equals(nick))
 			{
 				Run r = new Run(nick, boldCurrent, italicCurrent, underlineCurrent, fgColorIndexCurrent, bgColorIndexCurrent);
 				r.setNick(nick);
 				runs.addElement(r);
 				start += nicklen;
 				n = null;
 			}
 		}
 		
 	  } // end of for (int i=0; i<len; i++)

 	  
 	  if (start&lt;len) // take the rest

 		runs.addElement(new Run(s.substring(start,len),boldCurrent,italicCurrent,underlineCurrent,fgColorIndexCurrent,bgColorIndexCurrent));
 	}
 
 	public void reconstruct() // reconstruct ContentLine (prepare for display)

 	{
 		int x = borderx;
 		int y = -1; // inside a line think from top left as (0, 0)

 		int line = 0;
 		boolean display = true;
 		
 		int tabIndex = 0;
 		
 		for (Enumeration e = runs.elements(); e.hasMoreElements(); )
 	  	{
 			Run run = (Run)e.nextElement();
 			line = run.setXY(x, y, line);
 			
 			if (line > 0 && !canBreak)
 			{
 				line = 0;
 				display = false;
 			}
 			run.setDisplay(display);
 			
 			if (tabs != null && tabIndex &lt; tabs.size())
 			{
 				x = ((Integer)tabs.elementAt(tabIndex)).intValue();
 				tabIndex++;
 			}
 			else
 			{
 				x = run.pixX1; //+fmSpaceFonts;

 			}
 			y = run.pixY1;
 		}
 		lineDY = line + 1;
 	}
 
 	int paint(Graphics g, int lineRevY, Rectangle buf_clip)
 	{
 		if (lineRevY >= -lineDY + 1) // clipping of other edge in SmileyTextAreaArea.paint()

 		{
 			g.setColor(backgroundColor);
 			if (mouse_over)
 				g.setColor(mouseoverColor);
 				
 			if (line_selected)
 				g.setColor(selectedColor);
 				
 			//int y1 = dimy - bordery - 1 - fmDescent - lineRevY * fmDY - (lineDY - 1) * fmDY;

 			int y = dimy - bordery - (lineRevY + lineDY) * fmDY; // top

 
 			/* Painting must satisfy both off-screen clipping and on-screen clipping */
 			Rectangle scr_clip = g.getClipBounds();
 //			if (y > (buf_clip.y - fmDY) && y < (buf_clip.y + buf_clip.height + fmDY) && y > (scr_clip.y - fmDY) && y < (scr_clip.y + scr_clip.height + fmDY))

 //			if (y >= (buf_clip.y - (lineDY * fmDY)) && y <= (buf_clip.y + buf_clip.height))

 			if (y >= (buf_clip.y - (lineDY * fmDY)) && y &lt;= (buf_clip.y + buf_clip.height) && y >= (scr_clip.y - (lineDY * fmDY)) && y &lt;= (scr_clip.y + scr_clip.height))
 			{
 				g.fillRect(2, y, dimx - 3, lineDY * fmDY);
 				y += fmDY - fmDescent; // baseline of top text line

 				int n = 0;
 				for (Enumeration e = runs.elements(); e.hasMoreElements(); )
 				{
 					Run run = (Run)e.nextElement();
 					if (run.display())
 						run.paint(g, y, (n++) == 0);
 				}
 			}
 		}
 		return(lineRevY + lineDY);
 	}
 	
 	public void enableAnimations(boolean b)
 	{
 		setChanged();
 		notifyObservers(new Boolean(b));
 	}
 
 	public void setSelected(boolean b)
 	{
 		line_selected = b;
 	}
 	
 	public boolean isSelected()
 	{
 		return(line_selected);
 	}
 	
 	public void setMouseOver(boolean b)
 	{
 		mouse_over = b;
 	}
 
 	public void add(Run run)
 	{
 		runs.addElement(run);
 	}
 	
   } // ============ End of class ContentLine ============

 
 }