4 * License : The MIT License
5 * Copyright(c) 2008 olyutorskii
8 package jp.sfjp.jindolf.glyph;
10 import java.awt.Color;
12 import java.awt.Graphics2D;
13 import java.awt.Point;
14 import java.awt.Polygon;
15 import java.awt.Rectangle;
16 import java.awt.RenderingHints;
17 import java.awt.image.BufferedImage;
18 import java.io.IOException;
19 import java.text.DateFormat;
20 import java.util.LinkedList;
21 import java.util.List;
22 import java.util.regex.Pattern;
23 import jp.sfjp.jindolf.data.Anchor;
24 import jp.sfjp.jindolf.data.Avatar;
25 import jp.sfjp.jindolf.data.DialogPref;
26 import jp.sfjp.jindolf.data.Period;
27 import jp.sfjp.jindolf.data.Talk;
28 import jp.sfjp.jindolf.data.Village;
29 import jp.sfjp.jindolf.util.GUIUtils;
30 import jp.sourceforge.jindolf.corelib.TalkType;
34 * 会話部描画領域は、キャプション部と発言部から構成される。
36 public class TalkDraw extends AbstractTextRow{
39 public static final Color COLOR_PUBLIC = new Color(0xffffff);
41 public static final Color COLOR_WOLFONLY = new Color(0xff7777);
43 public static final Color COLOR_PRIVATE = new Color(0x939393);
45 public static final Color COLOR_GRAVE = new Color(0x9fb7cf);
47 private static final Color COLOR_CAPTIONFG = Color.WHITE;
48 private static final Color COLOR_DIALOGFG = Color.BLACK;
50 private static final Color COLOR_SIMPLEFG = Color.BLACK;
51 private static final Color COLOR_SIMPLEBG = Color.WHITE;
53 private static final int BALOONTIP_WIDTH = 16;
54 private static final int BALOONTIP_HEIGHT = 8;
55 private static final int UPPER_MARGIN = 5;
56 private static final int UNDER_MARGIN = 10;
57 private static final int OFFSET_ANCHOR = 36;
58 private static final int CAPTION_DIALOG_GAP = 3;
60 private static final Color COLOR_TRANS = new Color(0, 0, 0, 0);
61 private static final int BALOON_R = 10;
62 private static final BufferedImage BALOON_PUBLIC;
63 private static final BufferedImage BALOON_WOLFONLY;
64 private static final BufferedImage BALOON_GRAVE;
65 private static final BufferedImage BALOON_PRIVATE;
66 private static final BufferedImage SQUARE_PUBLIC;
67 private static final BufferedImage SQUARE_WOLFONLY;
68 private static final BufferedImage SQUARE_GRAVE;
69 private static final BufferedImage SQUARE_PRIVATE;
71 private static final float ANCHOR_FONT_RATIO = 0.9f;
74 BALOON_PUBLIC = createWedgeImage(COLOR_PUBLIC);
75 BALOON_WOLFONLY = createBubbleImage(COLOR_WOLFONLY);
76 BALOON_PRIVATE = createBubbleImage(COLOR_PRIVATE);
77 BALOON_GRAVE = createBubbleImage(COLOR_GRAVE);
78 SQUARE_PUBLIC = createSquareImage(COLOR_PUBLIC);
79 SQUARE_WOLFONLY = createSquareImage(COLOR_WOLFONLY);
80 SQUARE_GRAVE = createSquareImage(COLOR_GRAVE);
81 SQUARE_PRIVATE = createSquareImage(COLOR_PRIVATE);
85 private final Talk talk;
86 private Anchor showingAnchor;
88 private final GlyphDraw caption;
89 private BufferedImage faceImage;
90 private final GlyphDraw dialog;
91 private final List<AnchorDraw> anchorTalks = new LinkedList<AnchorDraw>();
92 private Point imageOrigin;
93 private Point dialogOrigin;
94 private Point tipOrigin;
95 private int baloonWidth;
96 private int baloonHeight;
98 private FontInfo anchorFontInfo;
99 private DialogPref dialogPref;
106 public TalkDraw(Talk talk){
107 this(talk, new DialogPref(), FontInfo.DEFAULT_FONTINFO);
114 * @param dialogPref 発言表示設定
115 * @param fontInfo フォント設定
117 public TalkDraw(Talk talk, DialogPref dialogPref, FontInfo fontInfo){
121 this.anchorFontInfo = deriveAnchorFontInfo(this.fontInfo);
122 this.dialogPref = dialogPref;
124 this.faceImage = getFaceImage();
125 this.caption = new GlyphDraw(getCaptionString(), this.fontInfo);
126 this.dialog = new GlyphDraw(this.talk.getDialog(), this.fontInfo);
130 Period period = this.talk.getPeriod();
131 List<Anchor> anchorList = Anchor.getAnchorList(this.talk.getDialog(),
133 this.dialog.setAnchorSet(anchorList);
140 * 指定した色で描画したクサビイメージを取得する。
144 private static BufferedImage createWedgeImage(Color color){
146 image = new BufferedImage(BALOONTIP_WIDTH,
148 BufferedImage.TYPE_INT_ARGB);
149 Graphics2D g2 = image.createGraphics();
150 RenderingHints renderHints = GUIUtils.getQualityHints();
151 g2.addRenderingHints(renderHints);
152 g2.setColor(COLOR_TRANS);
153 g2.fillRect(0, 0, BALOONTIP_WIDTH, BALOONTIP_HEIGHT);
155 Polygon poly = new Polygon();
157 poly.addPoint(16, 8);
158 poly.addPoint(16, 0);
159 g2.fillPolygon(poly);
164 * 指定した色で描画した泡イメージを取得する。
168 private static BufferedImage createBubbleImage(Color color){
170 image = new BufferedImage(BALOONTIP_WIDTH,
172 BufferedImage.TYPE_INT_ARGB);
173 Graphics2D g2 = image.createGraphics();
174 RenderingHints renderHints = GUIUtils.getQualityHints();
175 g2.addRenderingHints(renderHints);
176 g2.setColor(COLOR_TRANS);
177 g2.fillRect(0, 0, BALOONTIP_WIDTH, BALOONTIP_HEIGHT);
179 g2.fillOval(2, 4, 4, 4);
180 g2.fillOval(8, 2, 6, 6);
185 * 指定した色で描画した長方形イメージを返す。
189 private static BufferedImage createSquareImage(Color color){
191 image = new BufferedImage(BALOONTIP_WIDTH,
193 BufferedImage.TYPE_INT_ARGB);
194 Graphics2D g2 = image.createGraphics();
195 RenderingHints renderHints = GUIUtils.getQualityHints();
196 g2.addRenderingHints(renderHints);
198 g2.fillRect(0, 0, BALOONTIP_WIDTH, BALOONTIP_HEIGHT);
203 * 会話表示用フォントからアンカー表示用フォントを派生させる。
204 * @param font 派生元フォント
207 private static Font deriveAnchorFont(Font font){
208 float fontSize = font.getSize2D();
209 float newSize = fontSize * ANCHOR_FONT_RATIO;
210 return font.deriveFont(newSize);
214 * 会話表示用フォント設定からアンカー表示用フォント設定を派生させる。
215 * @param info 派生元フォント設定
218 private static FontInfo deriveAnchorFontInfo(FontInfo info){
219 Font newFont = deriveAnchorFont(info.getFont());
220 FontInfo result = info.deriveFont(newFont);
229 public static Color getTypedColor(TalkType type){
233 case PUBLIC: result = TalkDraw.COLOR_PUBLIC; break;
234 case WOLFONLY: result = TalkDraw.COLOR_WOLFONLY; break;
235 case GRAVE: result = TalkDraw.COLOR_GRAVE; break;
236 case PRIVATE: result = TalkDraw.COLOR_PRIVATE; break;
237 default: return null;
246 private void setColorDesign(){
247 if(this.dialogPref.isSimpleMode()){
248 this.caption.setColor(COLOR_SIMPLEFG);
250 this.caption.setColor(COLOR_CAPTIONFG);
253 this.dialog.setColor(COLOR_DIALOGFG);
262 public Talk getTalk(){
270 private BufferedImage getFaceImage(){
271 Village village = this.talk.getPeriod().getVillage();
272 Avatar avatar = this.talk.getAvatar();
274 boolean useBodyImage = this.dialogPref.useBodyImage();
275 boolean useMonoImage = this.dialogPref.useMonoImage();
278 if(this.talk.isGrave()){
281 image = village.getAvatarBodyMonoImage(avatar);
283 image = village.getAvatarFaceMonoImage(avatar);
287 image = village.getGraveBodyImage();
289 image = village.getGraveImage();
294 image = village.getAvatarBodyImage(avatar);
296 image = village.getAvatarFaceImage(avatar);
307 private CharSequence getCaptionString(){
308 StringBuilder result = new StringBuilder();
310 Avatar avatar = this.talk.getAvatar();
312 if(this.talk.hasTalkNo()){
313 result.append(this.talk.getAnchorNotation_G()).append(' ');
315 result.append(avatar.getFullName()).append(' ');
316 result.append(this.talk.getAnchorNotation());
320 DateFormat.getDateTimeInstance(DateFormat.MEDIUM,
322 long epoch = this.talk.getTimeFromID();
323 String decoded = dform.format(epoch);
324 result.append(decoded);
326 int count = this.talk.getTalkCount();
328 TalkType type = this.talk.getTalkType();
329 result.append(" (").append(Talk.encodeColorName(type));
330 result.append('#').append(count).append(')');
333 int charNum = this.talk.getTotalChars();
335 result.append(' ').append(charNum).append('字');
345 protected Color getTalkBgColor(){
346 if(this.dialogPref.isSimpleMode()) return COLOR_SIMPLEBG;
348 TalkType type = this.talk.getTalkType();
349 Color result = getTypedColor(type);
356 * @return {@inheritDoc}
359 public Rectangle recalcBounds(){
360 int newWidth = getWidth();
364 if( ! this.dialogPref.isSimpleMode()){
365 imageWidth = this.faceImage.getWidth(null);
366 imageHeight = this.faceImage.getHeight(null);
369 int tipWidth = BALOON_WOLFONLY.getWidth();
372 int minWidth = imageWidth + tipWidth + BALOON_R * 2;
373 if(newWidth < minWidth) modWidth = minWidth;
374 else modWidth = newWidth;
376 this.caption.setWidth(modWidth);
377 int captionWidth = this.caption.getWidth();
378 int captionHeight = this.caption.getHeight() + CAPTION_DIALOG_GAP;
380 this.dialog.setWidth(modWidth - minWidth);
381 int dialogWidth = this.dialog.getWidth();
382 int dialogHeight = this.dialog.getHeight();
384 if(this.dialogPref.alignBaloonWidth()){
385 this.baloonWidth = (modWidth - minWidth) + BALOON_R * 2;
387 this.baloonWidth = dialogWidth + BALOON_R * 2;
389 this.baloonHeight = dialogHeight + BALOON_R * 2;
391 int imageAndDialogWidth = imageWidth + tipWidth + this.baloonWidth;
393 int totalWidth = Math.max(captionWidth, imageAndDialogWidth);
395 int totalHeight = captionHeight;
396 totalHeight += Math.max(imageHeight, this.baloonHeight);
398 int imageYpos = captionHeight;
399 int dialogYpos = captionHeight;
400 int tipYpos = captionHeight;
401 if(imageHeight < this.baloonHeight){
402 imageYpos += (this.baloonHeight - imageHeight) / 2;
403 tipYpos += (this.baloonHeight - BALOON_WOLFONLY.getHeight()) / 2;
404 dialogYpos += BALOON_R;
406 dialogYpos += (imageHeight - this.baloonHeight) / 2 + BALOON_R;
407 tipYpos += (imageHeight - BALOON_WOLFONLY.getHeight()) / 2;
410 this.imageOrigin = new Point(0, imageYpos);
411 this.caption.setPos(this.bounds.x + 0, this.bounds.y + 0);
413 new Point(imageWidth+tipWidth+BALOON_R, dialogYpos);
414 this.dialog.setPos(this.bounds.x + imageWidth+tipWidth+BALOON_R,
415 this.bounds.y + dialogYpos);
416 this.tipOrigin = new Point(imageWidth, tipYpos);
418 for(AnchorDraw anchorDraw : this.anchorTalks){
419 anchorDraw.setWidth(modWidth - OFFSET_ANCHOR);
420 totalHeight += anchorDraw.getHeight();
423 if( this.dialogPref.isSimpleMode()
424 || this.dialogPref.alignBaloonWidth() ){
425 this.bounds.width = newWidth;
427 this.bounds.width = totalWidth;
429 this.bounds.height = UPPER_MARGIN + totalHeight + UNDER_MARGIN;
436 * @param xPos {@inheritDoc}
437 * @param yPos {@inheritDoc}
440 public void setPos(int xPos, int yPos){
441 super.setPos(xPos, yPos);
442 this.caption.setPos(this.bounds.x, this.bounds.y + UPPER_MARGIN);
443 this.dialog.setPos(this.bounds.x + this.dialogOrigin.x,
444 this.bounds.y + this.dialogOrigin.y
450 * アイコンイメージとフキダシを繋ぐ補助イメージを返す。
453 private BufferedImage getTipImage(){
456 TalkType type = this.talk.getTalkType();
458 if(this.dialogPref.isSimpleMode()){
460 case PUBLIC: tip = SQUARE_PUBLIC; break;
461 case WOLFONLY: tip = SQUARE_WOLFONLY; break;
462 case GRAVE: tip = SQUARE_GRAVE; break;
463 case PRIVATE: tip = SQUARE_PRIVATE; break;
471 case PUBLIC: tip = BALOON_PUBLIC; break;
472 case WOLFONLY: tip = BALOON_WOLFONLY; break;
473 case GRAVE: tip = BALOON_GRAVE; break;
474 case PRIVATE: tip = BALOON_PRIVATE; break;
487 * @param g {@inheritDoc}
490 public void paint(Graphics2D g){
491 final int xPos = this.bounds.x;
492 final int yPos = this.bounds.y + UPPER_MARGIN;
494 this.caption.paint(g);
496 if(this.dialogPref.isSimpleMode() ){
497 g.drawLine(xPos, this.bounds.y,
498 xPos + this.bounds.width, this.bounds.y );
500 g.drawImage(this.faceImage,
501 xPos + this.imageOrigin.x,
502 yPos + this.imageOrigin.y,
506 BufferedImage tip = getTipImage();
508 xPos + this.tipOrigin.x,
509 yPos + this.tipOrigin.y,
512 g.setColor(getTalkBgColor());
514 xPos + this.dialogOrigin.x - BALOON_R,
515 yPos + this.dialogOrigin.y - BALOON_R,
521 this.dialog.paint(g);
523 int anchorX = xPos + OFFSET_ANCHOR;
524 int anchorY = yPos + this.dialogOrigin.y + baloonHeight;
526 for(AnchorDraw anchorDraw : this.anchorTalks){
527 anchorDraw.setPos(anchorX, anchorY);
529 anchorY += anchorDraw.getHeight();
537 * @param fontInfo {@inheritDoc}
540 public void setFontInfo(FontInfo fontInfo){
541 super.setFontInfo(fontInfo);
543 this.anchorFontInfo = deriveAnchorFontInfo(this.fontInfo);
545 this.caption.setFontInfo(this.fontInfo);
546 this.dialog .setFontInfo(this.fontInfo);
548 for(AnchorDraw anchorDraw : this.anchorTalks){
549 anchorDraw.setFontInfo(this.anchorFontInfo);
561 public void setDialogPref(DialogPref pref){
562 this.dialogPref = pref;
563 this.faceImage = getFaceImage();
565 for(AnchorDraw anchorDraw : this.anchorTalks){
566 anchorDraw.setDialogPref(this.dialogPref);
577 * @param from {@inheritDoc}
578 * @param to {@inheritDoc}
581 public void drag(Point from, Point to){
582 this.caption.drag(from, to);
583 this.dialog.drag(from, to);
584 for(AnchorDraw anchorDraw : this.anchorTalks){
585 anchorDraw.drag(from, to);
592 * @param appendable {@inheritDoc}
593 * @return {@inheritDoc}
594 * @throws java.io.IOException {@inheritDoc}
597 public Appendable appendSelected(Appendable appendable)
599 this.caption.appendSelected(appendable);
600 this.dialog .appendSelected(appendable);
602 for(AnchorDraw anchorDraw : this.anchorTalks){
603 anchorDraw.appendSelected(appendable);
613 public void clearSelect(){
614 this.caption.clearSelect();
615 this.dialog.clearSelect();
616 for(AnchorDraw anchorDraw : this.anchorTalks){
617 anchorDraw.clearSelect();
623 * 与えられた座標にアンカー文字列が存在すればAnchorを返す。
627 public Anchor getAnchor(Point pt){
628 Anchor result = this.dialog.getAnchor(pt);
634 * アンカーにnullを指定すればアンカー表示は非表示となる。
636 * @param talkList アンカーの示す一連のTalk
638 public void showAnchorTalks(Anchor anchor, List<Talk> talkList){
639 if(anchor == null || this.showingAnchor == anchor){
640 this.showingAnchor = null;
641 this.anchorTalks.clear();
646 this.showingAnchor = anchor;
648 this.anchorTalks.clear();
649 for(Talk anchorTalk : talkList){
650 AnchorDraw anchorDraw =
651 new AnchorDraw(anchorTalk,
653 this.anchorFontInfo );
654 this.anchorTalks.add(anchorDraw);
663 * 与えられた座標に検索マッチ文字列があればそのインデックスを返す。
665 * @return 検索マッチインデックス
667 public int getRegexMatchIndex(Point pt){
668 int index = this.dialog.getRegexMatchIndex(pt);
674 * @param searchRegex パターン
677 public int setRegex(Pattern searchRegex){
680 total += this.dialog.setRegex(searchRegex);
682 for(AnchorDraw anchorDraw : this.anchorTalks){
683 total += anchorDraw.setRegex(searchRegex);
685 */ // TODO よくわからんので保留
691 * @return 検索ハイライトインデックス。見つからなければ-1。
693 public int getHotTargetIndex(){
694 return this.dialog.getHotTargetIndex();
699 * @param index ハイライトインデックス。負ならハイライト全クリア。
701 public void setHotTargetIndex(int index){
702 this.dialog.setHotTargetIndex(index);
710 public int getRegexMatches(){
711 return this.dialog.getRegexMatches();
715 * 特別な検索ハイライト描画をクリアする。
717 public void clearHotTarget(){
718 this.dialog.clearHotTarget();
723 * 特別な検索ハイライト領域の寸法を返す。
726 public Rectangle getHotTargetRectangle(){
727 return this.dialog.getHotTargetRectangle();