OSDN Git Service

901f5d2c686a1a171000ac520ad1ffecabed87ca
[jindolf/Jindolf.git] / src / main / java / jp / sfjp / jindolf / summary / DaySummary.java
1 /*
2  * summary of day panel
3  *
4  * License : The MIT License
5  * Copyright(c) 2008 olyutorskii
6  */
7
8 package jp.sfjp.jindolf.summary;
9
10 import java.awt.Color;
11 import java.awt.Component;
12 import java.awt.Container;
13 import java.awt.Dimension;
14 import java.awt.Frame;
15 import java.awt.GridBagConstraints;
16 import java.awt.GridBagLayout;
17 import java.awt.Image;
18 import java.awt.Insets;
19 import java.awt.LayoutManager;
20 import java.awt.event.ActionEvent;
21 import java.awt.event.ActionListener;
22 import java.awt.event.ItemEvent;
23 import java.awt.event.ItemListener;
24 import java.awt.event.WindowEvent;
25 import java.awt.event.WindowListener;
26 import java.text.NumberFormat;
27 import java.util.HashMap;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.SortedSet;
31 import java.util.TreeSet;
32 import javax.swing.ImageIcon;
33 import javax.swing.JButton;
34 import javax.swing.JComboBox;
35 import javax.swing.JDialog;
36 import javax.swing.JLabel;
37 import javax.swing.JScrollPane;
38 import javax.swing.JSeparator;
39 import javax.swing.JTable;
40 import javax.swing.ListSelectionModel;
41 import javax.swing.SwingConstants;
42 import javax.swing.table.DefaultTableCellRenderer;
43 import javax.swing.table.DefaultTableModel;
44 import javax.swing.table.TableColumn;
45 import javax.swing.table.TableColumnModel;
46 import jp.sfjp.jindolf.data.Avatar;
47 import jp.sfjp.jindolf.data.Period;
48 import jp.sfjp.jindolf.data.Talk;
49 import jp.sfjp.jindolf.data.Topic;
50 import jp.sfjp.jindolf.data.Village;
51 import jp.sfjp.jindolf.glyph.TalkDraw;
52 import jp.sfjp.jindolf.util.GUIUtils;
53 import jp.sourceforge.jindolf.corelib.TalkType;
54
55 /**
56  * その日ごとの集計。
57  */
58 @SuppressWarnings("serial")
59 public class DaySummary extends JDialog
60         implements WindowListener, ActionListener, ItemListener{
61
62     private static final NumberFormat AVERAGE_FORM;
63     private static final String PUBTALK   = "白発言";
64     private static final String WOLFTALK  = "赤発言";
65     private static final String GRAVETALK = "青発言";
66     private static final String PRVTALK   = "灰発言";
67     private static final String ALLTALK   = "全発言";
68     private static final int HORIZONTAL_GAP = 5;
69     private static final int VERTICAL_GAP   = 1;
70     private static final Color COLOR_ALL = new Color(0xffff80);
71
72     static{
73         AVERAGE_FORM = NumberFormat.getInstance();
74         AVERAGE_FORM.setMaximumFractionDigits(1);
75         AVERAGE_FORM.setMinimumFractionDigits(1);
76     }
77
78
79     private final DefaultTableModel tableModel;
80     private final TableColumn avatarColumn;
81
82     private final JTable tableComp;
83     private final JComboBox<String> typeSelector = new JComboBox<>();
84     private final JButton closeButton = new JButton("閉じる");
85     private final JLabel caption = new JLabel();
86     private final JLabel totalSum = new JLabel();
87
88     private TalkType talkFilter;
89     private Period period;
90
91
92     /**
93      * コンストラクタ。
94      * 集計結果を表示するモーダルダイアログを生成する。
95      * @param owner オーナー
96      */
97     public DaySummary(Frame owner){
98         super(owner);
99         setModal(true);
100
101         GUIUtils.modifyWindowAttributes(this, true, false, true);
102
103         setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
104         addWindowListener(this);
105
106         this.tableModel = createInitModel();
107         this.tableComp = new JTable();
108         this.tableComp.setModel(this.tableModel);
109         this.tableComp.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
110         this.tableComp.setIntercellSpacing(
111                 new Dimension(HORIZONTAL_GAP, VERTICAL_GAP) );
112         this.tableComp.setDefaultEditor(Object.class, null);
113         this.tableComp.setDefaultRenderer(Object.class, new CustomRenderer());
114         this.tableComp.setShowGrid(true);
115
116         TableColumnModel tcolModel = this.tableComp.getColumnModel();
117
118         this.avatarColumn = tcolModel.getColumn(0);
119
120         DefaultTableCellRenderer renderer;
121
122         renderer = new DefaultTableCellRenderer();
123         renderer.setHorizontalAlignment(SwingConstants.RIGHT);
124         tcolModel.getColumn(1).setCellRenderer(renderer);
125
126         renderer = new DefaultTableCellRenderer();
127         renderer.setHorizontalAlignment(SwingConstants.RIGHT);
128         tcolModel.getColumn(2).setCellRenderer(renderer);
129
130         renderer = new DefaultTableCellRenderer();
131         renderer.setHorizontalAlignment(SwingConstants.RIGHT);
132         tcolModel.getColumn(3).setCellRenderer(renderer);
133
134         this.typeSelector.addItem(PUBTALK);
135         this.typeSelector.addItem(WOLFTALK);
136         this.typeSelector.addItem(GRAVETALK);
137         this.typeSelector.addItem(PRVTALK);
138         this.typeSelector.addItem(ALLTALK);
139
140         this.closeButton.addActionListener(this);
141         this.typeSelector.addItemListener(this);
142
143         this.typeSelector.setSelectedItem(null);
144         this.typeSelector.setSelectedItem(PUBTALK);
145
146         design();
147
148         clearModel();
149
150         return;
151     }
152
153
154     /**
155      * 初期のデータモデルを生成する。
156      * @return データモデル
157      */
158     private static DefaultTableModel createInitModel(){
159         DefaultTableModel result;
160         result = new DefaultTableModel();
161
162         Object[] rowHeads = {"名前", "発言回数", "平均文字列長", "最終発言"};
163         result.setColumnCount(rowHeads.length);
164         result.setColumnIdentifiers(rowHeads);
165
166         return result;
167     }
168
169
170     /**
171      * テーブルをクリアする。
172      */
173     private void clearModel(){
174         int rows = this.tableModel.getRowCount();
175         for(int ct = 1; ct <= rows; ct++){
176             this.tableModel.removeRow(0);
177         }
178     }
179
180     /**
181      * 行を追加する。
182      * @param avatar アバター
183      * @param talkCount 発言回数
184      * @param totalChars 発言文字総数
185      * @param lastTime 最終発言時刻
186      */
187     private void appendRow(Avatar avatar,
188                             Integer talkCount,
189                             Integer totalChars,
190                             String lastTime ){
191         String talks = talkCount + " 回";
192
193         double average;
194         if(talkCount <= 0){
195             average = 0.0;
196         }else{
197             average = (double) totalChars / (double) talkCount;
198         }
199         String chars = AVERAGE_FORM.format(average) + " 文字";
200
201         Object[] row = {avatar, talks, chars, lastTime};
202         int rowIndex = this.tableModel.getRowCount();
203
204         this.tableModel.insertRow(rowIndex, row);
205
206         return;
207     }
208
209     /**
210      * デザインを行う。
211      */
212     private void design(){
213         Container content = getContentPane();
214
215         LayoutManager layout = new GridBagLayout();
216         GridBagConstraints constraints = new GridBagConstraints();
217         content.setLayout(layout);
218
219         constraints.insets = new Insets(5, 5, 5, 5);
220
221         constraints.gridwidth = 1;
222         constraints.fill = GridBagConstraints.NONE;
223         constraints.anchor = GridBagConstraints.WEST;
224         content.add(this.caption, constraints);
225
226         constraints.gridwidth = GridBagConstraints.REMAINDER;
227         constraints.fill = GridBagConstraints.NONE;
228         content.add(this.typeSelector, constraints);
229
230         JScrollPane scroller = new JScrollPane(this.tableComp);
231         constraints.weightx = 1.0;
232         constraints.weighty = 1.0;
233         constraints.fill = GridBagConstraints.BOTH;
234         content.add(scroller, constraints);
235
236         constraints.weightx = 0.0;
237         constraints.weighty = 0.0;
238         constraints.fill = GridBagConstraints.NONE;
239         constraints.anchor = GridBagConstraints.WEST;
240         content.add(this.totalSum, constraints);
241
242         constraints.weightx = 1.0;
243         constraints.weighty = 0.0;
244         constraints.fill = GridBagConstraints.HORIZONTAL;
245         content.add(new JSeparator(), constraints);
246
247         constraints.weightx = 0.0;
248         constraints.weighty = 0.0;
249         constraints.fill = GridBagConstraints.NONE;
250         constraints.anchor = GridBagConstraints.EAST;
251         content.add(this.closeButton, constraints);
252
253         return;
254     }
255
256     /**
257      * 与えられたPeriodで集計を更新する。
258      * @param newPeriod 日
259      */
260     public void summaryPeriod(Period newPeriod){
261         this.period = newPeriod;
262         summaryPeriod();
263     }
264
265     /**
266      * 集計を更新する。
267      */
268     private void summaryPeriod(){
269         clearModel();
270
271         if(this.period == null) return;
272
273         SortedSet<Avatar> avatarSet = new TreeSet<>();
274         Map<Avatar, Integer> talkCount  = new HashMap<>();
275         Map<Avatar, Integer> totalChars = new HashMap<>();
276         Map<Avatar, Talk> lastTalk = new HashMap<>();
277
278         List<Topic> topicList = this.period.getTopicList();
279         for(Topic topic : topicList){
280             if( ! (topic instanceof Talk)) continue;
281             Talk talk = (Talk) topic;
282             if(talk.getTalkCount() <= 0) continue;
283             if(    this.talkFilter != null
284                 && talk.getTalkType() != this.talkFilter) continue;
285
286             Avatar avatar = talk.getAvatar();
287
288             Integer counts = talkCount.get(avatar);
289             if(counts == null) counts = Integer.valueOf(0);
290             counts++;
291             talkCount.put(avatar, counts);
292
293             Integer total = totalChars.get(avatar);
294             if(total == null) total = Integer.valueOf(0);
295             total += talk.getTotalChars();
296             totalChars.put(avatar, total);
297
298             lastTalk.put(avatar, talk);
299
300             avatarSet.add(avatar);
301         }
302
303         int sum = 0;
304         for(Avatar avatar : avatarSet){
305             Integer counts = talkCount.get(avatar);
306             Integer total = totalChars.get(avatar);
307             String lastTime = lastTalk.get(avatar).getAnchorNotation();
308             appendRow(avatar, counts, total, lastTime);
309             sum += counts;
310         }
311
312         this.totalSum.setText("合計:" + sum + " 発言");
313
314         Village village = this.period.getVillage();
315         String villageName = village.getVillageName();
316         String periodCaption = this.period.getCaption();
317         this.caption.setText(villageName + "村 " + periodCaption);
318
319         return;
320     }
321
322     /**
323      * {@inheritDoc}
324      * @param event {@inheritDoc}
325      */
326     @Override
327     public void windowActivated(WindowEvent event){
328         return;
329     }
330
331     /**
332      * {@inheritDoc}
333      * @param event {@inheritDoc}
334      */
335     @Override
336     public void windowDeactivated(WindowEvent event){
337         return;
338     }
339
340     /**
341      * {@inheritDoc}
342      * @param event {@inheritDoc}
343      */
344     @Override
345     public void windowIconified(WindowEvent event){
346         return;
347     }
348
349     /**
350      * {@inheritDoc}
351      * @param event {@inheritDoc}
352      */
353     @Override
354     public void windowDeiconified(WindowEvent event){
355         return;
356     }
357
358     /**
359      * {@inheritDoc}
360      * @param event {@inheritDoc}
361      */
362     @Override
363     public void windowOpened(WindowEvent event){
364         return;
365     }
366
367     /**
368      * {@inheritDoc}
369      * ダイアログのクローズボタン押下処理を行う。
370      * @param event ウィンドウ変化イベント {@inheritDoc}
371      */
372     @Override
373     public void windowClosing(WindowEvent event){
374         close();
375         return;
376     }
377
378     /**
379      * {@inheritDoc}
380      * @param event {@inheritDoc}
381      */
382     @Override
383     public void windowClosed(WindowEvent event){
384         return;
385     }
386
387     /**
388      * {@inheritDoc}
389      * クローズボタン押下処理。
390      * @param event イベント {@inheritDoc}
391      */
392     @Override
393     public void actionPerformed(ActionEvent event){
394         if(event.getSource() != this.closeButton) return;
395         close();
396         return;
397     }
398
399     /**
400      * {@inheritDoc}
401      * コンボボックス操作処理。
402      * @param event イベント {@inheritDoc}
403      */
404     @Override
405     public void itemStateChanged(ItemEvent event){
406         if(event.getStateChange() != ItemEvent.SELECTED) return;
407
408         Object selected = this.typeSelector.getSelectedItem();
409         if     (selected == PUBTALK)   this.talkFilter = TalkType.PUBLIC;
410         else if(selected == WOLFTALK)  this.talkFilter = TalkType.WOLFONLY;
411         else if(selected == GRAVETALK) this.talkFilter = TalkType.GRAVE;
412         else if(selected == PRVTALK)   this.talkFilter = TalkType.PRIVATE;
413         else if(selected == ALLTALK)   this.talkFilter = null;
414
415         summaryPeriod();
416
417         return;
418     }
419
420     /**
421      * このパネルを閉じる。
422      */
423     private void close(){
424         clearModel();
425         this.period = null;
426         setVisible(false);
427         return;
428     }
429
430     /**
431      * Avatar 顔イメージ描画用カスタムセルレンダラ。
432      */
433     private class CustomRenderer extends DefaultTableCellRenderer{
434
435         /**
436          * コンストラクタ。
437          */
438         public CustomRenderer(){
439             super();
440             return;
441         }
442
443         /**
444          * {@inheritDoc}
445          * セルに{@link Avatar}がきたら顔アイコンと名前を表示する。
446          * @param value {@inheritDoc}
447          */
448         @Override
449         public void setValue(Object value){
450             if(value instanceof Avatar){
451                 Avatar avatar = (Avatar) value;
452
453                 Village village = DaySummary.this.period.getVillage();
454                 Image image = village.getAvatarFaceImage(avatar);
455                 if(image == null) image = village.getGraveImage();
456                 if(image != null){
457                     ImageIcon icon = new ImageIcon(image);
458                     setIcon(icon);
459                 }
460
461                 setText(avatar.getName());
462
463                 Dimension prefSize = getPreferredSize();
464
465                 int cellHeight = VERTICAL_GAP * 2 + prefSize.height;
466                 if(DaySummary.this.tableComp.getRowHeight() < cellHeight){
467                     DaySummary.this.tableComp.setRowHeight(cellHeight);
468                 }
469
470                 int cellWidth = HORIZONTAL_GAP * 2 + prefSize.width;
471                 if(   DaySummary.this.avatarColumn.getPreferredWidth()
472                     < cellWidth ){
473                     DaySummary.this.avatarColumn.setPreferredWidth(cellWidth);
474                 }
475
476                 return;
477             }
478
479             super.setValue(value);
480
481             return;
482         }
483
484         /**
485          *  {@inheritDoc}
486          * 統計種別によってセル色を変える。
487          * @param table {@inheritDoc}
488          * @param value {@inheritDoc}
489          * @param isSelected {@inheritDoc}
490          * @param hasFocus {@inheritDoc}
491          * @param row {@inheritDoc}
492          * @param column {@inheritDoc}
493          * @return {@inheritDoc}
494          */
495         @Override
496         public Component getTableCellRendererComponent(JTable table,
497                                                            Object value,
498                                                            boolean isSelected,
499                                                            boolean hasFocus,
500                                                            int row,
501                                                            int column ){
502             Component result = super.getTableCellRendererComponent(table,
503                                                                    value,
504                                                                    isSelected,
505                                                                    hasFocus,
506                                                                    row,
507                                                                    column );
508
509             Object selected = DaySummary.this.typeSelector.getSelectedItem();
510             Color bgColor = null;
511             if(selected == PUBTALK){
512                 bgColor = TalkDraw.COLOR_PUBLIC;
513             }else if(selected == WOLFTALK){
514                 bgColor = TalkDraw.COLOR_WOLFONLY;
515             }else if(selected == GRAVETALK){
516                 bgColor = TalkDraw.COLOR_GRAVE;
517             }else if(selected == PRVTALK){
518                 bgColor = TalkDraw.COLOR_PRIVATE;
519             }else if(selected == ALLTALK){
520                 bgColor = COLOR_ALL;
521             }else{
522                 assert false;
523                 return null;
524             }
525
526             result.setForeground(Color.BLACK);
527             result.setBackground(bgColor);
528
529             return result;
530         }
531     }
532
533 }