1 /*****************************************************/
2 /* This java file is a part of the */
4 /* - Plouf's Java IRC Client - */
6 /* Copyright (C) 2002 - 2004 Philippe Detournay */
8 /* All contacts : theplouf@yahoo.com */
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 */
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. */
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 */
28 /*****************************************************/
32 import irc.EventDispatcher;
33 import irc.IRCConfiguration;
34 import irc.StyleContext;
36 import java.awt.Color;
37 import java.awt.Dimension;
39 import java.awt.FontMetrics;
40 import java.awt.Graphics;
41 import java.awt.Image;
42 import java.awt.Rectangle;
43 import java.awt.image.ImageObserver;
44 import java.util.Vector;
51 * Create a new CharacterInfo
53 public CharacterInfo() {
54 frontColor = Color.black;
55 backColor = Color.white;
63 * Create a new CharacterInfo
66 * base info that will be copied in the newly created instance.
68 public CharacterInfo(CharacterInfo base) {
69 frontColor = base.frontColor;
70 backColor = base.backColor;
72 isUnderline = base.isUnderline;
73 isReverse = base.isReverse;
74 isTransparent = base.isTransparent;
78 public boolean equals(Object o) {
79 if (!(o instanceof CharacterInfo))
81 CharacterInfo c = (CharacterInfo) o;
82 if (!frontColor.equals(c.frontColor))
84 if (!backColor.equals(c.backColor))
86 if (isBold != c.isBold)
88 if (isUnderline != c.isUnderline)
90 if (isTransparent != c.isTransparent)
96 public int hashCode() {
104 return c + frontColor.hashCode() + backColor.hashCode();
110 public Color frontColor;
114 public Color backColor;
118 public boolean isBold;
122 public boolean isUnderline;
126 public boolean isReverse;
128 * True if transparent.
130 public boolean isTransparent;
134 * CharacterGroupItem.
136 class CharacterGroupItem {
144 public CharacterInfo info;
147 * Create a new CharacterGroupItem
152 public CharacterGroupItem(CharacterInfo nfo) {
165 public CharacterGroupItem[] items;
167 * Original stripped word.
169 public String originalstrippedword;
173 public String originalword;
175 * Smiley-decoded word.
177 public String decodedword;
179 * Last character style.
181 public CharacterInfo lastInfo;
184 * Create a new WordItem
189 * last character style.
191 public WordItem(CharacterGroupItem[] itm, CharacterInfo lInfo) {
195 for (int i = 0; i < items.length; i++)
196 decodedword += items[i].s;
197 originalword = decodedword;
198 originalstrippedword = decodedword;
217 * DecodedLineInternal.
219 class DecodedLineInternal extends DecodedLine {
223 public WordItem[] words;
227 * The formatted string drawer.
229 public class FormattedStringDrawer implements ImageObserver {
231 private Font _fontPlain;
232 private Font _fontBold;
233 private Color[] _cols;
234 private CharactersDrawer _drawer;
235 private IRCConfiguration _config;
236 private Dimension _tmp;
237 private LineItem[] _lines;
238 private int _vdirection;
239 private int _hdirection;
240 private FormattedStringDrawerListener _listener;
245 public static final int BOTTOM = 0;
249 public static final int TOP = 1;
253 public static final int LEFT = 0;
257 public static final int RIGHT = 1;
260 * Create a new FormattedStringDrawer.
263 * the global configuration.
265 * the style context to use.
267 * the listener to notify upon draw update.
269 public FormattedStringDrawer(IRCConfiguration config, StyleContext context, FormattedStringDrawerListener listener) {
270 _listener = listener;
271 _tmp = new Dimension();
272 _lines = new LineItem[8];
273 for (int i = 0; i < _lines.length; i++)
274 _lines[i] = new LineItem();
276 setFont(config.getStyleFont(context));
277 _drawer = new CharactersDrawer(_config);
278 setStyleContext(context);
279 _vdirection = BOTTOM;
281 if (config.getB("style:righttoleft"))
282 setHorizontalDirection(RIGHT);
287 * Create a new FormattedStringDrawer.
290 * the global configuration.
292 * the style context to use.
294 public FormattedStringDrawer(IRCConfiguration config, StyleContext context) {
295 this(config, context, null);
299 * Set the vertical alignment direction.
302 * vertical direction.
304 public void setVerticalDirection(int dir) {
309 * Get the vertical alignment direction.
311 * @return the vertical direction.
313 public int getVerticalDirection() {
318 * Set the horizontal alignment direction.
321 * horizontal direction.
323 public void setHorizontalDirection(int dir) {
328 * Get the horizontal alignment direction.
330 * @return horizontal direction.
332 public int getHorizontalDirection() {
337 * Set the style context to use.
342 public void setStyleContext(StyleContext context) {
343 _cols = _config.getStyleColors(context);
347 * Prepare a line for display.
351 * @return prepared line, ready for display.
353 public DecodedLine decodeLine(String str) {
354 DecodedLineInternal ans = new DecodedLineInternal();
357 String decoded = _drawer.decodeLine(str);
358 ans.decoded = decoded;
359 ans.decoded_stripped = getStripped(decoded);
360 Vector v = doWords(str, decoded);
361 ans.words = new WordItem[v.size()];
362 for (int i = 0; i < ans.words.length; i++)
363 ans.words[i] = (WordItem) v.elementAt(i);
367 private Vector doWords(String ostr, String dstr) {
368 Vector words = new Vector();
369 CharacterInfo info = new CharacterInfo();
370 info.frontColor = _cols[1];
371 info.backColor = _cols[0];
372 info.isTransparent = true;
374 while (dstr.length() > 0) {
375 int opos = ostr.indexOf(' ');
376 int dpos = dstr.indexOf(' ');
379 word = decodeWord(info, dstr + " ", _cols);
380 word.originalword = ostr + " ";
381 word.originalstrippedword = getStripped(ostr + " ");
384 String owrd = ostr.substring(0, opos);
385 String dwrd = dstr.substring(0, dpos);
386 word = decodeWord(info, dwrd + " ", _cols);
387 word.originalword = owrd + " ";
388 word.originalstrippedword = getStripped(owrd + " ");
389 ostr = ostr.substring(opos + 1);
390 dstr = dstr.substring(dpos + 1);
392 words.insertElementAt(word, words.size());
393 info = word.lastInfo;
400 * Get the given string width, in pixel.
405 * the FontMetrics that will be used on display.
406 * @return the string width, in pixel.
408 public int getHeight(DecodedLine str, FontMetrics fm) {
409 return _drawer.getHeight(str.decoded_stripped, fm, this);
413 * Get the given string height, in pixel.
418 * the FontMetrics that will be used on display.
419 * @return the string height, in pixel.
421 public int getWidth(DecodedLine str, FontMetrics fm) {
422 return _drawer.getWidth(str.decoded_stripped, fm, this);
425 private Font deriveFont(Font fnt, int style) {
426 return new Font(fnt.getName(), style, fnt.getSize());
430 * Set the colors to use, overriding current colors from color context.
435 public void setColors(Color[] cols) {
440 * Get the current color at index i.
444 * @return the color at index i.
446 public Color getColor(int i) {
451 * Set the font to use.
454 * the font to be used.
456 public void setFont(Font fnt) {
458 _fontPlain = deriveFont(_font, Font.PLAIN);
459 _fontBold = deriveFont(_font, Font.BOLD);
465 * @return the used font.
467 public Font getFont() {
471 private WordItem decodeWord(CharacterInfo base, String str, Color[] cols) {
472 Vector v = new Vector();
473 CharacterInfo current = new CharacterInfo(base);
474 CharacterGroupItem currentItem = new CharacterGroupItem(new CharacterInfo(current));
475 int size = str.length();
476 for (int pos = 0; pos < size; pos++) {
477 char c = str.charAt(pos);
481 current.isBold = false;
482 current.isUnderline = false;
483 current.isReverse = false;
484 current.frontColor = cols[1];
485 current.backColor = cols[0];
486 current.isTransparent = true;
487 } else if (code == 2) {
488 current.isBold = !current.isBold;
489 } else if (code == 31) {
490 current.isUnderline = !current.isUnderline;
491 } else if (code == 22) {
492 current.isReverse = !current.isReverse;
493 if (current.isReverse) {
494 current.frontColor = cols[0];
495 current.backColor = cols[1];
496 current.isTransparent = false;
498 current.frontColor = cols[1];
499 current.backColor = cols[0];
500 current.isTransparent = true;
502 } else if (code == 3) {
503 boolean front = true;
508 char d = str.charAt(pos);
509 if ((d >= '0') && (d <= '9')) {
511 if (frontC.length() == 2) {
517 if (backC.length() == 2) {
524 } else if (d == ',') {
537 if (frontC.length() == 0)
539 if (frontC.length() > 0) {
540 int col = Integer.parseInt(frontC);
542 current.frontColor = cols[col];
544 if (backC.length() > 0) {
545 int col = Integer.parseInt(backC);
547 current.backColor = cols[col];
548 current.isTransparent = (col == 0);
550 if ((frontC.length() == 0) && (backC.length() == 0)) {
551 current.frontColor = cols[1];
552 current.backColor = cols[0];
553 current.isTransparent = true;
556 if (!current.equals(currentItem.info)) {
557 v.insertElementAt(currentItem, v.size());
558 currentItem = new CharacterGroupItem(new CharacterInfo(current));
564 v.insertElementAt(currentItem, v.size());
566 CharacterGroupItem[] ans = new CharacterGroupItem[v.size()];
567 for (int i = 0; i < v.size(); i++) {
568 ans[i] = (CharacterGroupItem) v.elementAt(i);
571 return new WordItem(ans, current);
574 private FontMetrics getFontMetrics(Graphics g, CharacterInfo nfo) {
575 Font old = g.getFont();
577 g.setFont(_fontBold);
579 g.setFont(_fontPlain);
580 FontMetrics res = g.getFontMetrics();
585 private int drawPart(Graphics g, CharacterInfo nfo, String str, int x, int y, FontMetrics plainMetrics, int clipxl,
586 int clipxr, ImageObserver obs, Vector handles) {
587 FontMetrics fm = plainMetrics;
588 // int up=plainMetrics.getAscent();
589 int down = plainMetrics.getDescent();
592 g.setFont(_fontBold);
594 fm = g.getFontMetrics();
596 int width = _drawer.getWidth(str, fm, this);
597 if ((x <= clipxr) && (x + width > clipxl)) {
598 int height = _drawer.getHeight(str, fm, this);
599 Rectangle originalClip = g.getClipBounds();
602 int cw = clipxr - clipxl + 1;
605 g.clipRect(cx, cy, cw, ch);
607 g.setColor(nfo.backColor);
609 if (!nfo.isTransparent)
610 g.fillRect(x, y - height, width, height);
614 g.setColor(nfo.frontColor);
615 _drawer.draw(str, g, fm, x, y, obs, handles);
618 g.drawLine(x, y + 1, x + width - 1, y + 1);
619 if (originalClip != null)
620 g.setClip(originalClip.x, originalClip.y, originalClip.width, originalClip.height);
626 g.setFont(_fontPlain);
630 private void drawWord(Graphics g, WordItem word, int x, int y, boolean last, FontMetrics plainMetrics, int clipxl,
631 int clipxr, ImageObserver obs, Vector handles) {
632 for (int pos = 0; pos < word.items.length; pos++) {
633 CharacterGroupItem item = word.items[pos];
634 x += drawPart(g, item.info, item.s, x, y, plainMetrics, clipxl, clipxr, obs, handles);
639 * Strip a line from all its color and special codes.
643 * @return stripped line.
645 public String getStripped(String str) {
646 CharacterInfo info = new CharacterInfo();
647 info.frontColor = _cols[1];
648 info.backColor = _cols[0];
649 info.isTransparent = true;
651 while (str.length() > 0) {
652 int pos = str.indexOf(' ');
655 word = decodeWord(info, str, _cols);
658 String wrd = str.substring(0, pos);
659 word = decodeWord(info, wrd + " ", _cols);
660 str = str.substring(pos + 1);
662 if (res.length() > 0)
663 res += " " + word.originalword;
665 res += word.originalword;
670 private boolean isAlphaNum(char c) {
671 if ((c == '(') || (c == ')'))
673 if ((c == '<') || (c == '>'))
675 if ((c == '"') || (c == '"'))
677 if ((c == '{') || (c == '}'))
679 if ((c == '.') || (c == ','))
683 // if(c=='-') return false;
687 private String trimAlphaNum(String s) {
689 while ((index < s.length()) && !isAlphaNum(s.charAt(index)))
691 if (index == s.length())
693 s = s.substring(index);
694 index = s.length() - 1;
695 while ((index >= 0) && !isAlphaNum(s.charAt(index)))
699 s = s.substring(0, index + 1);
703 private void getWordItemWidthHeight(Graphics g, WordItem item, Dimension res) {
706 for (int i = 0; i < item.items.length; i++) {
707 FontMetrics fm = getFontMetrics(g, item.items[i].info);
708 _drawer.getWidthHeight(item.items[i].s, fm, res, this);
718 private void expandLines() {
719 LineItem[] n = new LineItem[_lines.length * 2];
720 System.arraycopy(_lines, 0, n, 0, _lines.length);
721 for (int i = _lines.length; i < n.length; i++)
722 n[i] = new LineItem();
727 * Get the height of the given decoded line.
732 * graphics where the string will be displayed.
738 * true if wrapping must occur.
739 * @return actual height.
741 public int getHeight(DecodedLine str, Graphics g, int x, int wmax, boolean wrap) {
742 WordItem[] words = ((DecodedLineInternal) str).words;
744 Font currFont = _fontPlain;
746 FontMetrics plainFm = g.getFontMetrics();
748 int currentLineLength = 0;
753 for (int i = 0; i < words.length; i++) {
754 WordItem word = words[i];
755 getWordItemWidthHeight(g, word, _tmp);
756 int wordWidth = _tmp.width;
757 if ((w + wordWidth > wmax) && (currentLineLength > 0) && wrap) {
758 w = _drawer.getWidth(" ", plainFm, this);
759 currentLineLength = 0;
763 if (_tmp.height > mh)
768 if (currentLineLength > 0)
774 * Draw the given prepared line.
777 * the prepared line to draw.
779 * the graphics where to draw.
787 * left clip position : drawing doesn't have to be complete left to
790 * right clip position : drawing doens't have to be complete right to
793 * true if word per word analyse must be performed, false otherwise.
794 * If not analyse is requested, the DrawResultItem array in res will
797 * true is wrapping must be done, false otherwise.
799 * analyse report destination for the drawed line.
801 public void draw(DecodedLine str, Graphics g, int left, int right, int y, int clipxl, int clipxr, boolean analyse,
802 boolean wrap, DrawResult res) {
803 WordItem[] words = ((DecodedLineInternal) str).words;
804 if (res.updateHandles == null) {
805 res.updateHandles = new Vector();
807 if (res.updateHandles.size() > 0)
808 res.updateHandles.removeAllElements();
812 Font currFont = _fontPlain;
814 FontMetrics plainFm = g.getFontMetrics();
817 int firstWordInLine = 0;
818 int currentLineLength = 0;
819 int wmax = right - left + 1;
822 for (int i = 0; i < words.length; i++) {
823 WordItem word = words[i];
824 getWordItemWidthHeight(g, word, _tmp);
825 int wordWidth = _tmp.width;
826 if ((w + wordWidth > wmax) && (currentLineLength > 0)) {
827 w = _drawer.getWidth(" ", plainFm, this);
828 LineItem newLine = _lines[lineCount++];
829 if (lineCount == _lines.length)
831 newLine.first = firstWordInLine;
832 newLine.count = currentLineLength;
834 currentLineLength = 0;
840 if (currentLineLength != 0) {
841 LineItem newLine = _lines[lineCount++];
842 if (lineCount == _lines.length)
844 newLine.first = firstWordInLine;
845 newLine.count = currentLineLength;
848 LineItem newLine = _lines[lineCount++];
850 newLine.count = words.length;
857 if ((res.items == null) || (res.items.length != s))
858 res.items = new DrawResultItem[s];
860 DrawResultItem[] dres = res.items;
865 int marginWidth = _drawer.getWidth(" ", plainFm, this);
868 if (_hdirection == RIGHT)
871 if (_vdirection == BOTTOM) {
872 for (int i = lineCount - 1; i >= 0; i--) {
878 px += hdir * marginWidth;
879 LineItem line = _lines[i];
880 for (int j = line.first; j < line.first + line.count; j++) {
882 getWordItemWidthHeight(g, words[j], _tmp);
884 int wordWidth = _tmp.width;
885 int wordHeight = _tmp.height;
890 int trimmedWidth = wordWidth;
891 if (maxHeight < wordHeight)
892 maxHeight = wordHeight;
893 if ((px + wordWidth > clipxl) && (px <= clipxr))
894 drawWord(g, words[j], px, y, j == line.first + line.count - 1, plainFm, clipxl, clipxr, this,
898 // String wrd=words[j].decodedword;
899 String owrd = words[j].originalword;
900 String swrd = words[j].originalstrippedword;
901 String twrd = trimAlphaNum(swrd.trim());
903 dres[j] = new DrawResultItem();
904 DrawResultItem ritem = dres[j];
907 ritem.originalword = owrd;
908 ritem.originalstrippedword = swrd;
909 ritem.rectangle = new StyledRectangle(px, py - wordHeight, trimmedWidth, wordHeight);
924 for (int i = 0; i < dres.length; i++)
925 dres[i].rectangle.y += h;
926 } else if (_vdirection == TOP) {
927 for (int i = 0; i < lineCount; i++) {
933 px += hdir * marginWidth;
934 LineItem line = _lines[i];
935 for (int j = line.first; j < line.first + line.count; j++) {
936 getWordItemWidthHeight(g, words[j], _tmp);
938 int wordWidth = _tmp.width;
939 int wordHeight = _tmp.height;
943 int trimmedWidth = wordWidth;
944 if (maxHeight < wordHeight)
945 maxHeight = wordHeight;
947 if ((px + wordWidth > clipxl) && (px <= clipxr))
948 drawWord(g, words[j], px, y + wordHeight, j == line.first + line.count - 1, plainFm, clipxl, clipxr, this,
952 String wrd = words[j].decodedword;
953 String owrd = words[j].originalword;
954 String swrd = words[j].originalstrippedword;
955 String twrd = trimAlphaNum(wrd.trim());
957 dres[j] = new DrawResultItem();
958 DrawResultItem ritem = dres[j];
961 ritem.originalword = owrd;
962 ritem.originalstrippedword = swrd;
963 ritem.rectangle = new StyledRectangle(px, py, trimmedWidth, wordHeight);
987 for (int i = 0; i < res.items.length; i++)
988 res.items[i].rectangle.x -= x1;
990 if (res.rectangle == null) {
991 res.rectangle = new StyledRectangle(x1, y, x2 - x1 + 1, h);
993 res.rectangle.x = x1;
995 res.rectangle.width = x2 - x1 + 1;
996 res.rectangle.height = h;
1001 public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) {
1002 if ((infoflags & ImageObserver.ERROR) != 0)
1004 if ((infoflags & ImageObserver.ABORT) != 0)
1007 // We don't care about properties, just go ahead...
1008 if ((infoflags & ImageObserver.PROPERTIES) != 0)
1011 if (_listener != null) {
1014 if ((infoflags & ImageObserver.WIDTH) != 0)
1015 what |= FormattedStringDrawerListener.SIZE;
1016 if ((infoflags & ImageObserver.HEIGHT) != 0)
1017 what |= FormattedStringDrawerListener.SIZE;
1018 if ((infoflags & ImageObserver.ALLBITS) != 0)
1019 what |= FormattedStringDrawerListener.DATA;
1020 if ((infoflags & ImageObserver.FRAMEBITS) != 0)
1021 what |= FormattedStringDrawerListener.FRAME;
1022 if ((infoflags & ImageObserver.SOMEBITS) != 0)
1023 what |= FormattedStringDrawerListener.DATA;
1026 b = (Boolean) EventDispatcher.dispatchEventAsyncAndWaitEx(_listener, "displayUpdated", new Object[] { img,
1027 new Integer(what) });
1028 return b.booleanValue();
1029 } catch (Throwable e) {
1030 e.printStackTrace();