Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: com/splendid/awtchat/SmileyTextAreaArea.java


1   /*
2   Subcomponent (main part) of SmileyTextArea,
3     which is intended to be part of Eteria IRC Client from Javier Kohen
4   2001 written by Frank Bartels for Splendid Internet GmbH, Kiel, Germany
5   
6                           ===O=V=E=R=V=I=E=W===
7                             (over this file)
8   
9                          Constants & Variables
10                              Constructor
11                             Private Utils
12                             Update & Paint
13                              Mouse Events
14                       Component's User Interface
15       Class Run  (a part of a line within attributes not changing)
16    Class ContentLine  (a line of runs, is one or more lines on display)
17  */
18  
19  package com.splendid.awtchat;
20  
21  import java.awt.*;
22  import java.awt.event.*;
23  import java.text.SimpleDateFormat;
24  import java.util.*;
25  import ar.com.jkohen.irc.MircMessage;
26  import ar.com.jkohen.awt.NickInfoPopup;
27  
28  public class SmileyTextAreaArea extends Canvas implements MouseListener, MouseMotionListener, AdjustmentListener, FocusListener, ComponentListener
29  {
30  
31    //=======================================================================
32    //                       Constants & Variables
33    //=======================================================================
34  
35    private int bufferlen = 200; // number of lines saved by default
36                                // (virtual lines, i.e. lines independent of display line length)
37  
38    private int dimx = 300; // reflects actual component width (initalized in constructor)
39    private int dimy = 200; // reflects actual component height (initalized in constructor)
40    private int borderx = 4;
41    private int bordery = 3;
42    private int dimxv = 294; // view area
43    private int dimyv = 196;
44    int subsequentindent = 12; // indent for subsequent lines (inside one append with long string)
45    Vector tabs;
46  
47    int SMILEY_DEFAULT_SIZE = 14;
48    int INTER = 6;
49  
50    int paint_i = 0;
51    int mode = SmileyTextArea.FAST; // FAST is default
52    int oldSbValue = 0;
53    boolean SB_WORKAROUND_ENABLED = false;
54    Scrollbar sb = null;
55    int sbValue = 0;
56    int sbLength = 0;
57    int offset = 0;
58    
59    static int WAITCOUNT = 0; // this constant is calculated on construction
60    boolean inupdate = false;
61    boolean inappend = false;
62    boolean last_action_scrolling = false;
63    boolean canBreak = true;
64    boolean isSelectable = false;
65  
66    HyperlinkReceiver hyperlinkReceiver = null;
67    CopyText copyText = null;
68    Vector lines = null;
69    SmileyTextArea sta = null;
70    Dimension preferreddim; // reflects preferred size
71    NickInfoPopup nick_pop;
72    Point old_coords;
73    
74    // Color stuff
75    static Color [] fixedColors = new Color[16]; // Init. in static block.
76    private int urlColorIndex = 12;  // Standard url color
77  
78    private static final int bgColorIndex = 0; //16;  // background color index
79    private static final int fgColorIndex = 1; //17;  // foreground color index
80  
81    Color backgroundColor;
82    Color foregroundColor;
83    Color mouseoverColor = new Color(0xf0, 0xf0, 0xff);
84    Color selectedColor = new Color(0xc0, 0xc0, 0xff);
85    
86    // the default values are overwritten on construction
87    private Font usedFonts[] = {null, null, null, null};
88    private FontMetrics fmFonts[] = {null, null, null, null};
89    private int fmSpaceFonts = 1;
90    private int fmDescentFonts[] = {1, 1, 1, 1};
91    private int fmAscentFonts[] = {1, 1, 1, 1};
92    private int fmDY = 2;
93    private int fmDescent = 1;
94    private int fmAscent = 1;
95    private int fmLeading = 0;
96    
97    // current settings of Graphics
98    private int currentCombinedBoldItalic = 0;
99    private boolean currentUnderlined = false;
100   private int currentColorIndex = 0;
101 
102   private Vector contentLines; // instance variable for the list of ContentLines
103   
104   private Image img_buff;
105   private Graphics gfx_buff;
106 
107   //=======================================================================
108   //                            Constructor
109   //=======================================================================
110 
111 
112   public void adjustmentValueChanged(AdjustmentEvent e)
113   {
114     setRawSbValue(e.getValue());
115   }
116   
117   public void focusGained(FocusEvent e)    
118   {
119 //  System.out.println("focusGained");
120                       // if above container gets the focus...
121     last_action_scrolling = false;      // ...redisplay, because hidden areas...
122   repaint();                // ...trash the view partially.
123   }
124 
125   public void focusLost(FocusEvent e)
126   {
127   }
128   
129   public void componentResized(ComponentEvent e)
130   {
131     dimensionInit();
132   }
133   
134   public void componentHidden(ComponentEvent e)
135   {
136   }
137   
138   public void componentMoved(ComponentEvent e)
139   {
140   }
141   
142   public void componentShown(ComponentEvent e)
143   {
144   dimensionInit();
145     
146     last_action_scrolling = false;
147     repaint();
148   }
149 
150   public SmileyTextAreaArea(SmileyTextArea sta, Scrollbar sb, HyperlinkReceiver hr, CopyText cp, NickInfoPopup np)
151   {
152     super();
153     this.sta = sta;
154     this.sb = sb;
155     hyperlinkReceiver = hr;
156   copyText = cp;
157     this.nick_pop = np;
158     fmDY = 1;
159 
160     SB_WORKAROUND_ENABLED = ((new Scrollbar(Scrollbar.HORIZONTAL, 20, 10, 0, 20)).getValue() == 20);
161 
162     backgroundColor = fixedColors[bgColorIndex];
163     super.setBackground(backgroundColor); // for consistency and against flickering
164     foregroundColor = fixedColors[fgColorIndex];
165     super.setForeground(foregroundColor); // for consistency only
166 
167 //  prepareFont(getFont());  getFont()==null at this point of time
168     prepareFont(sta.DEFAULT_FONT.getName(), sta.DEFAULT_FONT.getSize());
169 
170     contentLines = new Vector();
171 
172     sbLength = 0;
173     sbValue = 0;
174     correctSb();
175     sb.addAdjustmentListener(this);
176 
177     addFocusListener(this);        // Does not work since this does not get the keyboard focus... see focus_frame
178     addComponentListener(this);
179     dimensionInit();          // Initialize dimension by same means
180     addMouseListener(this);
181     addMouseMotionListener(this);
182   }
183 
184   private Container focus_element = null;  // Sort of passive construction step...
185   
186   public void addNotify()
187   {
188     super.addNotify();
189   if (focus_element == null)
190     {
191     focus_element = getContainer();
192     if (focus_element != null)
193       focus_element.addFocusListener(this);
194   }
195   }
196   
197   public Container getContainer()
198   {
199     Container c = getParent();
200     do
201     {
202     if (c != sta && c instanceof java.awt.Container)
203       return ((java.awt.Container)c);
204         else
205       c = c.getParent();
206       
207     } while (c != null);
208     return null;
209   }
210 
211   //=======================================================================
212   //                            Private Utils
213   //=======================================================================
214 
215   private void correctSb()
216   {
217     int len = dimyv / fmDY;
218     int scrollmax = sbLength - len;
219   if (scrollmax < 0)
220     scrollmax = 0;
221     if (sbValue > scrollmax)
222     sbValue = scrollmax; // needed for dimensionInit
223     if (sbValue < 0)
224     sbValue = 0;
225     int value = scrollmax - sbValue;
226   if (value < 0)
227     value = 0;
228 //    sb.setValues(value * fmDY, len * fmDY, 0, (SB_WORKAROUND_ENABLED ? scrollmax : scrollmax + len) * fmDY);
229   sb.setValues(value, len, 0, (SB_WORKAROUND_ENABLED ? scrollmax : scrollmax + len));
230   }
231   
232   // scrolling called from outside
233   
234   private void setRawSbValue(int value)
235   {
236   // this synchonization is not very critical and it costs too much time:
237   // to be exact, it should be synchronized, because called from outside
238   
239 //  offset = fmDY - (value % fmDY);
240   offset = 0;
241 //  value = value / fmDY;
242   
243 //  synchronized(this)
244 //  {
245     int len = dimyv / fmDY;
246     int scrollmax = sbLength - len;
247     if (scrollmax < 0)
248       scrollmax = 0;
249     int sbValueOld = sbValue;
250     sbValue = scrollmax - value;
251     if (sbValue < 0)
252       sbValue = 0;
253 //  }
254     if (sbValueOld != sbValue)
255     {
256     // (at end of paint())
257 //    last_action_scrolling = true;
258     last_action_scrolling = false;
259     repaint();
260     }
261   }
262 
263   private void prepareFont(String name, int size)
264   {
265     usedFonts[0] = new Font(name, Font.PLAIN, size);
266     usedFonts[1] = new Font(name, Font.BOLD, size);
267     usedFonts[2] = new Font(name, Font.ITALIC, size);
268     usedFonts[3] = new Font(name, Font.BOLD | Font.ITALIC, size);
269     fmDY=0;
270     fmDescent=0;
271     fmAscent=0;
272     fmLeading=9999;
273     for (int i=0; i<4; i++)
274     {
275       FontMetrics fm=getFontMetrics(usedFonts[i]);
276       fmFonts[i]=fm;
277       fmSpaceFonts+=fm.stringWidth(" ");
278       fmDescentFonts[i]=fm.getMaxDescent();
279       fmAscentFonts[i]=fm.getMaxAscent();
280       //if (fmDY<fm.getHeight()) fmDY=fm.getHeight();                   // fmDY is max.(height)
281       if (fmDescent<fm.getMaxDescent()) fmDescent=fm.getMaxDescent(); // fmDescent is max.
282       if (fmAscent<fm.getMaxAscent()) fmAscent=fm.getMaxAscent();   // fmAscent is max.
283       if (fmLeading>fm.getLeading()) fmLeading=fm.getLeading();     // fmLeading is min.
284     }
285     if (fmLeading<0) fmLeading=0;
286     fmDY=fmDescent+fmAscent+fmLeading + INTER; // construction of abstract fm.getHeight() for all fonts
287     fmSpaceFonts=(fmSpaceFonts+2)/4; // +2 in order to round; now fmSpaceFonts is mean value
288     if (fmDY<fmDescent+fmAscent+fmLeading) // fmDY could be too small!
289       fmDY=fmDescent+fmAscent+fmLeading;
290     correctSb();
291   }
292 
293   private void dimensionInit()
294   {
295     synchronized(this) // must be synchronized, because called from outside
296     {
297     Dimension d = getSize();
298     dimx = d.width;
299     dimy = d.height;
300     dimxv = dimx - borderx * 2;
301     dimyv = dimy - bordery * 2;
302     
303     sbLength = 0;
304     for (int i = contentLines.size() - 1; i >= 0; i--)
305     {
306       ContentLine cl = (ContentLine)contentLines.elementAt(i);
307       cl.reconstruct();
308       sbLength += cl.lineDY;
309       }
310       if (sbValue > sbLength)
311       sbValue = sbLength;
312       correctSb();
313     }
314     last_action_scrolling = false;
315     invalidate();
316     repaint();
317   }
318 
319   private ContentLine getContentLine(Point p)
320   {
321     if (usedFonts[3] == null)
322       return(null);
323     
324   p = new Point(p.x, p.y - offset);
325     int y0 = dimy - bordery + sbValue * fmDY;
326     
327     if (p.y > dimy - bordery)
328       return(null);
329     if (p.y < bordery)
330       return(null);
331     
332     for (int i = contentLines.size() - 1; i >= 0; i--)
333     {
334       ContentLine cl = (ContentLine)contentLines.elementAt(i);
335     y0 -= cl.lineDY * fmDY;
336     if (p.y > y0)
337       return(cl);
338     }
339     return(null);
340   }
341   
342   private Run getRun(Point p) // searching the run is analogous paint
343   { if (usedFonts[3]==null) return(null);
344   
345   p = new Point(p.x, p.y - offset);
346   
347     int y0=dimy-bordery+sbValue*fmDY;
348     if (p.y>dimy-bordery) return(null);
349     if (p.y<bordery) return(null);
350     for (int i=contentLines.size()-1; i>=0; i--)
351     { ContentLine cl=(ContentLine)contentLines.elementAt(i);
352       y0-=cl.lineDY*fmDY;
353       if (p.y>y0)
354       { // cl found, now search the run
355         y0+=fmLeading;
356         int y=p.y-y0;
357         int dyline=fmAscent+fmDescent;
358         for (Enumeration e=cl.runs.elements(); e.hasMoreElements(); )
359         { Run run=(Run)e.nextElement();
360           if (run.pixY1==run.pixY0) // only one line, check X0 and X1
361           { if (p.x>run.pixX0 && p.x<run.pixX1) return(run);
362             else continue;
363           }
364           else if (y<run.pixY0+dyline) // first line, check X0
365           { if (p.x>run.pixX0) return(run);
366             else continue;
367           }
368           else if (y<run.pixY1) // between first and last line
369           { return(run);
370           }
371           else if (y<run.pixY1) // last line, check X1
372           { if (p.x<run.pixX1) return(run);
373             else continue;
374           }
375         }
376         break; // return null
377       }
378     }
379     return(null);
380   }
381 
382   private final void deleteFirstLine()
383   {
384     if (contentLines.isEmpty())
385     return;
386     sbLength -= ((ContentLine)(contentLines.firstElement())).lineDY;
387     contentLines.removeElementAt(0);
388     if (paint_i > 0)
389     paint_i--;        // undo the effect of removeElementAt for paint if paint runs
390     if (sbValue > sbLength)
391     sbValue = sbLength;   // not really nescessary...
392     correctSb();
393   }
394   //=======================================================================
395   //                           Update & Paint
396   //=======================================================================
397 
398     private void createImageBuffer(int width, int height)
399   {
400     if (width < 1)
401       width = 1;
402     if (height < 1)
403       height = 1;
404       
405     this.img_buff = createImage(width, height);
406     if (gfx_buff != null)
407       gfx_buff.dispose();
408 
409     if (img_buff != null)
410       this.gfx_buff = img_buff.getGraphics();
411     else
412       this.gfx_buff = getGraphics();
413     }
414   
415   public void update(Graphics g)
416   {
417     inupdate = true;
418     paint(g);  // everything in paint, no clear, but no need to call from here (no effect)
419     inupdate = false;
420   }
421 
422 public synchronized void paint(Graphics g)
423 {
424       
425   if (gfx_buff == null || img_buff.getWidth(this) != dimx || img_buff.getHeight(this) != dimy)
426   {
427     createImageBuffer(dimx, dimy);
428     paintBorder(g);
429   }
430       
431     if (usedFonts[3] == null)
432   {
433     Font f = getFont();
434     if (f == null)
435       return; // must have a font...
436     prepareFont(f.getName(), f.getSize());
437   }
438 
439 
440     int dyLines = sbValue - oldSbValue;
441     int dy = dyLines * fmDY;
442     oldSbValue = sbValue;
443     boolean scrolling = (Math.abs(dy) < (dimy * 2) / 3);
444     
445     if (!inupdate && !inappend)
446       scrolling = false;   // scrolling only in update() or append()
447       
448     if (!last_action_scrolling)
449       scrolling = false;   // scrolling only if scrolling or append
450       
451     if (mode == SmileyTextArea.SAFE)
452       scrolling = false;  // scrolling is possibly not safe
453       
454     if (!isShowing())
455       scrolling = false;   // may be overlapped by another window
456 
457 
458 //scrolling = false;
459 
460 
461 
462   if (scrolling && dy != 0)  
463     {
464       if (mode == SmileyTextArea.SMOOTH)
465       {
466         int step = (dy / fmDY);  // so far exact divison (rest == 0)
467         for (int yy = 0; Math.abs(yy) < Math.abs(dy); yy += step)
468           {
469         if (yy != 0)
470         {
471           try
472           {
473             Thread.sleep(5);
474           } catch (InterruptedException e) {}
475         }
476 
477         if (dy < 0)
478             gfx_buff.copyArea(2, -step + 2 + (yy - dy + step), dimx - 4, dimy - 4 + step + (dy - step), 0, step); // up
479         else
480             gfx_buff.copyArea(2, 2 + yy, dimx - 4, dimy - 4 - step - (dy - step), 0, step); // down
481         
482         //area a littlebit too large (OK, but funny optics):
483 //        if (dy < 0)
484 //          g.copyArea(2, -step + 2, dimx - 4, dimy - 4 + step, 0, step); // up
485 //        else
486 //          g.copyArea(2, 2, dimx - 4, dimy - 4 - step, 0, step); // down
487           
488         
489         g.drawImage(img_buff, 0, 0, backgroundColor, this);
490       }
491     }
492     else
493     {
494       if (dy < 0)
495         gfx_buff.copyArea(2, -dy + 2, dimx - 4, dimy - 4 + dy, 0, dy); // up
496       else
497         gfx_buff.copyArea(2, 2, dimx - 4, dimy - 4 - dy, 0, dy); // down
498         
499       g.drawImage(img_buff, 0, 0, backgroundColor, this);
500     }
501     }
502 
503     if (bordery > 2)
504     {
505       gfx_buff.setColor(backgroundColor);
506     gfx_buff.fillRect(2, dimy - bordery, dimx - 4, bordery - 2);
507     }
508   
509     int lineRevY = -sbValue;
510     int maxLineRevY = (dimy - 4) / fmDY; // top
511     int minLineRevY = 0; // bottom
512 
513     if (scrolling && dyLines < 0)
514       maxLineRevY = -dyLines - 1;
515     
516     if (scrolling && dyLines >= 0)
517       minLineRevY = (dimy - 2 * bordery - 2 - dy) / fmDY;
518     
519   for (paint_i = contentLines.size() - 1; paint_i >= 0; paint_i--)
520   {
521       ContentLine cl = (ContentLine)contentLines.elementAt(paint_i);
522 
523     if (lineRevY + cl.lineDY - 1 < minLineRevY)
524       lineRevY += cl.lineDY; // below (skip)
525     else
526       lineRevY = cl.paint(gfx_buff, lineRevY);
527       
528     cl.setMouseOver(false);
529 
530     if (lineRevY > maxLineRevY)
531       break; // above (we are ready)
532   }
533   
534   paint_i = 0;
535     if (!(scrolling && dyLines <= 0)) // avoid clearing the top if scrolling up
536     {
537       int topdrawn = dimy - bordery - (lineRevY) * fmDY;
538     if (topdrawn > 2) // clear rest above textlines, if  there is a rest
539     {
540       gfx_buff.setColor(backgroundColor);
541       gfx_buff.fillRect(2, 2, dimx - 4, topdrawn);
542       }
543     }
544     
545     
546   nick_pop.paint(gfx_buff);
547   
548   if (isShowing())
549     g.drawImage(img_buff, 0, 0, backgroundColor, this);
550 
551   last_action_scrolling = true;
552   nick_pop.setVisible(false);
553 
554 }
555 
556   private void paintBorder(Graphics g)
557   {
558       gfx_buff.setColor(Color.gray);
559         int tempx1[] = {0, 0, dimx - 1};
560         int tempy1[] = {dimy - 1, 0, 0};
561         gfx_buff.drawPolyline(tempx1, tempy1, 3);
562         
563         gfx_buff.setColor(Color.black);
564         int tempx2[] = {1, 1, dimx - 2};
565         int tempy2[] = {dimy - 2, 1, 1};
566         gfx_buff.drawPolyline(tempx2, tempy2, 3);
567         
568         gfx_buff.setColor(Color.white);
569         int tempx3[] = {0, dimx - 1, dimx - 1};
570         int tempy3[] = {dimy - 1, dimy - 1, 0};
571         gfx_buff.drawPolyline(tempx3, tempy3, 3);
572       
573       gfx_buff.setColor(Color.lightGray);
574         int tempx4[] = {1, dimx - 2, dimx - 2};
575         int tempy4[] = {dimy - 2, dimy - 2, 1};
576         gfx_buff.drawPolyline(tempx4, tempy4, 3);
577     
578     gfx_buff.setClip(2, 2, dimx - 4, dimy - 4);
579   }
580   
581   //=======================================================================
582   //                            Mouse Events
583   //=======================================================================
584 
585   public void mousePressed(MouseEvent ev)
586   {
587   }
588   
589   public void mouseReleased(MouseEvent ev)
590   {
591       // This is the left click to select a line of text
592     
593     if ((ev.getModifiers() & MouseEvent.BUTTON3_MASK) == 0 && isSelectable)
594     {
595       Point mouse_coords = ev.getPoint();
596       ContentLine clicked_cl = getContentLine(mouse_coords);
597       
598       for (int i = 0; i < contentLines.size(); i++)
599         {
600           ContentLine cl = (ContentLine)contentLines.elementAt(i);
601         if (clicked_cl == cl)
602           cl.setSelected(!cl.isSelected());
603         else
604           cl.setSelected(false);
605         }
606       
607       last_action_scrolling = false;
608       repaint();
609     }
610 
611     // This is the right click to get a cut-paste window
612         
613     if (ev.isPopupTrigger() || (ev.getModifiers() & MouseEvent.BUTTON3_MASK) != 0)
614     {
615       String s = "";
616       for (int i = 0; i < contentLines.size(); i++)
617         {
618           ContentLine cl = (ContentLine)contentLines.elementAt(i);
619         s = s + cl.text + "\n";
620         }
621       if (copyText != null)
622         copyText.addText(s);
623     }
624   }
625   
626   public void mouseEntered(MouseEvent ev)
627   {
628   }
629   
630   public void mouseExited(MouseEvent ev)
631   {
632   for (int i = 0; i < contentLines.size(); i++)
633     {
634        ContentLine cl = (ContentLine)contentLines.elementAt(i);
635     cl.setMouseOver(false);
636   }
637   nick_pop.setVisible(false);
638 
639   last_action_scrolling = false;
640   repaint();
641 
642   }
643 
644   public void mouseClicked(MouseEvent ev)
645   {
646     Run run = getRun(ev.getPoint());
647   if (run != null)
648   {
649     if (!run.getUrl().equals("") && hyperlinkReceiver != null)
650       hyperlinkReceiver.handleHyperlink(run.url);
651       
652     if (!run.getNick().equals("") && hyperlinkReceiver != null)
653       hyperlinkReceiver.handleNick(run.nick);
654   }
655   }
656 
657   public void mouseMoved(MouseEvent ev)
658   {
659 
660   for (int i = 0; i < contentLines.size(); i++)
661     {
662        ContentLine cl = (ContentLine)contentLines.elementAt(i);
663     cl.setMouseOver(false);
664   }
665       
666 //  if (ev.getSource().equals(this))
667 //  {
668      Point mouse_coords = ev.getPoint();
669     old_coords = mouse_coords;
670     ContentLine cl = getContentLine(mouse_coords);
671       boolean repaint = true;
672     String n = null;
673     if (cl != null)
674     {
675       n = cl.nick;
676       cl.setMouseOver(true);
677     }
678       
679       String old_n = nick_pop.getNick();
680 
681       if (n == null)
682       {
683           if (old_n != null)
684           nick_pop.setVisible(false);
685           else
686           repaint = false;
687       }
688       else
689     {
690       if (!n.equals(old_n))
691         {
692             nick_pop.setNick(n);
693         nick_pop.setVisible(true);
694         }
695     }
696 
697       if(true)
698       {
699         nick_pop.setLocation(new Point(mouse_coords.x + 32, mouse_coords.y + nick_pop.getSize().height));
700       last_action_scrolling = false;
701       repaint();
702       }
703 //  }
704 
705     Run run = getRun(mouse_coords);
706     if (run != null)
707     {
708     if (!(run.getUrl().equals("") && run.getNick().equals("")) && hyperlinkReceiver != null)
709     {
710       setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
711           return;
712     }
713     }
714     setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
715   }
716 
717   public void mouseDragged(MouseEvent ev)
718   {
719   }
720 
721   //=======================================================================
722   //                        Component's User Interface
723   //=======================================================================
724 
725   public void append(String s, boolean interpretUrl)
726   {
727     addContent(s, interpretUrl, null);
728   }
729 
730   public void append(String s, boolean interpretUrl, String n)
731   {
732     addContent(s, interpretUrl, n);
733   }
734   
735   public void clean()
736   {
737       contentLines.removeAllElements();
738     if (gfx_buff != null)
739     {
740       gfx_buff.dispose();
741       gfx_buff = null;
742     }
743     dimensionInit();
744   }
745   
746   public void addContent(String s, boolean interpretUrl, String n)
747   {
748   synchronized(this)
749   {
750       ContentLine cl = new ContentLine(s, interpretUrl, n);
751       cl.reconstruct();
752     contentLines.addElement(cl);
753     
754     sbLength += cl.lineDY;
755 //    sbValue = 0;
756     if (oldSbValue == 0)
757       oldSbValue += cl.lineDY;
758     else
759       oldSbValue = -999;
760     correctSb();
761     
762     if (bufferlen >= 0 && contentLines.size() > bufferlen)
763       deleteFirstLine();
764   }
765   
766   if (sbValue == 0)
767   {
768     // (at end of paint())
769 //    last_action_scrolling = true;
770     repaint();
771   }
772 
773   }
774   
775   public void setMode(int mode)
776   {
777     synchronized(this)
778     {
779       this.mode = mode;
780     last_action_scrolling = false;
781     }
782     repaint();
783   }
784   
785   public void setBreaks(boolean canBreak)
786   {
787     this.canBreak = canBreak;
788   }
789 
790   public void addTab(int t)
791   {
792     if (tabs == null)
793       tabs = new Vector();
794     tabs.addElement(new Integer(t));
795   }
796   
797   public void setSubsequentIndent(int indent)
798   {
799     synchronized(this)
800     {
801       subsequentindent = indent;
802     last_action_scrolling = false;
803     }
804     dimensionInit();
805     // repaint(); (included in dimensionInit())
806   }
807   
808   public int getSubsequentIndent()
809   {
810     return(subsequentindent);
811   }
812 
813   public void setBufferlen(int bufferlen)
814   {
815     synchronized(this)
816     {
817     this.bufferlen = bufferlen;
818     while (bufferlen >= 0 && contentLines.size() > bufferlen)
819       deleteFirstLine();
820     }
821   }
822   
823   public int getBufferlen()
824   {
825     return(bufferlen);
826   }
827 
828   public void setBackground(Color c)
829   {
830     synchronized(this)
831     {
832       fixedColors[bgColorIndex] = c;
833     backgroundColor = c;
834     super.setBackground(backgroundColor); // for consistency and against flickering
835     last_action_scrolling = false;
836     }
837     repaint();
838   }
839   
840   public Color getBackground()
841   { return(fixedColors[bgColorIndex]);
842   }
843   
844   public void setForeground(Color c)
845   { synchronized(this)
846     { fixedColors[fgColorIndex]=c;
847       foregroundColor=c;
848       super.setForeground(foregroundColor); // for consistency only
849       last_action_scrolling=false;
850     }
851     repaint();
852   }
853   
854   public Color getForeground()
855   {
856   return(fixedColors[fgColorIndex]);
857   }
858 
859   public static void setColorPalette(Color palette[])
860   {
861     fixedColors = palette;
862   }
863   
864   public void setColorPaletteEntry(int n, Color c)
865   {
866     synchronized(this)
867     {
868       fixedColors[(n % 16)] = c;
869       last_action_scrolling = false;
870     }
871     repaint();
872   }
873   
874   public Color getColorPaletteEntry(int n)
875   {
876     return(fixedColors[(n % 16)]);
877   }
878 
879   public void changeFont(String name, int size)
880   {
881     synchronized(this)
882     {
883       prepareFont(name, size);
884       last_action_scrolling = false;
885     }
886     dimensionInit();
887     // repaint(); (included in dimensionInit())
888   }
889   
890   public Font getFont()
891   {
892   return(usedFonts[0]);
893   }
894 
895   //=======================================================================
896   //     Class Run  (a part of a line within attributes not changing)
897   //=======================================================================
898 
899   // === class Run represents a text fragment including attributes ===
900   
901   private class Run
902   { public String text = null;
903     public String url = null;
904     public String nick = null;
905     public boolean display = true;
906     public int pixX0 = 0; // Start(0)
907     public int pixY0 = 0;
908     public int pixX1 = 0; // End(1)
909     public int pixY1 = 0;
910     public int nbreaks = 0;
911     public Vector breaks = null; // if the Run splits at lineends the text indices are saved here
912     public int combinedBoldItalic = 0;
913     public boolean underlined = false;
914     public int fgColorIndexMine = fgColorIndex;
915     public int bgColorIndexMine = bgColorIndex;
916     public Image smiley = null;
917 
918   // Constructor (text)
919   
920     public Run(String atext, boolean bold, boolean italic, boolean underlined, int fgColorIndex, int bgColorIndex)
921     {
922     char c;
923     int len = atext.length();
924     text = "";
925     for (int i = 0; i < len; i++)
926     {
927       c = atext.charAt(i);
928       if (Character.isISOControl(c))
929         continue; // skip control characters
930       if (!Character.isDefined(c))
931         continue; // skip undefined characters
932       text += c;
933     }
934         this.combinedBoldItalic = bold ? (italic ? 3 : 1) : (italic ? 2 : 0);
935         this.underlined = underlined;
936         this.fgColorIndexMine = fgColorIndex;
937         this.bgColorIndexMine = bgColorIndex;
938     }
939     
940   // Constructor (smiley)
941   
942     public Run(String text, Image smiley, int bgColorIndex)
943     {
944       this.text = text;
945       this.smiley = smiley;
946       this.bgColorIndexMine = bgColorIndex;
947     }
948     
949   // if URL, you need this second construction step
950   
951     public void setUrl(String url)
952     {
953       this.url = url;
954       // preserve bold and italic and bgColorIndexMine
955       underlined = true;
956       fgColorIndexMine = urlColorIndex;
957     }
958 
959     public void setNick(String nick)
960     {
961       this.nick = nick;
962     }
963     
964     public void setDisplay(boolean b)
965     {
966       this.display = b;
967     }
968     
969     public boolean display()
970     {
971       return(display);
972     }
973     
974     private int mySpaceIndex(String s, int i)
975     {
976       int len = s.length();
977         for (int j = i; j < len; j++)
978           if (Character.isSpaceChar(s.charAt(j)))
979             return(j);
980           
981         return(-1);
982     }
983 
984     public int setXY(int newPixX, int newPixY, int line) // prepare the Run for display
985     {
986       String s = text;
987     int loopcount, lasti, i, x, snipped = 0;
988     
989         if (newPixX >= dimxv - fmSpaceFonts * 2) // break Run, needed for speed and smileys
990         {
991           newPixX = borderx + subsequentindent;
992           newPixY += fmDY;
993           line++;
994         }
995         
996         breaks = null;
997         nbreaks = 0;
998       pixX0 = newPixX;
999         pixY0 = newPixY;
1000        if (smiley != null)
1001        {
1002          pixX1 = newPixX + smiley.getWidth(null);
1003          pixY1 = newPixY;
1004          if (pixX1 > dimx - borderx)
1005          {
1006            pixX0 = borderx + subsequentindent;
1007              pixY0 += fmDY;
1008              pixX1 = borderx + subsequentindent + smiley.getWidth(null);
1009              pixY1 += fmDY;
1010              line++;
1011          }
1012          return(line); // smiley case ready
1013        }
1014      
1015      pixX1 = newPixX+fmFonts[combinedBoldItalic].stringWidth(s);
1016      pixY1 = newPixY;
1017      
1018      if (smiley != null)
1019        return(line); // do not break inside a smiley
1020        
1021      loopcount = 0;
1022      while (pixX1 > dimx - borderx) // then must break line
1023      {
1024    if (loopcount++ > 100)
1025      break; // avoid deadlocks (although there are none ;-)
1026      
1027        for (lasti = 0, i = 0; (i = mySpaceIndex(s, i)) >= 0; )
1028        {
1029      x = newPixX + fmFonts[combinedBoldItalic].stringWidth(s.substring(0, i));
1030      if ( x > dimx - borderx)
1031        break; // found proper break (the last one on actual line)
1032      i++; // pos of next char after space
1033//      while (i < s.length() && Character.isSpaceChar(s.charAt(i)))
1034//        i++; // hop over spaces
1035          lasti = i;
1036        }
1037        if (lasti == 0) // break complete s
1038        {
1039      if (newPixX <= borderx + subsequentindent) // already gone to the next line?
1040      {
1041        // the (spaceless) s does not fit on a line! break by force somewhere
1042        for (lasti = 1, i = 1; i < s.length() - 1; i++)
1043        {
1044          x = newPixX + fmFonts[combinedBoldItalic].stringWidth(s.substring(0, i + 1));
1045          if (x > dimx - borderx)
1046          {
1047            lasti = i;
1048            break;
1049          }
1050        }
1051      }
1052      else
1053      {
1054        newPixX = borderx + subsequentindent;
1055        if (pixY0 == pixY1 && breaks == null) // if break at beginning of run...
1056        {
1057          pixX0 = borderx + subsequentindent; // ...the beginning also must go on next line
1058          pixY0 += fmDY;
1059        }
1060        pixX1 = newPixX + fmFonts[combinedBoldItalic].stringWidth(s);
1061        pixY1 += fmDY;
1062        line++;
1063        continue; // retry with linebreak (almost nothing to do for this) before
1064      }
1065        }
1066        
1067        if (lasti > 0 && lasti < s.length()) // break line (can not use else!)
1068        {
1069          if (!canBreak)
1070            return(line);
1071            
1072          if (breaks == null)
1073            breaks = new Vector();
1074            breaks.addElement(new Integer(lasti + snipped));
1075      nbreaks++;
1076      s = s.substring(lasti);
1077      snipped += lasti;
1078        }
1079        
1080        newPixX = borderx + subsequentindent;
1081        pixX1 = newPixX + fmFonts[combinedBoldItalic].stringWidth(s);
1082        pixY1 += fmDY;
1083        line++;
1084      }
1085      
1086      return(line);
1087    }
1088
1089    private void print(Graphics g, String s, int x, int y)
1090    {
1091      if (smiley != null)
1092      {
1093          int dy=smiley.getHeight(null); if (dy==0) dy=SMILEY_DEFAULT_SIZE;
1094          int ascent=fmAscentFonts[combinedBoldItalic];
1095          int descent=fmDescentFonts[combinedBoldItalic];
1096          int yoff=-(ascent-descent+dy)/2; // put in center between ascent and descent
1097          if (yoff+dy-1>descent) yoff=descent-dy+1;  // but not below descent
1098
1099          if (bgColorIndexMine!=bgColorIndex) // Not the normal background color->paint bg first
1100          {
1101            int len=smiley.getWidth(null); if (len==0) len=SMILEY_DEFAULT_SIZE;
1102            g.setColor(fixedColors[bgColorIndexMine]);
1103             g.fillRect(x, y-ascent, len, ascent + descent);
1104                                  // +1 because rectangle goes only to n-1 on bottom
1105                                  // -1 because from -ascent to descent (incl) is 1 too much
1106              g.setColor(fixedColors[fgColorIndexMine]);
1107          }
1108
1109          g.drawImage(smiley, x, y + yoff, null);
1110        }
1111        else
1112        {
1113            if (bgColorIndexMine!=bgColorIndex) // Not the normal background color->paint bg first
1114            {
1115              int len=fmFonts[combinedBoldItalic].stringWidth(s);
1116              int ascent=fmAscentFonts[combinedBoldItalic];
1117              g.setColor(fixedColors[bgColorIndexMine]);
1118              g.fillRect(x, y-ascent, len, ascent + fmDescentFonts[combinedBoldItalic]);
1119                                  // +1 because rectangle goes only to n-1 on bottom
1120                                  // -1 because from -ascent to descent (incl) is 1 too much
1121                g.setColor(fixedColors[fgColorIndexMine]);
1122          }
1123
1124          g.drawString(s, x, y);
1125
1126        if (underlined) // underlined->add the line
1127        {
1128          int len=fmFonts[combinedBoldItalic].stringWidth(s);
1129          int below=fmDescentFonts[combinedBoldItalic]/4;
1130            if (below<1) below=1; // otherwise the underline line sticks to the font
1131            // if (getItalic())
1132            //   would like to subtract something from len, but I don't know how much
1133            g.drawLine(x,y+below,x+len-1,y+below); // -1 makes it comaptible with fillRect len
1134            if (getBold())
1135              g.drawLine(x,y+below+1,x+len-1,y+below+1);
1136        }
1137      }
1138    }
1139    
1140    public void paint(Graphics g, int y0, boolean force)
1141    {
1142    int x = pixX0;
1143    int y = y0 + pixY0;
1144    setMyGraphicsAttributes(g, force);
1145    if (nbreaks == 0) // makes simple case fast
1146    {
1147      print(g, text, x, y);
1148    }
1149    else
1150    {
1151      int oldj = 0;
1152      int j = 0;
1153      for (int i = 0; i <= nbreaks; i++)
1154      {
1155        if (i == nbreaks)
1156          j = text.length();
1157        else
1158          j = ((Integer)(breaks.elementAt(i))).intValue();
1159        print(g, text.substring(oldj, j), x, y);
1160        x = borderx + subsequentindent;
1161        y += fmDY;
1162        oldj = j;
1163          }
1164        }
1165  }
1166
1167    public void setMyGraphicsAttributes(Graphics g, boolean force)
1168    {
1169      if (currentCombinedBoldItalic != combinedBoldItalic || force)
1170      {
1171        g.setFont(usedFonts[combinedBoldItalic]);
1172          currentCombinedBoldItalic = combinedBoldItalic;
1173        }
1174        if (currentColorIndex != fgColorIndexMine || force)
1175        {
1176          g.setColor(fixedColors[fgColorIndexMine]);
1177          currentColorIndex = fgColorIndexMine;
1178        }
1179        // currentUnderlined != underlined does not matter for g
1180    }
1181
1182    public String getUrl()
1183    {
1184    return(url == null ? "" : url);
1185    }
1186    
1187    public String getNick()
1188    {
1189    return(nick == null ? "" : nick);
1190    }
1191  
1192    public boolean getBold()
1193    {
1194    return((combinedBoldItalic & 1) == 1);
1195    }
1196    public boolean getItalic()
1197    {
1198    return((combinedBoldItalic & 2) == 2);
1199    }
1200  
1201    public boolean getUnderlined()
1202    {
1203    return(underlined);
1204    }
1205    public Color getColor()
1206    {
1207    return(fixedColors[fgColorIndexMine]);
1208    }
1209    public Font getFont()
1210    {
1211    return(usedFonts[combinedBoldItalic]);
1212    }
1213    public Image getSmiley() // may return null!
1214    {
1215    return(smiley);
1216    }
1217  } // ============ End of class Run ============
1218
1219  //=======================================================================
1220  //  Class ContentLine  (a line of runs, is one or more lines on display)
1221  //=======================================================================
1222
1223  private class ContentLine // === class ContentLine represents an abstract line of text ===
1224  { public String text;
1225    public Vector runs;
1226    public int lineStart;
1227    public int lineEnd;
1228    public int lineDY=1;
1229     public String nick;
1230  public boolean mouse_over;
1231  public boolean line_selected;
1232  public Date time_stamp;
1233  
1234  // Constructor (parses string and produces runs)
1235
1236    public ContentLine(String s, boolean interpretUrl, String n)
1237    {
1238      boolean boldCurrent = false;
1239    boolean italicCurrent = false;
1240    boolean underlineCurrent = false;
1241    int bgColorIndexCurrent = bgColorIndex;
1242    int fgColorIndexCurrent = fgColorIndex;
1243
1244    if (s.length() == 0)
1245      return; // empty line, no runs
1246      
1247    time_stamp = new Date();
1248    if (false)
1249    {
1250      SimpleDateFormat date_format = new SimpleDateFormat("[HH:mm:ss]");
1251      s = date_format.format(time_stamp) + " " + s;
1252    }
1253      
1254    // runs.addElement(new Run(s,false,false,false,fgColorIndex,bgColorIndex));
1255    //                        (simple version (without parsing))
1256    text = s;
1257    nick = n;
1258    runs = new Vector();
1259
1260      
1261      int hrefend = -1;
1262      int start = 0;
1263      int len = s.length();
1264      for (int i = 0; i < len; i++)
1265      {
1266      char ch = s.charAt(i);
1267      
1268        // find potential href's end and check for email
1269/*
1270        if (i > hrefend)
1271        {
1272      hrefend = findEndOfHref(s, i);
1273      if (interpretUrl && hrefend < len && i < hrefend)
1274      {
1275        // is email address ?
1276      
1277               if(s.charAt(hrefend) == '@')
1278               {
1279                   if (start < i)
1280                    runs.addElement(new Run(s.substring(start, i), boldCurrent, italicCurrent, underlineCurrent, fgColorIndexCurrent, bgColorIndexCurrent));
1281                    
1282             int emailend = findEndOfHref(s, hrefend + 1);
1283             String emailstr = s.substring(i, emailend);
1284             Run run = new Run(emailstr, boldCurrent, italicCurrent, underlineCurrent, fgColorIndexCurrent, bgColorIndexCurrent);
1285           
1286                   // add protocol mailto: to URL saved in run if missing so far
1287                   
1288                   if (emailstr.length() >= 7)
1289                   {
1290            emailstr = "mailto:" + emailstr;
1291                   }
1292                   else
1293                   {
1294                       if (!emailstr.substring(0, 7).toUpperCase().equals("MAILTO:"))
1295                         emailstr = "mailto:" + emailstr;
1296                    }
1297                   run.setUrl(emailstr);
1298                   runs.addElement(run);
1299                   i = emailend - 1;
1300                   start = i + 1;
1301                   continue;
1302               }
1303            }
1304        }
1305*/
1306        // check for href (including #channel)
1307        if (interpretUrl && isHref(s, i))
1308        {
1309      if (start < i)
1310        runs.addElement(new Run(s.substring(start,i), boldCurrent, italicCurrent, underlineCurrent, fgColorIndexCurrent, bgColorIndexCurrent));
1311      
1312      hrefend = findEndOfHref(s, i);
1313            String urlstr = s.substring(i, hrefend);
1314          Run run = new Run(urlstr, boldCurrent, italicCurrent, underlineCurrent, fgColorIndexCurrent, bgColorIndexCurrent);
1315          
1316            // add protocol http:// to URL saved in run if missing so far
1317          
1318          if (urlstr.length() >= 4)
1319              if (urlstr.substring(0, 4).toUpperCase().equals("WWW."))
1320                urlstr = "http://" + urlstr;
1321                
1322//      if (urlstr.length() >= 8 && urlstr.indexOf("://") == -1)
1323//        if (urlstr.indexOf("@") > 0)
1324//          urlstr = "mailto:" + urlstr;
1325                
1326            run.setUrl(urlstr);
1327            runs.addElement(run);
1328            i = hrefend - 1;
1329            start = i + 1;
1330            continue;  
1331        }
1332
1333        // check for smileys
1334        
1335    if (sta.smileys != null)
1336    {
1337      int imax = i + sta.maxCodeLength;
1338      if (imax > len)
1339        imax = len;
1340      String smax = s.substring(i, imax);
1341      Image image;
1342      while (smax.length() > 0)
1343      {
1344        if ((image = (Image)sta.smileys.get(smax)) != null)    // found (longest) smiley
1345        {
1346          if (start < i)
1347            runs.addElement(new Run(s.substring(start, i), boldCurrent, italicCurrent, underlineCurrent, fgColorIndexCurrent, bgColorIndexCurrent));
1348          runs.addElement(new Run(smax, image, bgColorIndexCurrent));
1349          i += smax.length() - 1;
1350          start = i + 1;
1351          break;
1352        }
1353        smax = smax.substring(0, smax.length() - 1);
1354      }
1355    }
1356        
1357        // check for control codes
1358        
1359        switch(ch)
1360        { case MircMessage.BELL:
1361            Toolkit.getDefaultToolkit().beep();
1362            break;
1363          case MircMessage.BOLD:
1364          case MircMessage.ITALIC:
1365          case MircMessage.UNDERLINE:
1366          case MircMessage.COLOR:
1367          case MircMessage.REVERSE:
1368          case MircMessage.RESET:        // is control code
1369            if (start<i)
1370              runs.addElement(new Run(s.substring(start,i), boldCurrent,italicCurrent,underlineCurrent, fgColorIndexCurrent,bgColorIndexCurrent));
1371            start=i+1;
1372            switch(ch)
1373            { case MircMessage.BOLD:
1374                boldCurrent=!boldCurrent;
1375                break;
1376              case MircMessage.ITALIC:
1377                italicCurrent=!italicCurrent;
1378                break;
1379              case MircMessage.UNDERLINE:
1380                underlineCurrent=!underlineCurrent;
1381                break;
1382              case MircMessage.COLOR:
1383                // parse color(s) 0,0 ... 15,15 (... 99,99 mapped modulo 16)
1384                { int j,k;
1385                  char c;
1386                  int fgv=-1;
1387                  int bgv=-1;
1388                  for (j=i+1; j<len && j<=i+2; j++)
1389                  { c=s.charAt(j);
1390                    if (!Character.isDigit(c)) break;
1391                    if (fgv<0) fgv=0;
1392                    fgv=fgv*10+Character.digit(c,10);
1393                  }
1394                  if (j<len && s.charAt(j)==',')
1395                  { k=j;
1396                    for (j=k+1; j<len && j<=k+2; j++)
1397                    { c=s.charAt(j);
1398                      if (!Character.isDigit(c)) break;
1399                      if (bgv<0) bgv=0;
1400                      bgv=bgv*10+Character.digit(c,10);
1401                    }
1402                  }
1403                  if (fgv>=0) fgColorIndexCurrent=fgv % 16;
1404                  if (bgv>=0) bgColorIndexCurrent=bgv % 16;
1405                  if (-1==fgv && -1==bgv)
1406                  { fgColorIndexCurrent=fgColorIndex;
1407                    bgColorIndexCurrent=bgColorIndex;
1408                  }
1409                  i=j-1;
1410                  start=i+1;
1411                }
1412                break;
1413              case MircMessage.REVERSE:
1414                int saveci=bgColorIndexCurrent;
1415                bgColorIndexCurrent=fgColorIndexCurrent;
1416                fgColorIndexCurrent=saveci;
1417                break;
1418              case MircMessage.RESET:
1419                boldCurrent=false;
1420                italicCurrent=false;
1421                underlineCurrent=false;
1422                bgColorIndexCurrent=bgColorIndex;
1423                fgColorIndexCurrent=fgColorIndex;
1424                break;
1425            }
1426            break;
1427        }
1428      } // end of for (int i=0; i<len; i++)
1429      
1430      if (start<len) // take the rest
1431        runs.addElement(new Run(s.substring(start,len),boldCurrent,italicCurrent,underlineCurrent,fgColorIndexCurrent,bgColorIndexCurrent));
1432        
1433      if (nick != null)
1434      {
1435          Run run = (Run)runs.elementAt(0);
1436          run.setNick(nick);
1437          runs.setElementAt(run, 0);
1438      }
1439      
1440    }
1441
1442    public int findEndOfHref(String s, int start)
1443    {
1444      int len = s.length();
1445      
1446    for (int i = start; i < len; i++)
1447    {
1448      char c = s.charAt(i);
1449      if (Character.isLetterOrDigit(c))
1450        continue;
1451        
1452          switch (c)
1453          {
1454      default:
1455              continue;
1456            case ' ':
1457              return i;
1458      }
1459        }
1460        
1461        return len;
1462    }
1463
1464    public boolean isHref(String s, int i)
1465    {
1466    if (i < 0 || i >= s.length())
1467      return(false);
1468      
1469      char c = s.charAt(i);
1470      
1471    if (c == '#')
1472      return(true);
1473      
1474    if (c != 'h' && c != 'H' && c != 'w' && c != 'W' && c != 'f' && c != 'F')
1475      return(false);
1476      
1477    if (s.length() < i + 8)
1478      return(false); // 8 chars at least (www.x.yy http://x)
1479      
1480    if (s.substring(i, i + 4).toUpperCase().equals("WWW."))
1481      return(true);
1482      
1483    if (s.substring(i, i + 7).toUpperCase().equals("HTTP://"))
1484      return(true);
1485      
1486    if (s.substring(i, i + 6).toUpperCase().equals("FTP://"))
1487      return(true);
1488      
1489//    if (s.substring(i).indexOf("@") >= 0)
1490//      return(true);
1491      
1492    return(false);
1493    }
1494
1495    public void reconstruct() // reconstruct ContentLine (prepare for display)
1496    {
1497    int x = borderx;
1498    int y = -1; // inside a line think from top left as (0, 0)
1499    int line = 0;
1500    boolean display = true;
1501    
1502    int tabIndex = 0;
1503    
1504    for (Enumeration e = runs.elements(); e.hasMoreElements(); )
1505        {
1506      Run run = (