OSDN Git Service

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