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 = (