/*
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.text.SimpleDateFormat;
import java.util.*;
import ar.com.jkohen.irc.MircMessage;
import ar.com.jkohen.awt.NickInfoPopup;
public class SmileyTextAreaArea extends Canvas implements MouseListener, MouseMotionListener, AdjustmentListener, FocusListener, ComponentListener
{
//=======================================================================
// Constants & Variables
//=======================================================================
private int bufferlen = 200; // 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 INTER = 6;
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;
static int WAITCOUNT = 0; // this constant is calculated on construction
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;
//=======================================================================
// Constructor
//=======================================================================
public void adjustmentValueChanged(AdjustmentEvent e)
{
setRawSbValue(e.getValue());
}
public void focusGained(FocusEvent e)
{
// System.out.println("focusGained");
// if above container gets the focus...
last_action_scrolling = false; // ...redisplay, because hidden areas...
repaint(); // ...trash the view partially.
}
public void focusLost(FocusEvent e)
{
}
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);
}
private 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();
}
}
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 + INTER; // 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;
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 >= 0; i--)
{
ContentLine cl = (ContentLine)contentLines.elementAt(i);
cl.reconstruct();
sbLength += cl.lineDY;
}
if (sbValue > 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 >= 0; i--)
{
ContentLine cl = (ContentLine)contentLines.elementAt(i);
y0 -= cl.lineDY * fmDY;
if (p.y > 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 >= 0; i--)
{
ContentLine cl = (ContentLine)contentLines.elementAt(i);
y0 -= cl.lineDY * fmDY;
if (p.y > 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;
int dyline = fmAscent + fmDescent;
for (Enumeration e = cl.runs.elements(); e.hasMoreElements(); )
{
Run run = (Run)e.nextElement();
int y2 = run.pixY0 + dyline + INTER;
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)
this.gfx_buff = img_buff.getGraphics();
else
this.gfx_buff = getGraphics();
}
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.SAFE)
scrolling = false; // scrolling is possibly not safe
if (!isShowing())
scrolling = false; // may be overlapped by another window
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) < Math.abs(dy); yy += step)
{
if (yy != 0)
{
try
{
Thread.sleep(5);
} catch (InterruptedException e) {}
}
if (dy < 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 < 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 < 0)
maxLineRevY = -dyLines - 1;
if (scrolling && dyLines >= 0)
minLineRevY = (dimy - 2 * bordery - 2 - dy) / fmDY;
for (int i = 0; i < contentLines.size(); i++)
{
ContentLine cl = (ContentLine)contentLines.elementAt(i);
cl.setMouseOver(false);
}
if (mouse_coords != null)
{
ContentLine cl = getContentLine(mouse_coords);
if (cl != null)
cl.setMouseOver(true);
}
for (paint_i = contentLines.size() - 1; paint_i >= 0; paint_i--)
{
ContentLine cl = (ContentLine)contentLines.elementAt(paint_i);
if (lineRevY + cl.lineDY - 1 < minLineRevY)
lineRevY += cl.lineDY; // below (skip)
else
lineRevY = cl.paint(gfx_buff, lineRevY);
if (lineRevY > maxLineRevY)
break; // above (we are ready)
}
paint_i = 0;
if (!(scrolling && dyLines <= 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 mousePressed(MouseEvent ev)
{
}
public void mouseReleased(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)
{
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)
{
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)
{
// (at end of paint())
// last_action_scrolling = true;
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 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;
public Image smiley = null;
// 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++;
}
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);
}
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 boolean imageUpdate(Image img, int flags, int x, int y, int w, int h)
{
last_action_scrolling = false;
repaint();
return(true);
}
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 <= 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]);
}
public Image getSmiley() // may return null!
{
return(smiley);
}
} // ============ End of class Run ============
//=======================================================================
// Class ContentLine (a line of runs, is one or more lines on display)
//=======================================================================
private class ContentLine // === 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 String nick;
public boolean mouse_over;
public boolean line_selected;
public Date time_stamp;
// 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
time_stamp = new Date();
if (false)
{
SimpleDateFormat date_format = new SimpleDateFormat("[HH:mm:ss]");
s = date_format.format(time_stamp) + " " + s;
}
// runs.addElement(new Run(s,false,false,false,fgColorIndex,bgColorIndex));
// (simple version (without parsing))
text = s;
nick = n;
runs = new Vector();
int hrefend = -1;
int start = 0;
int len = s.length();
for (int i = 0; i < 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)
if (interpretUrl && isHref(s, i))
{
if (start < i)
runs.addElement(new Run(s.substring(start,i), boldCurrent, italicCurrent, underlineCurrent, fgColorIndexCurrent, bgColorIndexCurrent));
hrefend = findEndOfHref(s, i);
String urlstr = s.substring(i, hrefend);
Run run = new Run(urlstr, boldCurrent, italicCurrent, underlineCurrent, fgColorIndexCurrent, bgColorIndexCurrent);
// add protocol http:// to URL saved in run if missing so far
if (urlstr.length() >= 4)
if (urlstr.substring(0, 4).toUpperCase().equals("WWW."))
urlstr = "http://" + urlstr;
// if (urlstr.length() >= 8 && urlstr.indexOf("://") == -1)
// if (urlstr.indexOf("@") > 0)
// urlstr = "mailto:" + url