/*
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 >= 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;
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) < Math.abs(dy); yy += step)
{
if (yy != 0)
{
long time = System.currentTimeMillis() + 10;
while (System.currentTimeMillis() < time)
Thread.yield();
}
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;
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) < 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 <= 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 <= 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 < 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 < 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 < 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<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<len && j<=i+2; j++)
{ c=s.charAt(j);
if (!Character.isDigit(c)) break;
if (fgv<0) fgv=0;
fgv=fgv*10+Character.digit(c,10);
}
if (j<len && s.charAt(j)==',')
{ k=j;
for (j=k+1; j<len && j<=k+2; j++)
{ c=s.charAt(j);
if (!Character.isDigit(c)) break;
if (bgv<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<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 < 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 <= (buf_clip.y + buf_clip.height) && y >= (scr_clip.y - (lineDY * fmDY)) && y <= (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 ============
}