]> git.somenet.org - irc/pjirc-ng.git/blob - src/main/java/irc/style/StyledList.java
Pjirc 2.2.1 as available on the net, reformatted and made it compile.
[irc/pjirc-ng.git] / src / main / java / irc / style / StyledList.java
1 /*****************************************************/
2 /*          This java file is a part of the          */
3 /*                                                   */
4 /*           -  Plouf's Java IRC Client  -           */
5 /*                                                   */
6 /*   Copyright (C)  2002 - 2005 Philippe Detournay   */
7 /*                                                   */
8 /*         All contacts : theplouf@yahoo.com         */
9 /*                                                   */
10 /*  PJIRC is free software; you can redistribute     */
11 /*  it and/or modify it under the terms of the GNU   */
12 /*  General Public License as published by the       */
13 /*  Free Software Foundation; version 2 or later of  */
14 /*  the License.                                     */
15 /*                                                   */
16 /*  PJIRC is distributed in the hope that it will    */
17 /*  be useful, but WITHOUT ANY WARRANTY; without     */
18 /*  even the implied warranty of MERCHANTABILITY or  */
19 /*  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU   */
20 /*  General Public License for more details.         */
21 /*                                                   */
22 /*  You should have received a copy of the GNU       */
23 /*  General Public License along with PJIRC; if      */
24 /*  not, write to the Free Software Foundation,      */
25 /*  Inc., 59 Temple Place, Suite 330, Boston,        */
26 /*  MA  02111-1307  USA                              */
27 /*                                                   */
28 /*****************************************************/
29
30 package irc.style;
31
32 import irc.IRCConfiguration;
33 import irc.ListenerGroup;
34 import irc.StyleContext;
35
36 import java.awt.Color;
37 import java.awt.Cursor;
38 import java.awt.Font;
39 import java.awt.Graphics;
40 import java.awt.Image;
41 import java.awt.Panel;
42 import java.awt.Rectangle;
43 import java.awt.event.InputEvent;
44 import java.awt.event.MouseEvent;
45 import java.awt.event.MouseListener;
46 import java.awt.event.MouseMotionListener;
47 import java.util.Enumeration;
48 import java.util.Hashtable;
49 import java.util.Vector;
50
51 /**
52  * LimitedArray, used to remove too old lines from the text history.
53  */
54 class LimitedArray {
55         private int _size;
56         private int _maximum;
57         private int _missing;
58         private Object[] _array;
59
60         /**
61          * Create a new LimitedArray
62          * 
63          * @param max
64          *          maximum number of items.
65          */
66         public LimitedArray(int max) {
67                 _size = 0;
68                 _missing = 0;
69                 _maximum = max;
70                 _array = new Object[4];
71         }
72
73         /**
74          * Expand the actual number of stockable items.
75          */
76         public void expand() {
77                 if (_array.length >= _maximum)
78                         return;
79                 int ns = _array.length << 1;
80                 Object[] n = new Object[ns];
81                 System.arraycopy(_array, 0, n, 0, _array.length);
82                 _array = n;
83         }
84
85         /**
86          * Add an object.
87          * 
88          * @param obj
89          *          object to add.
90          */
91         public void add(Object obj) {
92                 if (size() >= _array.length)
93                         expand();
94                 if (size() >= _array.length)
95                         _missing++;
96                 _array[size() % _array.length] = obj;
97                 _size++;
98         }
99
100         /**
101          * Get the object at given index.
102          * 
103          * @param index
104          *          index.
105          * @return the object at index, or null if object has been removed.
106          */
107         public Object get(int index) {
108                 if (index < _missing)
109                         return null;
110                 return _array[(index) % _array.length];
111         }
112
113         /**
114          * Get the number of objects.
115          * 
116          * @return number of objects.
117          */
118         public int size() {
119                 return _size;
120         }
121 }
122
123 /**
124  * ResultPair.
125  */
126 class ResultPair {
127         /**
128          * Line count.
129          */
130         public int line;
131         /**
132          * Draw result.
133          */
134         public DrawResult result;
135 }
136
137 /**
138  * A styled list is a panel designed for displaying lines of styled text, using
139  * special color and style codes, and smileys.
140  */
141 public class StyledList extends Panel implements MouseListener, MouseMotionListener, FormattedStringDrawerListener {
142         private LimitedArray _list;
143         private Hashtable _nickInfos;
144         private boolean _wrap;
145         private int _last;
146         private int _first;
147         private int _left;
148         private int _width;
149         private int _toScrollX;
150         private int _toScrollY;
151         private FormattedStringDrawer _drawer;
152         private Image _buffer;
153         private int _bufferWidth;
154         private int _bufferHeight;
155         private int _lastWidth;
156         private int _lastHeight;
157         private Hashtable _results;
158         private MultipleWordCatcher _catcher;
159         private WordListRecognizer _wordListRecognizer;
160         private IRCConfiguration _ircConfiguration;
161
162         private int _pressedX;
163         private int _pressedY;
164         private int _draggedX;
165         private int _draggedY;
166         private boolean _dragging;
167         private DrawResultItem _currentItem;
168         private DrawResultItem _currentFloatItem;
169         private DrawResultItem _currentHighLightItem;
170         private String _currentFloatText;
171         private String _copiedString;
172         private boolean _fullDraw;
173
174         private ListenerGroup _listeners;
175
176         private ResultPair[] _addedResults;
177         private int _addedCount;
178
179         private int _hdirection;
180         private int _vdirection;
181         private Color _colormale;
182         private Color _colorfemeale;
183         private Color _colorundef;
184
185         private Vector _updateItems;
186         private long _lastRefresh = System.currentTimeMillis();
187
188         private Image _backImage;
189         private int _backTiling;
190
191         private int _maximumSize;
192         private DecodedLine _emptyLine;
193
194         private final static int BOTTOM = FormattedStringDrawer.BOTTOM;
195         private final static int TOP = FormattedStringDrawer.TOP;
196         /** Left to right direction. */
197         public final static int LEFT = FormattedStringDrawer.LEFT;
198         /** Right to left direction. */
199         public final static int RIGHT = FormattedStringDrawer.RIGHT;
200
201         private final static boolean _doubleBuffer = true; // NON-BUFFERED DISPLAY NOT
202                                                                                                                                                                                                                         // YET FUNCTIONAL
203
204         /**
205          * Create a new StyledList with automatic text wraping.
206          * 
207          * @param config
208          *          global irc configuration.
209          * @param context
210          *          style context.
211          */
212         public StyledList(IRCConfiguration config, StyleContext context) {
213                 this(config, true, context);
214         }
215
216         /**
217          * Create a new StyledList.
218          * 
219          * @param config
220          *          global irc configuration.
221          * @param wrap
222          *          true if wrapping must occur, false otherwise.
223          * @param context
224          *          style context.
225          */
226         public StyledList(IRCConfiguration config, boolean wrap, StyleContext context) {
227                 this(config, wrap, context, Color.blue, Color.pink, Color.gray);
228         }
229
230         /**
231          * Create a new StyledList.
232          * 
233          * @param config
234          *          global irc configuration.
235          * @param wrap
236          *          true if wrapping must occur, false otherwise.
237          * @param context
238          *          style context.
239          * @param male
240          *          male color for asl.
241          * @param femeale
242          *          femeale color for asl.
243          * @param undef
244          *          undefined gender color for asl.
245          */
246         public StyledList(IRCConfiguration config, boolean wrap, StyleContext context, Color male, Color femeale, Color undef) {
247                 super();
248                 _backImage = null;
249                 _backTiling = IRCConfiguration.TILING_CENTER;
250                 _colormale = male;
251                 _colorfemeale = femeale;
252                 _colorundef = undef;
253                 _nickInfos = new Hashtable();
254                 _fullDraw = false;
255                 _addedResults = new ResultPair[64];
256                 for (int i = 0; i < _addedResults.length; i++)
257                         _addedResults[i] = new ResultPair();
258                 _addedCount = 0;
259                 _hdirection = LEFT;
260                 _vdirection = BOTTOM;
261                 _ircConfiguration = config;
262                 _copiedString = "";
263                 _dragging = false;
264                 _currentFloatItem = null;
265                 _currentFloatText = null;
266                 _currentItem = null;
267                 _toScrollX = 0;
268                 _toScrollY = 0;
269                 _left = 0;
270                 _wrap = wrap;
271                 _buffer = null;
272                 _drawer = new FormattedStringDrawer(_ircConfiguration, context, this);
273                 _drawer.setHorizontalDirection(_hdirection);
274                 _drawer.setVerticalDirection(_vdirection);
275                 _catcher = new MultipleWordCatcher();
276                 _wordListRecognizer = new WordListRecognizer();
277                 _catcher.addRecognizer(new ChannelRecognizer());
278                 _catcher.addRecognizer(new URLRecognizer());
279                 _catcher.addRecognizer(_wordListRecognizer);
280                 _results = new Hashtable();
281                 _listeners = new ListenerGroup();
282                 addMouseListener(this);
283                 addMouseMotionListener(this);
284                 _maximumSize = _ircConfiguration.getI("style:maximumlinecount");
285                 _emptyLine = _drawer.decodeLine("");
286                 clear();
287                 setBackgroundImage(_ircConfiguration.getStyleBackgroundImage(context));
288                 setBackgroundTiling(_ircConfiguration.getStyleBackgroundTiling(context));
289                 if (_ircConfiguration.getB("style:righttoleft"))
290                         setHorizontalDirection(RIGHT);
291         }
292
293         /**
294          * Release this object. No further call may be performed on this object.
295          */
296         public void release() {
297                 clear();
298                 dispose();
299                 removeMouseListener(this);
300                 removeMouseMotionListener(this);
301         }
302
303         private void drawBackImage(Graphics g, int w, int h) {
304                 int iw = _backImage.getWidth(this);
305                 int ih = _backImage.getHeight(this);
306                 switch (_backTiling & 0xff) {
307                 case IRCConfiguration.TILING_FIXED: {
308                         int x = 0;
309                         int y = 0;
310                         if ((_backTiling & IRCConfiguration.TILING_HORIZONTAL_RIGHT) != 0)
311                                 x = w - iw - 1;
312                         if ((_backTiling & IRCConfiguration.TILING_VERTICAL_DOWN) != 0)
313                                 y = h - ih - 1;
314                         g.setColor(_drawer.getColor(0));
315                         g.fillRect(0, 0, w, h);
316                         g.drawImage(_backImage, x, y, _drawer.getColor(0), this);
317                         break;
318                 }
319                 case IRCConfiguration.TILING_CENTER: {
320                         int x = (w - iw) / 2;
321                         int y = (h - ih) / 2;
322                         g.setColor(_drawer.getColor(0));
323                         g.fillRect(0, 0, w, h);
324                         g.drawImage(_backImage, x, y, _drawer.getColor(0), this);
325                         break;
326                 }
327                 case IRCConfiguration.TILING_STRETCH: {
328                         g.drawImage(_backImage, 0, 0, w, h, _drawer.getColor(0), this);
329                         break;
330                 }
331                 case IRCConfiguration.TILING_TILE: {
332                         int x = 0;
333                         while (x < w) {
334                                 int y = 0;
335                                 while (y < h) {
336                                         g.drawImage(_backImage, x, y, _drawer.getColor(0), this);
337                                         y += ih;
338                                 }
339                                 x += iw;
340                         }
341                         break;
342                 }
343                 }
344         }
345
346         private void expandResult() {
347                 ResultPair[] n = new ResultPair[_addedResults.length * 2];
348                 System.arraycopy(_addedResults, 0, n, 0, _addedResults.length);
349                 for (int i = _addedResults.length; i < n.length; i++)
350                         n[i] = new ResultPair();
351                 _addedResults = n;
352         }
353
354         /**
355          * Set the horizontal display direction.
356          * 
357          * @param direction
358          *          horizontal display direction.
359          */
360         public void setHorizontalDirection(int direction) {
361                 _hdirection = direction;
362                 _drawer.setHorizontalDirection(_hdirection);
363         }
364
365         /**
366          * Get the horizontal display direction.
367          * 
368          * @return horizontal display direction.
369          */
370         public int getHorizontalDirection() {
371                 return _hdirection;
372         }
373
374         /**
375          * Set the background image for display.
376          * 
377          * @param img
378          *          background image, or null if no background image is to be
379          *          displayed.
380          */
381         public void setBackgroundImage(Image img) {
382                 _backImage = img;
383                 repaint();
384         }
385
386         /**
387          * Set the background image tiling.
388          * 
389          * @param t
390          *          background image tiling mode. See IRCConfiguration for tiling
391          *          modes.
392          */
393         public void setBackgroundTiling(int t) {
394                 _backTiling = t;
395                 repaint();
396         }
397
398         /**
399          * Set the font to be used for display.
400          * 
401          * @param fnt
402          *          font to be used.
403          */
404         @Override
405         public void setFont(Font fnt) {
406                 _drawer.setFont(fnt);
407                 reinit();
408                 repaint();
409         }
410
411         /**
412          * Set the wrap mode.
413          * 
414          * @param wrap
415          *          mode, true if end-of-line wrapping must be performed, false
416          *          otherwise.
417          */
418         public void setWrap(boolean wrap) {
419                 _wrap = wrap;
420                 reinit();
421                 repaint();
422         }
423
424         /**
425          * Set the nick list for recognition.
426          * 
427          * @param list
428          *          the nick list.
429          */
430         public synchronized void setNickList(String[] list) {
431                 String[] actualList = new String[list.length];
432                 _nickInfos.clear();
433                 for (int i = 0; i < list.length; i++) {
434                         String nick = list[i];
435                         String info = "";
436                         int pos = nick.indexOf(":");
437                         if (pos != -1) {
438                                 info = nick.substring(pos + 1);
439                                 nick = nick.substring(0, pos);
440                         }
441                         actualList[i] = nick;
442                         _nickInfos.put(nick.toLowerCase(java.util.Locale.ENGLISH), info);
443                 }
444                 _wordListRecognizer.setList(actualList);
445         }
446
447         /**
448          * Add a listener.
449          * 
450          * @param lis
451          *          the new listener.
452          */
453         public synchronized void addStyledListListener(StyledListListener lis) {
454                 _listeners.addListener(lis);
455         }
456
457         /**
458          * Remove a listener.
459          * 
460          * @param lis
461          *          the listener to remove.
462          */
463         public synchronized void removeStyledListListener(StyledListListener lis) {
464                 _listeners.removeListener(lis);
465         }
466
467         /**
468          * Set the left offset for this list rendering.
469          * 
470          * @param left
471          *          the left offset, in pixel.
472          */
473         public synchronized void setLeft(int left) {
474                 // int w=getSize().width;
475                 int oldLeft = _left;
476                 _left = left;
477                 if (_left < 0)
478                         _left = 0;
479                 if (_left >= getLogicalWidth())
480                         _left = getLogicalWidth() - 1;
481
482                 if (_hdirection == RIGHT)
483                         _left = -_left;
484
485                 if (_left != oldLeft) {
486                         addToScroll(_left - oldLeft, 0);
487                         repaint();
488                 }
489         }
490
491         /**
492          * Get the left offset.
493          * 
494          * @return the left offset, in pixel.
495          */
496         public int getLeft() {
497                 if (_hdirection == RIGHT)
498                         return -_left;
499                 return _left;
500         }
501
502         /**
503          * Set the first line to be displayed.
504          * 
505          * @param first
506          *          the first line to be displayed.
507          */
508         public synchronized void setFirst(int first) {
509                 if (_vdirection != TOP)
510                         _fullDraw = true;
511                 _vdirection = TOP;
512                 _drawer.setVerticalDirection(TOP);
513                 int oldFirst = _first;
514                 _first = first;
515                 if (_first < 0)
516                         _last = 0;
517                 if (_first >= _list.size())
518                         _last = _list.size() - 1;
519                 if (_first != oldFirst) {
520                         addToScroll(0, _first - oldFirst);
521                         repaint();
522                 }
523         }
524
525         /**
526          * Set the last line to be displayed.
527          * 
528          * @param last
529          *          last line to be displayed.
530          */
531         public synchronized void setLast(int last) {
532                 if (_vdirection != BOTTOM)
533                         _fullDraw = true;
534                 _vdirection = BOTTOM;
535                 _drawer.setVerticalDirection(BOTTOM);
536                 int oldLast = _last;
537                 _last = last;
538                 if (_last < 0)
539                         _last = 0;
540                 if (_last >= _list.size())
541                         _last = _list.size() - 1;
542                 if (_last != oldLast) {
543                         addToScroll(0, _last - oldLast);
544                         repaint();
545                 }
546         }
547
548         /**
549          * Get the logical width of this list.
550          * 
551          * @return the logical width, in pixel.
552          */
553         public int getLogicalWidth() {
554                 return _width;
555         }
556
557         /**
558          * Get the last displayed line.
559          * 
560          * @return the last displayed line.
561          */
562         public int getLast() {
563                 return _last;
564         }
565
566         /**
567          * Get the number of line in this list.
568          * 
569          * @return the line count.
570          */
571         public synchronized int getLineCount() {
572                 return _list.size();
573         }
574
575         /**
576          * Add a line at the end of this list.
577          * 
578          * @param line
579          *          new line to add.
580          */
581         public synchronized void addLine(String line) {
582                 DecodedLine dline = _drawer.decodeLine(line);
583                 _list.add(dline);
584
585                 if (_vdirection == BOTTOM) {
586                         if (_last == _list.size() - 2)
587                                 setLast(_last + 1);
588                 } else if (_vdirection == TOP) {
589                         _fullDraw = true;
590                         repaint();
591                 }
592         }
593
594         /**
595          * Add the given lines at the end of this list.
596          * 
597          * @param lines
598          *          lines to add.
599          */
600         public synchronized void addLines(String[] lines) {
601                 boolean willScroll = (_list.size() - 1 == _last);
602
603                 for (int i = 0; i < lines.length; i++)
604                         _list.add(_drawer.decodeLine(lines[i]));
605
606                 if (_vdirection == BOTTOM) {
607                         if (willScroll)
608                                 setLast(_list.size() - 1);
609                 } else if (_vdirection == TOP) {
610                         _fullDraw = true;
611                         repaint();
612                 }
613         }
614
615         private void reinit() {
616                 if (_buffer != null)
617                         _buffer.flush();
618                 _buffer = null;
619                 _results = new Hashtable();
620         }
621
622         /**
623          * Dispose any off-screen ressources used by the list. This method won't put
624          * the list in a non-drawable state, but next screen refresh might me slower.
625          */
626         public synchronized void dispose() {
627                 reinit();
628         }
629
630         /**
631          * Clear all the lines in this list.
632          */
633         public synchronized void clear() {
634                 _list = new LimitedArray(_maximumSize);
635                 _last = _list.size() - 1;
636                 _first = 0;
637                 setLeft(0);
638                 _width = getSize().width;
639                 _fullDraw = true;
640                 repaint();
641         }
642
643         /**
644          * Clear all the lines in this list, reconfiguring the maximum line count to
645          * max.
646          * 
647          * @param max
648          *          the new maximum line count.
649          */
650         public synchronized void clear(int max) {
651                 _maximumSize = max;
652                 clear();
653         }
654
655         private void drawPart(Graphics g, int x, int y, int w, int h, boolean analyse, int gw, int gh) {
656                 // System.out.println("draw part "+x+","+y+","+w+","+h);
657                 if (y < 0) {
658                         h += y;
659                         y = 0;
660                 }
661
662                 if (_backImage != null) {
663                         drawBackImage(g, gw, gh);
664                 } else {
665                         g.setColor(_drawer.getColor(0));
666                         g.fillRect(x, y, w, h);
667                 }
668
669                 if (_vdirection == BOTTOM) {
670                         int first = _last;
671                         int posY = getSize().height;
672                         while ((posY > y + h) && (first >= 0))
673                                 posY -= getHeight(first--, g);
674                         if (first != _last)
675                                 posY += getHeight(++first, g);
676                         draw(g, 0, first, posY, y, x, x + w - 1, analyse);
677                 } else if (_vdirection == TOP) {
678                         int first = _first;
679                         int posY = 0;
680                         while ((posY < y) && (first < _list.size()))
681                                 posY += getHeight(first++, g);
682                         if (first != _first)
683                                 posY -= getHeight(--first, g);
684                         draw(g, first, _list.size() - 1, posY, y + h, x, x + w - 1, analyse);
685                 }
686         }
687
688         @Override
689         public synchronized void paint(Graphics g) {
690                 if (_doubleBuffer || (_toScrollX != 0) || (_toScrollY != 0)) {
691                         if ((_toScrollX != 0) || (_toScrollY != 0))
692                                 _fullDraw = true;
693                         update(g);
694                         return;
695                 }
696                 int x = 0;
697                 int y = 0;
698                 int w = getSize().width;
699                 int h = getSize().height;
700                 Rectangle cl = g.getClipBounds();
701                 if (cl != null) {
702                         x = cl.x;
703                         y = cl.y;
704                         w = cl.width;
705                         h = cl.height;
706                 }
707                 drawPart(g, x, y, w, h, false, w, h);
708         }
709
710         private int getHeight(Graphics g, int a, int b) {
711                 if (b < a) {
712                         int tmp = a;
713                         a = b;
714                         b = tmp;
715                 }
716                 int res = 0;
717                 for (int i = a; i <= b; i++)
718                         res += getHeight(i, g);
719                 return res;
720         }
721
722         private void draw(Graphics g, int from, int to, int y, int crossy, int debx, int finx, boolean analyse) {
723                 int w = getSize().width;
724                 // int h=getSize().height;
725                 // int wrapPos=w;
726                 _addedCount = 0;
727
728                 DrawResult res = new DrawResult();
729
730                 if (_vdirection == BOTTOM) {
731                         int index = to;
732                         while ((index >= from) && (y > crossy)) {
733                                 DecodedLine str = (DecodedLine) _list.get(index);
734                                 if (str == null)
735                                         str = _emptyLine;
736                                 _drawer.draw(str, g, -_left, w - 1 - _left, y, debx, finx, analyse, _wrap, res);
737                                 StyledRectangle rect = res.rectangle;
738                                 if (rect.width > _width) {
739                                         _width = rect.width;
740                                         _listeners.sendEventAsync("virtualSizeChanged", this);
741                                 }
742                                 if (analyse) {
743                                         ResultPair p = _addedResults[_addedCount++];
744                                         if (_addedCount == _addedResults.length)
745                                                 expandResult();
746                                         p.line = index;
747                                         p.result = res;
748                                         res = new DrawResult();
749                                 }
750                                 y -= rect.height;
751                                 index--;
752                         }
753                 } else {
754                         int index = from;
755                         while ((index <= to) && (y < crossy)) {
756                                 DecodedLine str = (DecodedLine) _list.get(index);
757                                 if (str == null)
758                                         str = _emptyLine;
759                                 _drawer.draw(str, g, -_left, w - 1 - _left, y, debx, finx, analyse, _wrap, res);
760                                 StyledRectangle rect = res.rectangle;
761                                 if (rect.width > _width) {
762                                         _width = rect.width;
763                                         _listeners.sendEventAsync("virtualSizeChanged", this);
764                                 }
765                                 if (analyse) {
766                                         ResultPair p = _addedResults[_addedCount++];
767                                         if (_addedCount == _addedResults.length)
768                                                 expandResult();
769                                         p.line = index;
770                                         p.result = res;
771                                         res = new DrawResult();
772                                 }
773                                 y += rect.height;
774                                 index++;
775                         }
776
777                 }
778         }
779
780         private void addToScroll(int vx, int vy) {
781                 _toScrollX += vx;
782                 _toScrollY += vy;
783         }
784
785         private int getScrollX() {
786                 if (_dragging)
787                         return 0;
788                 int res = _toScrollX;
789                 _toScrollX = 0;
790                 return res;
791         }
792
793         private int getScrollY() {
794                 if (_dragging)
795                         return 0;
796                 int res = _toScrollY;
797                 _toScrollY = 0;
798                 return res;
799         }
800
801         private void scrollDrawItems(int dx, int dy) {
802                 int h = getSize().height;
803                 Enumeration e = _results.keys();
804                 while (e.hasMoreElements()) {
805                         Integer key = (Integer) e.nextElement();
806                         DrawResult res = (DrawResult) _results.get(key);
807                         res.rectangle.x += dx;
808                         res.rectangle.y += dy;
809                         if ((res.rectangle.y + res.rectangle.height < 0) || (res.rectangle.y >= h)) {
810                                 _results.remove(key);
811                         }
812                 }
813         }
814
815         private void combineItems() {
816                 for (int i = 0; i < _addedCount; i++) {
817                         ResultPair k = _addedResults[i];
818                         _results.put(new Integer(k.line), k.result);
819                 }
820                 _addedCount = 0;
821         }
822
823         private DrawResultItem findItem(int x, int y) {
824                 Enumeration e = _results.elements();
825                 while (e.hasMoreElements()) {
826                         DrawResult result = (DrawResult) e.nextElement();
827                         if (result.rectangle.contains(x, y)) {
828                                 int rx = x - result.rectangle.x;
829                                 int ry = y - result.rectangle.y;
830                                 for (int i = 0; i < result.items.length; i++) {
831                                         DrawResultItem item = result.items[i];
832                                         if (item.rectangle.contains(rx, ry))
833                                                 return item;
834                                 }
835                         }
836                 }
837                 return null;
838         }
839
840         private int findLine(int y) {
841                 Enumeration e = _results.keys();
842                 while (e.hasMoreElements()) {
843                         Integer i = (Integer) e.nextElement();
844
845                         DrawResult result = (DrawResult) _results.get(i);
846                         if ((result.rectangle.y <= y) && (result.rectangle.y + result.rectangle.height > y)) {
847                                 return i.intValue();
848                         }
849                 }
850                 return -1;
851         }
852
853         private int getHeight(int lineIndex, Graphics g) {
854                 DrawResult r = (DrawResult) _results.get(new Integer(lineIndex));
855                 if (r != null)
856                         return r.rectangle.height;
857
858                 int wrapPos = getSize().width;
859                 DecodedLine str = (DecodedLine) _list.get(lineIndex);
860                 if (str == null)
861                         str = _emptyLine;
862                 return _drawer.getHeight(str, g, -_left, wrapPos, _wrap);
863         }
864
865         private Color findColor(String info) {
866                 return _ircConfiguration.getASLColor(info, _colormale, _colorfemeale, _colorundef);
867         }
868
869         private synchronized Vector getUpdateItems() {
870                 Vector items = _updateItems;
871                 _updateItems = null;
872                 return items;
873         }
874
875         private synchronized boolean addToUpdateItems(Integer line) {
876                 if (_updateItems == null)
877                         _updateItems = new Vector();
878                 for (int i = 0; i < _updateItems.size(); i++) {
879                         Integer r = (Integer) _updateItems.elementAt(i);
880                         if (r.equals(line))
881                                 return false;
882                 }
883                 _updateItems.insertElementAt(line, _updateItems.size());
884                 return true;
885         }
886
887         @Override
888         public synchronized void update(Graphics g) {
889                 int w = getSize().width;
890                 int h = getSize().height;
891                 if (h <= 0 || w <= 0)
892                         return;
893
894                 Graphics gra = g;
895                 if (_doubleBuffer) {
896                         if (_buffer != null) {
897                                 if ((_bufferWidth < w) || (_bufferHeight < h)) {
898                                         reinit();
899                                 }
900                                 // Optimize memory usage
901                                 if ((_bufferHeight > w * 1.5) || (_bufferHeight > h * 1.5)) {
902                                         reinit();
903                                 }
904                                 if ((_lastWidth != w) || (_lastHeight != h)) {
905                                         _fullDraw = true;
906                                 }
907                         }
908
909                         _lastWidth = w;
910                         _lastHeight = h;
911
912                         if (_buffer == null) {
913                                 _buffer = createImage(w, h);
914                                 if (_buffer == null) {
915                                         repaint();
916                                         return;
917                                 }
918                                 _bufferWidth = w;
919                                 _bufferHeight = h;
920                                 _fullDraw = true;
921                         }
922
923                         gra = _buffer.getGraphics();
924                 }
925
926                 if (_ircConfiguration.getB("style:backgroundimage"))
927                         _fullDraw = true;
928
929                 int scrx = getScrollX();
930                 int scry = getScrollY();
931                 Vector items = getUpdateItems();
932
933                 if (!_fullDraw) {
934
935                         if (scrx < 0) {
936                                 gra.copyArea(0, 0, w + scrx, h, -scrx, 0);
937                                 scrollDrawItems(-scrx, 0);
938                                 drawPart(gra, 0, 0, -scrx, h, false, w, h);
939                         } else if (scrx > 0) {
940                                 gra.copyArea(scrx, 0, w - scrx, h, -scrx, 0);
941                                 scrollDrawItems(-scrx, 0);
942                                 drawPart(gra, w - scrx, 0, scrx, h, false, w, h);
943                         }
944
945                         if (scry > 0) {
946                                 int baseY;
947                                 if (_vdirection == BOTTOM)
948                                         baseY = getHeight(gra, _last - scry + 1, _last);
949                                 else
950                                         baseY = getHeight(gra, _first - scry, _first - 1);
951
952                                 gra.copyArea(0, baseY, w, h - baseY, 0, -baseY);
953                                 scrollDrawItems(0, -baseY);
954                                 drawPart(gra, 0, h - baseY, w, baseY, true, w, h);
955                                 combineItems();
956                         } else if (scry < 0) {
957                                 int baseY;
958                                 if (_vdirection == BOTTOM)
959                                         baseY = getHeight(gra, _last + 1, _last - scry);
960                                 else
961                                         baseY = getHeight(gra, _first, _first - scry - 1);
962
963                                 gra.copyArea(0, 0, w, h - baseY, 0, baseY);
964                                 scrollDrawItems(0, baseY);
965                                 drawPart(gra, 0, 0, w, baseY, true, w, h);
966                                 combineItems();
967                         }
968
969                         if (items != null) {
970                                 for (int i = 0; i < items.size(); i++) {
971                                         Integer line = (Integer) items.elementAt(i);
972                                         DrawResult res = (DrawResult) _results.get(line);
973                                         if (res != null) {
974                                                 StyledRectangle r = res.rectangle;
975                                                 drawPart(gra, r.x, r.y, r.width, r.height, false, w, h);
976                                         }
977                                 }
978                         }
979
980                 } else {
981                         _results = new Hashtable();
982                         drawPart(gra, 0, 0, w, h, true, w, h);
983                         combineItems();
984                         _fullDraw = false;
985                 }
986
987                 if (_dragging)
988                         makeXor(gra);
989                 if (_doubleBuffer)
990                         g.drawImage(_buffer, 0, 0, this);
991                 if (_dragging)
992                         makeXor(gra);
993
994                 if (!_dragging && (_currentFloatItem != null) && _ircConfiguration.getB("style:floatingasl")) {
995                         int x = _currentFloatItem.rectangle.x + _currentFloatItem.parent.rectangle.x + 4;
996                         int y = _currentFloatItem.rectangle.y + _currentFloatItem.parent.rectangle.y;
997                         if (_vdirection == TOP)
998                                 y += 8;
999                         else
1000                                 y -= 8;
1001
1002                         if (y < 0)
1003                                 y = 0;
1004
1005                         String info = _currentFloatText;
1006                         String text = _ircConfiguration.formatASL(info);
1007                         if (text.length() > 0) {
1008                                 int tw = g.getFontMetrics().stringWidth(text);
1009                                 int fh = g.getFont().getSize();
1010
1011                                 if (y + fh + 5 > h)
1012                                         y = h - fh - 5;
1013                                 if (x + tw + 5 > w)
1014                                         x = w - tw - 5;
1015                                 g.setColor(getAlphaColor(findColor(info), _ircConfiguration.getI("style:floatingaslalpha")));
1016                                 g.fillRect(x, y, tw + 4, fh + 4);
1017                                 g.setColor(Color.white);
1018                                 g.drawString(text, x + 2, y + fh);
1019                         }
1020                 }
1021
1022                 if (_ircConfiguration.getB("style:highlightlinks")) {
1023                         if (!_dragging && (_currentHighLightItem != null)) {
1024                                 int x = _currentHighLightItem.rectangle.x + _currentHighLightItem.parent.rectangle.x;
1025                                 int y = _currentHighLightItem.rectangle.y + _currentHighLightItem.parent.rectangle.y;
1026                                 g.setXORMode(Color.white);
1027                                 g.setColor(Color.black);
1028                                 g.fillRect(x, y, _currentHighLightItem.rectangle.width, _currentHighLightItem.rectangle.height);
1029                                 g.setPaintMode();
1030                         }
1031                 }
1032         }
1033
1034         private Color getAlphaColor(Color c, int alpha) {
1035                 try {
1036                         return new Color(c.getRed(), c.getGreen(), c.getBlue(), alpha);
1037                 } catch (Throwable ex) {
1038                         return c;
1039                 }
1040         }
1041
1042         private void makeXor(Graphics g) {
1043                 String res = "";
1044                 int dw = _draggedX - _pressedX;
1045                 int dh = _draggedY - _pressedY;
1046
1047                 int pressedX = _pressedX;
1048                 int pressedY = _pressedY;
1049
1050                 g.setXORMode(Color.white);
1051                 g.setColor(Color.black);
1052                 int i = findLine(pressedY);
1053                 int basei = i;
1054                 DrawResult result = (DrawResult) _results.get(new Integer(i));
1055
1056                 if (result == null) {
1057                         _copiedString = "";
1058                         return;
1059                 }
1060
1061                 int px, py;
1062
1063                 px = pressedX - result.rectangle.x;
1064                 py = pressedY - result.rectangle.y;
1065
1066                 DrawResultItem item = null;
1067                 int a, b = 0;
1068                 for (a = 0; a < result.items.length; a++) {
1069                         if (result.items[a].rectangle.contains(px, py)) {
1070                                 item = result.items[a];
1071                                 b = a;
1072                         }
1073                 }
1074
1075                 if ((item == null) || ((px + dw < item.rectangle.x) && (py + dh < item.rectangle.y))
1076                                 || (py + dh < item.rectangle.y)) {
1077                         _copiedString = "";
1078                         return;
1079                 }
1080                 boolean terminated = false;
1081                 while (!terminated) {
1082                         res += item.originalstrippedword;
1083                         StyledRectangle r = item.rectangle;
1084                         g.fillRect(r.x + result.rectangle.x, r.y + result.rectangle.y, r.width, r.height);
1085
1086                         if (!((i == basei) && (a == b)) && (item.rectangle.contains(px + dw, py + dh)))
1087                                 break;
1088                         b++;
1089                         if (b >= result.items.length) {
1090                                 b = 0;
1091                                 i++;
1092                                 result = (DrawResult) _results.get(new Integer(i));
1093                                 if (result == null)
1094                                         break;
1095                                 px = pressedX - result.rectangle.x;
1096                                 py = pressedY - result.rectangle.y;
1097                                 res += "\n";
1098                         }
1099                         item = result.items[b];
1100                         if (item.rectangle.y > py + dh)
1101                                 terminated = true;
1102                         if (_hdirection == LEFT)
1103                                 if ((item.rectangle.x > px + dw) && (item.rectangle.y + item.rectangle.height > py + dh))
1104                                         terminated = true;
1105                         if (_hdirection == RIGHT)
1106                                 if ((item.rectangle.x + item.rectangle.width < px + dw) && (item.rectangle.y + item.rectangle.height > py + dh))
1107                                         terminated = true;
1108                 }
1109
1110                 _copiedString = res;
1111                 g.setPaintMode();
1112         }
1113
1114         @Override
1115         public synchronized void mouseClicked(MouseEvent e) {
1116                 if ((e.getModifiers() & InputEvent.SHIFT_MASK) != 0) {
1117                         String res = "";
1118                         for (int i = 0; i < _list.size(); i++) {
1119                                 DecodedLine str = (DecodedLine) _list.get(i);
1120                                 if (str == null)
1121                                         str = _emptyLine;
1122                                 res += str.original + "\n";
1123                         }
1124                         _listeners.sendEventAsync("copyEvent", this, res, e);
1125                 }
1126         }
1127
1128         @Override
1129         public void mouseEntered(MouseEvent e) {
1130                 _currentFloatItem = null;
1131                 _currentItem = null;
1132                 _currentHighLightItem = null;
1133                 defCursor();
1134                 mouseMoved(e);
1135         }
1136
1137         @Override
1138         public void mouseExited(MouseEvent e) {
1139                 _currentFloatItem = null;
1140                 _currentItem = null;
1141                 _currentHighLightItem = null;
1142                 repaint();
1143         }
1144
1145         @Override
1146         public synchronized void mousePressed(MouseEvent e) {
1147                 _pressedX = e.getX();
1148                 _pressedY = e.getY();
1149                 _draggedX = _pressedX;
1150                 _draggedY = _pressedY;
1151                 _copiedString = "";
1152                 _dragging = false;
1153                 _currentItem = null;
1154                 DrawResultItem item = findItem(e.getX(), e.getY());
1155                 if (item != null) {
1156                         String type = _catcher.getType(item.item);
1157                         if (type == null) {
1158                                 // ignore...
1159                         } else if (type.equals("channel")) {
1160                                 _listeners.sendEventAsync("channelEvent", this, item.item, e);
1161                         } else if (type.equals("url")) {
1162                                 _listeners.sendEventAsync("URLEvent", this, item.item, e);
1163                         } else if (type.equals("wordlist")) {
1164                                 _listeners.sendEventAsync("nickEvent", this, item.item, e);
1165                         }
1166                 }
1167         }
1168
1169         @Override
1170         public synchronized void mouseReleased(MouseEvent e) {
1171                 if (_dragging) {
1172                         _dragging = false;
1173                         repaint();
1174                         if (_copiedString.length() > 0)
1175                                 _listeners.sendEventAsync("copyEvent", this, _copiedString, e);
1176                 }
1177         }
1178
1179         @Override
1180         public synchronized void mouseDragged(MouseEvent e) {
1181                 _draggedX = e.getX();
1182                 _draggedY = e.getY();
1183                 _dragging = true;
1184                 DrawResultItem item = findItem(e.getX(), e.getY());
1185                 if (item != _currentItem) {
1186                         _currentItem = item;
1187                         repaint();
1188                 }
1189         }
1190
1191         private void handCursor() {
1192                 if (!getCursor().equals(new Cursor(Cursor.HAND_CURSOR)))
1193                         setCursor(new Cursor(Cursor.HAND_CURSOR));
1194         }
1195
1196         private void defCursor() {
1197                 if (!getCursor().equals(new Cursor(Cursor.DEFAULT_CURSOR)))
1198                         setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
1199         }
1200
1201         private boolean sameItem(DrawResultItem a, DrawResultItem b) {
1202                 if ((a == null) && (b == null))
1203                         return true;
1204                 if (a == null)
1205                         return false;
1206                 if (b == null)
1207                         return false;
1208                 return a.equals(b);
1209         }
1210
1211         @Override
1212         public synchronized void mouseMoved(MouseEvent e) {
1213                 DrawResultItem item = findItem(e.getX(), e.getY());
1214                 DrawResultItem oldFloat = _currentFloatItem;
1215                 DrawResultItem oldHigh = _currentHighLightItem;
1216                 if (!sameItem(item, _currentItem)) {
1217                         _currentItem = item;
1218                         _currentFloatItem = null;
1219                         _currentHighLightItem = null;
1220                         if (item != null) {
1221                                 String type = _catcher.getType(item.item);
1222                                 if (type != null) {
1223                                         handCursor();
1224                                         if (type.equals("wordlist")) {
1225                                                 String info = (String) _nickInfos.get(item.item.toLowerCase(java.util.Locale.ENGLISH));
1226                                                 if (info == null)
1227                                                         info = "";
1228                                                 if (info.length() == 0) {
1229                                                         _currentFloatItem = null;
1230                                                 } else {
1231                                                         _currentFloatItem = item;
1232                                                         _currentFloatText = info;
1233                                                 }
1234                                         }
1235
1236                                         if (_currentFloatItem == null)
1237                                                 _currentHighLightItem = item;
1238                                 } else
1239                                         defCursor();
1240                         } else
1241                                 defCursor();
1242                 }
1243
1244                 boolean repaint = false;
1245                 if (!sameItem(oldFloat, _currentFloatItem))
1246                         if (_ircConfiguration.getB("style:floatingasl"))
1247                                 repaint = true;
1248                 if (!sameItem(oldHigh, _currentHighLightItem))
1249                         repaint = true;
1250                 if (repaint)
1251                         repaint();
1252         }
1253
1254         @Override
1255         public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) {
1256                 // the background image has been updated
1257                 _fullDraw = true;
1258                 repaint();
1259                 return true;
1260         }
1261
1262         @Override
1263         public synchronized Boolean displayUpdated(Object handle, Integer what) {
1264                 boolean foundSome = false;
1265
1266                 // now we should go through all our draw results, and find which of them
1267                 // belong to this handle.
1268                 Enumeration e = _results.keys();
1269                 while (e.hasMoreElements()) {
1270                         Integer line = (Integer) e.nextElement();
1271                         DrawResult result = (DrawResult) _results.get(line);
1272                         if (result.updateHandles != null) {
1273                                 for (int i = 0; i < result.updateHandles.size(); i++) {
1274                                         if (result.updateHandles.elementAt(i).equals(handle)) {
1275                                                 if ((what.intValue() & FormattedStringDrawerListener.SIZE) != 0) {
1276                                                         _fullDraw = true;
1277                                                         repaint();
1278                                                         return Boolean.TRUE;
1279                                                 }
1280
1281                                                 // ok, so the line number 'line' must be redrawn
1282                                                 foundSome = true;
1283
1284                                                 // invalidate line line
1285                                                 addToUpdateItems(line);
1286                                                 if ((System.currentTimeMillis() - _lastRefresh > 10)
1287                                                                 || ((what.intValue() & FormattedStringDrawerListener.DATA) != 0)) {
1288                                                         repaint();
1289                                                         _lastRefresh = System.currentTimeMillis();
1290                                                 }
1291                                         }
1292                                 }
1293                         }
1294                 }
1295
1296                 if (foundSome)
1297                         return Boolean.TRUE;
1298                 return Boolean.FALSE;
1299         }
1300 }