4 * License : The MIT License
\r
5 * Copyright(c) 2008 olyutorskii
\r
8 package jp.sourceforge.jindolf;
\r
10 import java.awt.Color;
\r
11 import java.awt.Component;
\r
12 import java.awt.Dimension;
\r
13 import java.awt.Graphics;
\r
14 import java.awt.Graphics2D;
\r
15 import java.awt.Insets;
\r
16 import java.awt.Point;
\r
17 import java.awt.Rectangle;
\r
18 import java.awt.RenderingHints;
\r
19 import java.awt.event.ActionEvent;
\r
20 import java.awt.event.ActionListener;
\r
21 import java.awt.event.ComponentEvent;
\r
22 import java.awt.event.ComponentListener;
\r
23 import java.awt.event.MouseEvent;
\r
24 import java.awt.font.FontRenderContext;
\r
25 import java.io.IOException;
\r
26 import java.util.EventListener;
\r
27 import java.util.LinkedList;
\r
28 import java.util.List;
\r
29 import java.util.ListIterator;
\r
30 import java.util.regex.Pattern;
\r
31 import javax.swing.AbstractAction;
\r
32 import javax.swing.Action;
\r
33 import javax.swing.ActionMap;
\r
34 import javax.swing.InputMap;
\r
35 import javax.swing.JComponent;
\r
36 import javax.swing.JMenuItem;
\r
37 import javax.swing.JPopupMenu;
\r
38 import javax.swing.JTextField;
\r
39 import javax.swing.KeyStroke;
\r
40 import javax.swing.Scrollable;
\r
41 import javax.swing.SwingConstants;
\r
42 import javax.swing.event.EventListenerList;
\r
43 import javax.swing.event.MouseInputListener;
\r
44 import javax.swing.text.DefaultEditorKit;
\r
49 * 表示に影響する要因は、Periodの中身、LayoutManagerによるサイズ変更、
\r
50 * フォント属性の指定、フィルタリング操作、ドラッギングによる文字列選択操作、
\r
51 * 文字列検索および検索ナビゲーション。
\r
53 @SuppressWarnings("serial")
\r
54 public class Discussion extends JComponent
\r
55 implements Scrollable, MouseInputListener, ComponentListener{
\r
57 private static final Color COLOR_NORMALBG = Color.BLACK;
\r
58 private static final Color COLOR_SIMPLEBG = Color.WHITE;
\r
60 private static final int MARGINTOP = 50;
\r
61 private static final int MARGINBOTTOM = 100;
\r
63 private Period period;
\r
64 private final List<TextRow> rowList = new LinkedList<TextRow>();
\r
65 private final List<TalkDraw> talkDrawList = new LinkedList<TalkDraw>();
\r
67 private TopicFilter topicFilter;
\r
68 private TopicFilter.FilterContext filterContext;
\r
69 private RegexPattern regexPattern;
\r
71 private Point dragFrom;
\r
73 private FontInfo fontInfo;
\r
74 private final RenderingHints hints = new RenderingHints(null);
\r
76 private DialogPref dialogPref;
\r
78 private Dimension idealSize;
\r
79 private int lastWidth = -1;
\r
81 private final DiscussionPopup popup = new DiscussionPopup();
\r
83 private final EventListenerList thisListenerList =
\r
84 new EventListenerList();
\r
86 private final Action copySelectedAction =
\r
87 new ProxyAction(ActionManager.CMD_COPY);
\r
92 public Discussion(){
\r
95 this.fontInfo = FontInfo.DEFAULT_FONTINFO;
\r
96 this.dialogPref = new DialogPref();
\r
98 this.hints.put(RenderingHints.KEY_ANTIALIASING,
\r
99 RenderingHints.VALUE_ANTIALIAS_ON);
\r
100 this.hints.put(RenderingHints.KEY_RENDERING,
\r
101 RenderingHints.VALUE_RENDER_QUALITY);
\r
102 updateRenderingHints();
\r
106 addMouseListener(this);
\r
107 addMouseMotionListener(this);
\r
108 addComponentListener(this);
\r
110 setComponentPopupMenu(this.popup);
\r
113 ActionMap actionMap = getActionMap();
\r
114 actionMap.put(DefaultEditorKit.copyAction, this.copySelectedAction);
\r
123 * FontRenderContextが更新された後は必ず呼び出す必要がある。
\r
125 private void updateRenderingHints(){
\r
126 Object textAliaseValue;
\r
127 FontRenderContext context = this.fontInfo.getFontRenderContext();
\r
128 if(context.isAntiAliased()){
\r
129 textAliaseValue = RenderingHints.VALUE_TEXT_ANTIALIAS_ON;
\r
131 textAliaseValue = RenderingHints.VALUE_TEXT_ANTIALIAS_OFF;
\r
133 this.hints.put(RenderingHints.KEY_TEXT_ANTIALIASING,
\r
136 Object textFractionalValue;
\r
137 if(context.usesFractionalMetrics()){
\r
138 textFractionalValue = RenderingHints.VALUE_FRACTIONALMETRICS_ON;
\r
140 textFractionalValue = RenderingHints.VALUE_FRACTIONALMETRICS_OFF;
\r
142 this.hints.put(RenderingHints.KEY_FRACTIONALMETRICS,
\r
143 textFractionalValue);
\r
151 private void setColorDesign(){
\r
153 if(this.dialogPref.isSimpleMode()){
\r
154 fgColor = COLOR_SIMPLEBG;
\r
156 fgColor = COLOR_NORMALBG;
\r
159 setForeground(fgColor);
\r
167 * @param newFontInfo フォント設定
\r
169 public void setFontInfo(FontInfo newFontInfo){
\r
170 this.fontInfo = newFontInfo;
\r
172 updateRenderingHints();
\r
174 for(TextRow row : this.rowList){
\r
175 row.setFontInfo(this.fontInfo);
\r
189 * @param newPref 発言表示設定
\r
191 public void setDialogPref(DialogPref newPref){
\r
192 this.dialogPref = newPref;
\r
194 for(TextRow row : this.rowList){
\r
195 if(row instanceof TalkDraw){
\r
196 TalkDraw talkDraw = (TalkDraw) row;
\r
197 talkDraw.setDialogPref(this.dialogPref);
\r
198 }else if(row instanceof SysEventDraw){
\r
199 SysEventDraw sysDraw = (SysEventDraw) row;
\r
200 sysDraw.setDialogPref(this.dialogPref);
\r
215 * @return 現在のPeriod
\r
217 public Period getPeriod(){
\r
218 return this.period;
\r
223 * 新しいPeriodの表示内容はまだ反映されない。
\r
224 * @param period 新しいPeriod
\r
226 public final void setPeriod(Period period){
\r
227 if(period == null){
\r
228 this.period = null;
\r
229 this.rowList.clear();
\r
230 this.talkDrawList.clear();
\r
234 if( this.period == period
\r
235 && period.getTopics() == this.rowList.size() ){
\r
240 this.period = period;
\r
242 this.filterContext = null;
\r
244 this.rowList.clear();
\r
245 this.talkDrawList.clear();
\r
246 for(Topic topic : this.period.getTopicList()){
\r
248 if(topic instanceof Talk){
\r
249 Talk talk = (Talk) topic;
\r
250 TalkDraw talkDraw = new TalkDraw(talk,
\r
253 this.talkDrawList.add(talkDraw);
\r
255 }else if(topic instanceof SysEvent){
\r
256 SysEvent sysEvent = (SysEvent) topic;
\r
257 row = new SysEventDraw(sysEvent,
\r
264 this.rowList.add(row);
\r
278 * @param filter 発言フィルタ
\r
280 public void setTopicFilter(TopicFilter filter){
\r
281 this.topicFilter = filter;
\r
289 public void filtering(){
\r
290 if( this.topicFilter != null
\r
291 && this.topicFilter.isSame(this.filterContext)){
\r
295 if(this.topicFilter != null){
\r
296 this.filterContext = this.topicFilter.getFilterContext();
\r
298 this.filterContext = null;
\r
313 public RegexPattern getRegexPattern(){
\r
314 return this.regexPattern;
\r
318 * 与えられた正規表現にマッチする文字列をハイライト描画する。
\r
319 * @param newPattern 検索パターン
\r
322 public int setRegexPattern(RegexPattern newPattern){
\r
323 this.regexPattern = newPattern;
\r
329 Pattern pattern = null;
\r
330 if(this.regexPattern != null){
\r
331 pattern = this.regexPattern.getPattern();
\r
334 for(TalkDraw talkDraw : this.talkDrawList){
\r
335 total += talkDraw.setRegex(pattern);
\r
344 * 検索結果の次候補をハイライト表示する。
\r
346 public void nextHotTarget(){
\r
347 TalkDraw oldTalk = null;
\r
349 TalkDraw newTalk = null;
\r
351 TalkDraw firstTalk = null;
\r
353 boolean findOld = true;
\r
354 for(TalkDraw talkDraw : this.talkDrawList){
\r
355 int matches = talkDraw.getRegexMatches();
\r
356 if(firstTalk == null && matches > 0){
\r
357 firstTalk = talkDraw;
\r
360 int index = talkDraw.getHotTargetIndex();
\r
361 if(index < 0) continue;
\r
362 oldTalk = talkDraw;
\r
364 scrollRectWithMargin(talkDraw.getHotTargetRectangle());
\r
365 if(oldIndex < matches - 1 && ! isFiltered(talkDraw) ){
\r
366 newTalk = talkDraw;
\r
367 newIndex = oldIndex + 1;
\r
372 if(isFiltered(talkDraw)) continue;
\r
373 if(matches <= 0) continue;
\r
374 newTalk = talkDraw;
\r
380 Rectangle showRect = null;
\r
381 if(oldTalk == null && firstTalk != null){
\r
382 firstTalk.setHotTargetIndex(0);
\r
383 showRect = firstTalk.getHotTargetRectangle();
\r
384 }else if( oldTalk != null
\r
385 && newTalk != null){
\r
386 oldTalk.clearHotTarget();
\r
387 newTalk.setHotTargetIndex(newIndex);
\r
388 showRect = newTalk.getHotTargetRectangle();
\r
391 if(showRect != null){
\r
392 scrollRectWithMargin(showRect);
\r
401 * 検索結果の前候補をハイライト表示する。
\r
403 public void prevHotTarget(){
\r
404 TalkDraw oldTalk = null;
\r
406 TalkDraw newTalk = null;
\r
408 TalkDraw firstTalk = null;
\r
410 boolean findOld = true;
\r
411 int size = this.talkDrawList.size();
\r
412 ListIterator<TalkDraw> iterator =
\r
413 this.talkDrawList.listIterator(size);
\r
414 while(iterator.hasPrevious()){
\r
415 TalkDraw talkDraw = iterator.previous();
\r
416 int matches = talkDraw.getRegexMatches();
\r
417 if(firstTalk == null && matches > 0){
\r
418 firstTalk = talkDraw;
\r
421 int index = talkDraw.getHotTargetIndex();
\r
422 if(index < 0) continue;
\r
423 oldTalk = talkDraw;
\r
425 scrollRectWithMargin(talkDraw.getHotTargetRectangle());
\r
426 if(oldIndex > 0 && ! isFiltered(talkDraw) ){
\r
427 newTalk = talkDraw;
\r
428 newIndex = oldIndex - 1;
\r
433 if(isFiltered(talkDraw)) continue;
\r
434 if(matches <= 0) continue;
\r
435 newTalk = talkDraw;
\r
436 newIndex = matches - 1;
\r
441 Rectangle showRect = null;
\r
442 if(oldTalk == null && firstTalk != null){
\r
443 int matches = firstTalk.getRegexMatches();
\r
444 firstTalk.setHotTargetIndex(matches - 1);
\r
445 showRect = firstTalk.getHotTargetRectangle();
\r
446 }else if( oldTalk != null
\r
447 && newTalk != null){
\r
448 oldTalk.clearHotTarget();
\r
449 newTalk.setHotTargetIndex(newIndex);
\r
450 showRect = newTalk.getHotTargetRectangle();
\r
453 if(showRect != null){
\r
454 scrollRectWithMargin(showRect);
\r
463 * 検索結果の特殊ハイライト表示を解除。
\r
465 public void clearHotTarget(){
\r
466 for(TalkDraw talkDraw : this.talkDrawList){
\r
467 talkDraw.clearHotTarget();
\r
474 * 指定した領域に若干の上下マージンを付けて
\r
475 * スクロールウィンドウに表示させる。
\r
476 * @param rectangle 指定領域
\r
478 private void scrollRectWithMargin(Rectangle rectangle){
\r
479 Rectangle show = new Rectangle(rectangle);
\r
480 show.y -= MARGINTOP;
\r
481 show.height += MARGINTOP + MARGINBOTTOM;
\r
483 scrollRectToVisible(show);
\r
491 private void clearSizeCache(){
\r
492 this.idealSize = null;
\r
493 this.lastWidth = -1;
\r
499 * 指定した矩形がフィルタリング対象か判定する。
\r
501 * @return フィルタリング対象ならtrue
\r
503 private boolean isFiltered(TextRow row){
\r
504 if(this.topicFilter == null) return false;
\r
507 if(row instanceof TalkDraw){
\r
508 topic = ((TalkDraw)row).getTalk();
\r
509 }else if(row instanceof SysEventDraw){
\r
510 topic = ((SysEventDraw)row).getSysEvent();
\r
515 return this.topicFilter.isFiltered(topic);
\r
519 * フィルタリング指定に従いTextRowを表示するか否か設定する。
\r
521 private void filterTopics(){
\r
522 for(TextRow row : this.rowList){
\r
523 if(isFiltered(row)) row.setVisible(false);
\r
524 else row.setVisible(true);
\r
531 * 全子TextRowがリサイズされる。
\r
532 * @param width コンポーネント幅
\r
534 private void setWidth(int width){
\r
535 this.lastWidth = width;
\r
536 Insets insets = getInsets();
\r
537 int rowWidth = width - (insets.left + insets.right);
\r
538 for(TextRow row : this.rowList){
\r
539 row.setWidth(rowWidth);
\r
548 * 子TextRowの縦位置レイアウトを行う。
\r
550 * TextRowは必要に応じて移動させられるがリサイズされることはない。
\r
552 private void layoutVertical(){
\r
553 Rectangle unionRect = null;
\r
554 Insets insets = getInsets();
\r
555 int vertPos = insets.top;
\r
557 for(TextRow row : this.rowList){
\r
558 if( ! row.isVisible() ) continue;
\r
560 row.setPos(insets.left, vertPos);
\r
561 Rectangle rowBound = row.getBounds();
\r
562 vertPos += rowBound.height;
\r
564 if(unionRect == null){
\r
565 unionRect = new Rectangle(rowBound);
\r
567 unionRect.add(rowBound);
\r
571 if(unionRect == null){
\r
572 unionRect = new Rectangle(insets.left, insets.top, 0, 0);
\r
575 if(this.idealSize == null){
\r
576 this.idealSize = new Dimension();
\r
579 int newWidth = insets.left + unionRect.width + insets.right;
\r
580 int newHeight = insets.top + unionRect.height + insets.bottom;
\r
582 this.idealSize.setSize(newWidth, newHeight);
\r
584 setPreferredSize(this.idealSize);
\r
593 * Rowsの縦位置を再レイアウトする。
\r
595 public void layoutRows(){
\r
596 int width = getWidth();
\r
603 * @param g {@inheritDoc}
\r
606 public void paintComponent(Graphics g){
\r
607 Graphics2D g2 = (Graphics2D) g;
\r
608 g2.setRenderingHints(this.hints);
\r
610 Rectangle clipRect = g2.getClipBounds();
\r
611 g2.fillRect(clipRect.x, clipRect.y, clipRect.width, clipRect.height);
\r
613 for(TextRow row : this.rowList){
\r
614 if( ! row.isVisible() ) continue;
\r
616 Rectangle rowRect = row.getBounds();
\r
617 if( ! rowRect.intersects(clipRect) ) continue;
\r
627 * @return {@inheritDoc}
\r
630 public Dimension getPreferredScrollableViewportSize(){
\r
631 return getPreferredSize();
\r
636 * @return {@inheritDoc}
\r
639 public boolean getScrollableTracksViewportWidth(){
\r
645 * @return {@inheritDoc}
\r
648 public boolean getScrollableTracksViewportHeight(){
\r
654 * @param visibleRect {@inheritDoc}
\r
655 * @param orientation {@inheritDoc}
\r
656 * @param direction {@inheritDoc}
\r
657 * @return {@inheritDoc}
\r
660 public int getScrollableBlockIncrement(Rectangle visibleRect,
\r
663 if(orientation == SwingConstants.VERTICAL){
\r
664 return visibleRect.height;
\r
666 return 30; // TODO フォント高 × 1.5 ぐらい?
\r
671 * @param visibleRect {@inheritDoc}
\r
672 * @param orientation {@inheritDoc}
\r
673 * @param direction {@inheritDoc}
\r
674 * @return {@inheritDoc}
\r
677 public int getScrollableUnitIncrement(Rectangle visibleRect,
\r
684 * 任意の発言の表示が占める画面領域を返す。
\r
685 * 発言がフィルタリング対象の時はnullを返す。
\r
689 public Rectangle getTalkBounds(Talk talk){
\r
690 if( this.topicFilter != null
\r
691 && this.topicFilter.isFiltered(talk)) return null;
\r
693 for(TalkDraw talkDraw : this.talkDrawList){
\r
694 if(talkDraw.getTalk() == talk){
\r
695 Rectangle rect = talkDraw.getBounds();
\r
705 * @param from ドラッグ開始位置
\r
706 * @param to 現在のドラッグ位置
\r
708 private void drag(Point from, Point to){
\r
709 Rectangle dragRegion = new Rectangle();
\r
710 dragRegion.setFrameFromDiagonal(from, to);
\r
712 for(TextRow row : this.rowList){
\r
713 if(isFiltered(row)) continue;
\r
714 if( ! row.getBounds().intersects(dragRegion) ) continue;
\r
715 row.drag(from, to);
\r
724 private void clearSelect(){
\r
725 for(TextRow row : this.rowList){
\r
733 * 与えられた点座標を包含する発言を返す。
\r
734 * @param pt 点座標(JComponent基準)
\r
735 * @return 点座標を含む発言。含む発言がなければnullを返す。
\r
738 private TalkDraw getHittedTalkDraw(Point pt){
\r
739 for(TalkDraw talkDraw : this.talkDrawList){
\r
740 if(isFiltered(talkDraw)) continue;
\r
741 Rectangle bounds = talkDraw.getBounds();
\r
742 if(bounds.contains(pt)) return talkDraw;
\r
749 * @param pt クリックポイント
\r
751 private void hitAnchor(Point pt){
\r
752 TalkDraw talkDraw = getHittedTalkDraw(pt);
\r
753 if(talkDraw == null) return;
\r
755 Anchor anchor = talkDraw.getAnchor(pt);
\r
756 if(anchor == null) return;
\r
758 for(AnchorHitListener listener : getAnchorHitListeners()){
\r
759 AnchorHitEvent event =
\r
760 new AnchorHitEvent(this, talkDraw, anchor, pt);
\r
761 listener.anchorHitted(event);
\r
768 * 検索マッチ文字列クリック動作の処理。
\r
769 * @param pt クリックポイント
\r
771 private void hitRegex(Point pt){
\r
772 TalkDraw talkDraw = getHittedTalkDraw(pt);
\r
773 if(talkDraw == null) return;
\r
775 int index = talkDraw.getRegexMatchIndex(pt);
\r
776 if(index < 0) return;
\r
779 talkDraw.setHotTargetIndex(index);
\r
787 * MouseInputListenerを参照せよ。
\r
788 * @param event {@inheritDoc}
\r
792 public void mouseClicked(MouseEvent event){
\r
793 Point pt = event.getPoint();
\r
794 if(event.getButton() == MouseEvent.BUTTON1){
\r
804 * @param event {@inheritDoc}
\r
807 public void mouseEntered(MouseEvent event){
\r
808 // TODO ここでキーボードフォーカス処理が必要?
\r
814 * @param event {@inheritDoc}
\r
817 public void mouseExited(MouseEvent event){
\r
824 * @param event {@inheritDoc}
\r
827 public void mousePressed(MouseEvent event){
\r
828 requestFocusInWindow();
\r
830 if(event.getButton() == MouseEvent.BUTTON1){
\r
832 this.dragFrom = event.getPoint();
\r
841 * @param event {@inheritDoc}
\r
844 public void mouseReleased(MouseEvent event){
\r
845 if(event.getButton() == MouseEvent.BUTTON1){
\r
846 this.dragFrom = null;
\r
854 * @param event {@inheritDoc}
\r
856 // TODO ドラッグ範囲がビューポートを超えたら自動的にスクロールしてほしい。
\r
858 public void mouseDragged(MouseEvent event){
\r
859 if(this.dragFrom == null) return;
\r
860 Point dragTo = event.getPoint();
\r
861 drag(this.dragFrom, dragTo);
\r
867 * @param event {@inheritDoc}
\r
870 public void mouseMoved(MouseEvent event){
\r
876 * @param event {@inheritDoc}
\r
879 public void componentShown(ComponentEvent event){
\r
885 * @param event {@inheritDoc}
\r
888 public void componentHidden(ComponentEvent event){
\r
894 * @param event {@inheritDoc}
\r
897 public void componentMoved(ComponentEvent event){
\r
903 * @param event {@inheritDoc}
\r
906 public void componentResized(ComponentEvent event){
\r
907 int width = getWidth();
\r
908 int height = getHeight();
\r
909 if(width != this.lastWidth){
\r
912 if( this.idealSize.width != width
\r
913 || this.idealSize.height != height ){
\r
923 public CharSequence getSelected(){
\r
924 StringBuilder selected = new StringBuilder();
\r
926 for(TextRow row : this.rowList){
\r
927 if(isFiltered(row)) continue;
\r
929 row.appendSelected(selected);
\r
930 }catch(IOException e){
\r
931 assert false; // ありえない
\r
936 if(selected.length() <= 0) return null;
\r
942 * 選択文字列をクリップボードにコピーする。
\r
945 public CharSequence copySelected(){
\r
946 CharSequence selected = getSelected();
\r
947 if(selected == null) return null;
\r
948 ClipboardAction.copyToClipboard(selected);
\r
953 * 矩形の示す一発言をクリップボードにコピーする。
\r
956 public CharSequence copyTalk(){
\r
957 TalkDraw talkDraw = this.popup.lastPopupedTalkDraw;
\r
958 if(talkDraw == null) return null;
\r
959 Talk talk = talkDraw.getTalk();
\r
961 StringBuilder selected = new StringBuilder();
\r
963 Avatar avatar = talk.getAvatar();
\r
964 selected.append(avatar.getName()).append(' ');
\r
966 String anchor = talk.getAnchorNotation();
\r
967 selected.append(anchor);
\r
968 if(talk.hasTalkNo()){
\r
969 selected.append(' ').append(talk.getAnchorNotation_G());
\r
971 selected.append('\n');
\r
973 selected.append(talk.getDialog());
\r
974 if(selected.charAt(selected.length() - 1) != '\n'){
\r
975 selected.append('\n');
\r
978 ClipboardAction.copyToClipboard(selected);
\r
984 * ポップアップメニュートリガ座標に発言があればそれを返す。
\r
987 public Talk getPopupedTalk(){
\r
988 TalkDraw talkDraw = this.popup.lastPopupedTalkDraw;
\r
989 if(talkDraw == null) return null;
\r
990 Talk talk = talkDraw.getTalk();
\r
995 * ポップアップメニュートリガ座標にアンカーがあればそれを返す。
\r
998 public Anchor getPopupedAnchor(){
\r
999 return this.popup.lastPopupedAnchor;
\r
1006 public void updateUI(){
\r
1008 this.popup.updateUI();
\r
1016 * COPY処理を行うキーの設定をJTextFieldから流用する。
\r
1017 * おそらくはCtrl-C。MacならCommand-Cかも。
\r
1019 private void updateInputMap(){
\r
1020 InputMap thisInputMap = getInputMap();
\r
1022 InputMap sampleInputMap;
\r
1023 sampleInputMap = new JTextField().getInputMap();
\r
1024 KeyStroke[] strokes = sampleInputMap.allKeys();
\r
1025 for(KeyStroke stroke : strokes){
\r
1026 Object bind = sampleInputMap.get(stroke);
\r
1027 if(bind.equals(DefaultEditorKit.copyAction)){
\r
1028 thisInputMap.put(stroke, DefaultEditorKit.copyAction);
\r
1036 * ActionListenerを追加する。
\r
1037 * @param listener リスナー
\r
1039 public void addActionListener(ActionListener listener){
\r
1040 this.thisListenerList.add(ActionListener.class, listener);
\r
1042 this.popup.menuCopy .addActionListener(listener);
\r
1043 this.popup.menuSelTalk .addActionListener(listener);
\r
1044 this.popup.menuJumpAnchor .addActionListener(listener);
\r
1045 this.popup.menuWebTalk .addActionListener(listener);
\r
1046 this.popup.menuSummary .addActionListener(listener);
\r
1052 * ActionListenerを削除する。
\r
1053 * @param listener リスナー
\r
1055 public void removeActionListener(ActionListener listener){
\r
1056 this.thisListenerList.remove(ActionListener.class, listener);
\r
1058 this.popup.menuCopy .removeActionListener(listener);
\r
1059 this.popup.menuSelTalk .removeActionListener(listener);
\r
1060 this.popup.menuJumpAnchor .removeActionListener(listener);
\r
1061 this.popup.menuWebTalk .removeActionListener(listener);
\r
1062 this.popup.menuSummary .removeActionListener(listener);
\r
1068 * ActionListenerを列挙する。
\r
1069 * @return すべてのActionListener
\r
1071 public ActionListener[] getActionListeners(){
\r
1072 return this.thisListenerList.getListeners(ActionListener.class);
\r
1076 * AnchorHitListenerを追加する。
\r
1077 * @param listener リスナー
\r
1079 public void addAnchorHitListener(AnchorHitListener listener){
\r
1080 this.thisListenerList.add(AnchorHitListener.class, listener);
\r
1085 * AnchorHitListenerを削除する。
\r
1086 * @param listener リスナー
\r
1088 public void removeAnchorHitListener(AnchorHitListener listener){
\r
1089 this.thisListenerList.remove(AnchorHitListener.class, listener);
\r
1094 * AnchorHitListenerを列挙する。
\r
1095 * @return すべてのAnchorHitListener
\r
1097 public AnchorHitListener[] getAnchorHitListeners(){
\r
1098 return this.thisListenerList.getListeners(AnchorHitListener.class);
\r
1103 * @param <T> {@inheritDoc}
\r
1104 * @param listenerType {@inheritDoc}
\r
1105 * @return {@inheritDoc}
\r
1108 public <T extends EventListener> T[] getListeners(Class<T> listenerType){
\r
1110 result = this.thisListenerList.getListeners(listenerType);
\r
1112 if(result.length <= 0){
\r
1113 result = super.getListeners(listenerType);
\r
1120 * キーボード入力用ダミーAction。
\r
1122 private class ProxyAction extends AbstractAction{
\r
1124 private final String command;
\r
1128 * @param command コマンド
\r
1129 * @throws NullPointerException 引数がnull
\r
1131 public ProxyAction(String command) throws NullPointerException{
\r
1133 if(command == null) throw new NullPointerException();
\r
1134 this.command = command;
\r
1140 * @param event {@inheritDoc}
\r
1143 public void actionPerformed(ActionEvent event){
\r
1144 Object source = event.getSource();
\r
1145 int id = event.getID();
\r
1146 String actcmd = this.command;
\r
1147 long when = event.getWhen();
\r
1148 int modifiers = event.getModifiers();
\r
1150 for(ActionListener listener : getActionListeners()){
\r
1151 ActionEvent newEvent = new ActionEvent(source,
\r
1156 listener.actionPerformed(newEvent);
\r
1166 private class DiscussionPopup extends JPopupMenu{
\r
1168 private final JMenuItem menuCopy =
\r
1169 new JMenuItem("選択範囲をコピー");
\r
1170 private final JMenuItem menuSelTalk =
\r
1171 new JMenuItem("この発言をコピー");
\r
1172 private final JMenuItem menuJumpAnchor =
\r
1173 new JMenuItem("アンカーの示す先へジャンプ");
\r
1174 private final JMenuItem menuWebTalk =
\r
1175 new JMenuItem("この発言をブラウザで表示...");
\r
1176 private final JMenuItem menuSummary =
\r
1177 new JMenuItem("発言を集計...");
\r
1179 private TalkDraw lastPopupedTalkDraw;
\r
1180 private Anchor lastPopupedAnchor;
\r
1185 public DiscussionPopup(){
\r
1188 add(this.menuCopy);
\r
1189 add(this.menuSelTalk);
\r
1191 add(this.menuJumpAnchor);
\r
1192 add(this.menuWebTalk);
\r
1194 add(this.menuSummary);
\r
1197 .setActionCommand(ActionManager.CMD_COPY);
\r
1199 .setActionCommand(ActionManager.CMD_COPYTALK);
\r
1200 this.menuJumpAnchor
\r
1201 .setActionCommand(ActionManager.CMD_JUMPANCHOR);
\r
1203 .setActionCommand(ActionManager.CMD_WEBTALK);
\r
1205 .setActionCommand(ActionManager.CMD_DAYSUMMARY);
\r
1207 this.menuWebTalk.setIcon(GUIUtils.getWWWIcon());
\r
1214 * @param comp {@inheritDoc}
\r
1215 * @param x {@inheritDoc}
\r
1216 * @param y {@inheritDoc}
\r
1219 public void show(Component comp, int x, int y){
\r
1220 Point point = new Point(x, y);
\r
1222 this.lastPopupedTalkDraw = getHittedTalkDraw(point);
\r
1223 if(this.lastPopupedTalkDraw != null){
\r
1224 this.menuSelTalk.setEnabled(true);
\r
1225 this.menuWebTalk.setEnabled(true);
\r
1227 this.menuSelTalk.setEnabled(false);
\r
1228 this.menuWebTalk.setEnabled(false);
\r
1231 if(this.lastPopupedTalkDraw != null){
\r
1232 this.lastPopupedAnchor =
\r
1233 this.lastPopupedTalkDraw.getAnchor(point);
\r
1235 this.lastPopupedAnchor = null;
\r
1238 if(this.lastPopupedAnchor != null){
\r
1239 this.menuJumpAnchor.setEnabled(true);
\r
1241 this.menuJumpAnchor.setEnabled(false);
\r
1244 if(getSelected() != null){
\r
1245 this.menuCopy.setEnabled(true);
\r
1247 this.menuCopy.setEnabled(false);
\r
1250 super.show(comp, x, y);
\r
1256 // TODO シンプルモードの追加
\r
1257 // Period変更を追跡するリスナ化
\r