OSDN Git Service

例外の具体化
[jindolf/Jindolf.git] / src / main / java / jp / sourceforge / jindolf / Controller.java
1 /*\r
2  * MVC controller\r
3  *\r
4  * License : The MIT License\r
5  * Copyright(c) 2008 olyutorskii\r
6  */\r
7 \r
8 package jp.sourceforge.jindolf;\r
9 \r
10 import java.awt.BorderLayout;\r
11 import java.awt.Component;\r
12 import java.awt.Container;\r
13 import java.awt.Cursor;\r
14 import java.awt.EventQueue;\r
15 import java.awt.Frame;\r
16 import java.awt.LayoutManager;\r
17 import java.awt.Window;\r
18 import java.awt.event.ActionEvent;\r
19 import java.awt.event.ActionListener;\r
20 import java.awt.event.KeyAdapter;\r
21 import java.awt.event.MouseAdapter;\r
22 import java.awt.event.WindowAdapter;\r
23 import java.awt.event.WindowEvent;\r
24 import java.io.File;\r
25 import java.io.IOException;\r
26 import java.io.UnsupportedEncodingException;\r
27 import java.lang.reflect.InvocationTargetException;\r
28 import java.net.URL;\r
29 import java.net.URLEncoder;\r
30 import java.util.HashMap;\r
31 import java.util.List;\r
32 import java.util.Map;\r
33 import java.util.Set;\r
34 import java.util.concurrent.Executor;\r
35 import java.util.concurrent.Executors;\r
36 import java.util.logging.Handler;\r
37 import java.util.logging.Logger;\r
38 import java.util.regex.Pattern;\r
39 import javax.swing.JButton;\r
40 import javax.swing.JComponent;\r
41 import javax.swing.JDialog;\r
42 import javax.swing.JFrame;\r
43 import javax.swing.JOptionPane;\r
44 import javax.swing.JToolBar;\r
45 import javax.swing.JTree;\r
46 import javax.swing.LookAndFeel;\r
47 import javax.swing.SwingUtilities;\r
48 import javax.swing.UIManager;\r
49 import javax.swing.UnsupportedLookAndFeelException;\r
50 import javax.swing.WindowConstants;\r
51 import javax.swing.event.ChangeEvent;\r
52 import javax.swing.event.ChangeListener;\r
53 import javax.swing.event.TreeExpansionEvent;\r
54 import javax.swing.event.TreeSelectionEvent;\r
55 import javax.swing.event.TreeSelectionListener;\r
56 import javax.swing.event.TreeWillExpandListener;\r
57 import javax.swing.tree.TreePath;\r
58 import jp.sourceforge.jindolf.corelib.LandDef;\r
59 import jp.sourceforge.jindolf.corelib.VillageState;\r
60 \r
61 /**\r
62  * いわゆるMVCでいうとこのコントローラ。\r
63  */\r
64 public class Controller\r
65         implements ActionListener,\r
66                    TreeWillExpandListener,\r
67                    TreeSelectionListener,\r
68                    ChangeListener,\r
69                    AnchorHitListener {\r
70 \r
71     private final ActionManager actionManager;\r
72     private final TopView topView;\r
73     private final LandsModel model;\r
74 \r
75     private final FilterPanel filterFrame;\r
76     private final LogFrame showlogFrame;\r
77     private final OptionPanel optionPanel;\r
78     private final FindPanel findPanel;\r
79     private final TalkPreview talkPreview;\r
80     private JFrame helpFrame;\r
81     private AccountPanel accountFrame;\r
82     private DaySummary daySummaryPanel;\r
83     private VillageDigest digestPanel;\r
84     private final Map<Window, Boolean> windowMap =\r
85             new HashMap<Window, Boolean>();\r
86 \r
87     private volatile boolean isBusyNow;\r
88 \r
89     private JFrame topFrame = null;\r
90 \r
91     /**\r
92      * コントローラの生成。\r
93      * @param actionManager アクション管理\r
94      * @param topView 最上位ビュー\r
95      * @param model 最上位データモデル\r
96      */\r
97     public Controller(ActionManager actionManager,\r
98                        TopView topView,\r
99                        LandsModel model){\r
100         super();\r
101 \r
102         this.actionManager = actionManager;\r
103         this.topView = topView;\r
104         this.model = model;\r
105 \r
106         JToolBar toolbar = this.actionManager.getBrowseToolBar();\r
107         this.topView.setBrowseToolBar(toolbar);\r
108 \r
109         this.actionManager.addActionListener(this);\r
110 \r
111         JTree treeView = this.topView.getTreeView();\r
112         treeView.setModel(this.model);\r
113         treeView.addTreeWillExpandListener(this);\r
114         treeView.addTreeSelectionListener(this);\r
115 \r
116         this.topView.getTabBrowser().addChangeListener(this);\r
117         this.topView.getTabBrowser().addActionListener(this);\r
118         this.topView.getTabBrowser().addAnchorHitListener(this);\r
119 \r
120         JButton reloadVillageListButton = this.topView\r
121                                          .getLandsTree()\r
122                                          .getReloadVillageListButton();\r
123         reloadVillageListButton.addActionListener(this);\r
124         reloadVillageListButton.setEnabled(false);\r
125 \r
126         this.filterFrame = new FilterPanel(this.topFrame);\r
127         this.filterFrame.addChangeListener(this);\r
128         this.filterFrame.pack();\r
129         this.filterFrame.setVisible(false);\r
130 \r
131         this.showlogFrame = new LogFrame(this.topFrame);\r
132         this.showlogFrame.pack();\r
133         this.showlogFrame.setSize(600, 500);\r
134         this.showlogFrame.setLocationByPlatform(true);\r
135         this.showlogFrame.setVisible(false);\r
136         if(Jindolf.hasLoggingPermission()){\r
137             Handler newHandler = this.showlogFrame.getHandler();\r
138             Logger jre14Logger = Jindolf.logger().getJre14Logger();\r
139             jre14Logger.addHandler(newHandler);\r
140             Handler[] handlers = jre14Logger.getHandlers();\r
141             for(Handler handler : handlers){\r
142                 if( ! (handler instanceof PileHandler) ) continue;\r
143                 PileHandler pile = (PileHandler) handler;\r
144                 pile.delegate(newHandler);\r
145                 pile.close();\r
146             }\r
147         }\r
148 \r
149         this.talkPreview = new TalkPreview();\r
150         this.talkPreview.pack();\r
151         this.talkPreview.setSize(700, 500);\r
152         this.talkPreview.setVisible(false);\r
153         this.talkPreview.loadDraft();\r
154 \r
155         this.optionPanel = new OptionPanel(this.topFrame);\r
156         this.optionPanel.pack();\r
157         this.optionPanel.setSize(450, 500);\r
158         this.optionPanel.setVisible(false);\r
159 \r
160         this.findPanel = new FindPanel(this.topFrame);\r
161         this.findPanel.pack();\r
162         this.findPanel.setVisible(false);\r
163         this.findPanel.loadHistory();\r
164 \r
165         this.windowMap.put(this.filterFrame,  true);\r
166         this.windowMap.put(this.showlogFrame, false);\r
167         this.windowMap.put(this.talkPreview,  false);\r
168         this.windowMap.put(this.optionPanel,  false);\r
169         this.windowMap.put(this.findPanel,    true);\r
170 \r
171         AppSetting setting = Jindolf.getAppSetting();\r
172 \r
173         FontInfo fontInfo = setting.getFontInfo();\r
174         this.topView.getTabBrowser().setFontInfo(fontInfo);\r
175         this.talkPreview.setFontInfo(fontInfo);\r
176         this.optionPanel.getFontChooser().setFontInfo(fontInfo);\r
177 \r
178         ProxyInfo proxyInfo = setting.getProxyInfo();\r
179         this.optionPanel.getProxyChooser().setProxyInfo(proxyInfo);\r
180 \r
181         DialogPref pref = setting.getDialogPref();\r
182         this.topView.getTabBrowser().setDialogPref(pref);\r
183         this.optionPanel.getDialogPrefPanel().setDialogPref(pref);\r
184 \r
185         return;\r
186     }\r
187 \r
188     /**\r
189      * トップフレームを生成する。\r
190      * @return トップフレーム\r
191      */\r
192     @SuppressWarnings("serial")\r
193     public JFrame createTopFrame(){\r
194         this.topFrame = new JFrame();\r
195 \r
196         Container content = this.topFrame.getContentPane();\r
197         LayoutManager layout = new BorderLayout();\r
198         content.setLayout(layout);\r
199         content.add(this.topView, BorderLayout.CENTER);\r
200 \r
201         Component glassPane = new JComponent() {};\r
202         glassPane.addMouseListener(new MouseAdapter() {});\r
203         glassPane.addKeyListener(new KeyAdapter() {});\r
204         this.topFrame.setGlassPane(glassPane);\r
205 \r
206         this.topFrame.setJMenuBar(this.actionManager.getMenuBar());\r
207         setFrameTitle(null);\r
208 \r
209         this.windowMap.put(this.topFrame, false);\r
210 \r
211         this.topFrame.setDefaultCloseOperation(\r
212                 WindowConstants.DISPOSE_ON_CLOSE);\r
213         this.topFrame.addWindowListener(new WindowAdapter(){\r
214             @Override\r
215             public void windowClosed(WindowEvent event){\r
216                 shutdown();\r
217             }\r
218         });\r
219 \r
220         return this.topFrame;\r
221     }\r
222 \r
223     /**\r
224      * About画面を表示する。\r
225      */\r
226     private void actionAbout(){\r
227         String message =\r
228                 Jindolf.TITLE\r
229                 + "   Version " + Jindolf.VERSION + "\n"\r
230                 + Jindolf.COPYRIGHT + "\n"\r
231                 + "ライセンス: " + Jindolf.LICENSE + "\n"\r
232                 + "連絡先: " + Jindolf.CONTACT;\r
233 \r
234         if(Jindolf.COMMENT.length() > 0){\r
235             message += "\n" + Jindolf.COMMENT;\r
236         }\r
237 \r
238         JOptionPane pane = new JOptionPane(message,\r
239                                            JOptionPane.INFORMATION_MESSAGE,\r
240                                            JOptionPane.DEFAULT_OPTION,\r
241                                            GUIUtils.getLogoIcon());\r
242 \r
243         JDialog dialog = pane.createDialog(this.topFrame,\r
244                                            Jindolf.TITLE + "について");\r
245 \r
246         dialog.pack();\r
247         dialog.setVisible(true);\r
248         dialog.dispose();\r
249 \r
250         return;\r
251     }\r
252 \r
253     /**\r
254      * アプリ終了。\r
255      */\r
256     private void actionExit(){\r
257         shutdown();\r
258         return;\r
259     }\r
260 \r
261     /**\r
262      * Help画面を表示する。\r
263      */\r
264     private void actionHelp(){\r
265         if(this.helpFrame != null){                 // show Toggle\r
266             toggleWindow(this.helpFrame);\r
267             return;\r
268         }\r
269 \r
270         this.helpFrame = new HelpFrame();\r
271         this.helpFrame.pack();\r
272         this.helpFrame.setSize(450, 450);\r
273 \r
274         this.windowMap.put(this.helpFrame, false);\r
275 \r
276         this.helpFrame.setVisible(true);\r
277 \r
278         return;\r
279     }\r
280 \r
281     /**\r
282      * 村をWebブラウザで表示する。\r
283      */\r
284     private void actionShowWebVillage(){\r
285         TabBrowser browser = this.topView.getTabBrowser();\r
286         Village village = browser.getVillage();\r
287         if(village == null) return;\r
288 \r
289         Land land = village.getParentLand();\r
290         ServerAccess server = land.getServerAccess();\r
291 \r
292         URL url = server.getVillageURL(village);\r
293 \r
294         String urlText = url.toString();\r
295         if(village.getState() != VillageState.GAMEOVER){\r
296             urlText += "#bottom";\r
297         }\r
298 \r
299         WebIPCDialog.showDialog(this.topFrame, urlText);\r
300 \r
301         return;\r
302     }\r
303 \r
304     /**\r
305      * 村に対応するまとめサイトをWebブラウザで表示する。\r
306      */\r
307     private void actionShowWebWiki(){\r
308         TabBrowser browser = this.topView.getTabBrowser();\r
309         Village village = browser.getVillage();\r
310         if(village == null) return;\r
311 \r
312         String villageName;\r
313         LandDef landDef = village.getParentLand().getLandDef();\r
314         if(landDef.getLandId().equals("wolfg")){\r
315             String vnum = "000" + village.getVillageID();\r
316             vnum = vnum.substring(vnum.length() - 3);\r
317             villageName = landDef.getLandPrefix() + vnum;\r
318         }else{\r
319             villageName = village.getVillageName();\r
320         }\r
321 \r
322         StringBuilder url =\r
323                 new StringBuilder()\r
324                 .append("http://wolfbbs.jp/")\r
325                 .append(villageName)\r
326                 .append("%C2%BC.html");\r
327 \r
328         WebIPCDialog.showDialog(this.topFrame, url.toString());\r
329 \r
330         return;\r
331     }\r
332 \r
333     /**\r
334      * 村に対応するキャスト紹介表ジェネレーターをWebブラウザで表示する。\r
335      */\r
336     private void actionShowWebCast(){\r
337         TabBrowser browser = this.topView.getTabBrowser();\r
338         Village village = browser.getVillage();\r
339         if(village == null) return;\r
340 \r
341         Land land = village.getParentLand();\r
342         ServerAccess server = land.getServerAccess();\r
343 \r
344         URL villageUrl = server.getVillageURL(village);\r
345 \r
346         StringBuilder url = new StringBuilder("http://hon5.com/jinro/");\r
347 \r
348         try{\r
349             url.append("?u=")\r
350                .append(URLEncoder.encode(villageUrl.toString(), "UTF-8"));\r
351         }catch(UnsupportedEncodingException e){\r
352             return;\r
353         }\r
354 \r
355         url.append("&s=1");\r
356 \r
357         WebIPCDialog.showDialog(this.topFrame, url.toString());\r
358 \r
359         return;\r
360     }\r
361 \r
362     /**\r
363      * 日(Period)をWebブラウザで表示する。\r
364      */\r
365     private void actionShowWebDay(){\r
366         PeriodView periodView = currentPeriodView();\r
367         if(periodView == null) return;\r
368 \r
369         Period period = periodView.getPeriod();\r
370         if(period == null) return;\r
371 \r
372         TabBrowser browser = this.topView.getTabBrowser();\r
373         Village village = browser.getVillage();\r
374         if(village == null) return;\r
375 \r
376         Land land = village.getParentLand();\r
377         ServerAccess server = land.getServerAccess();\r
378 \r
379         URL url = server.getPeriodURL(period);\r
380 \r
381         String urlText = url.toString();\r
382         if(period.isHot()) urlText += "#bottom";\r
383 \r
384         WebIPCDialog.showDialog(this.topFrame, urlText);\r
385 \r
386         return;\r
387     }\r
388 \r
389     /**\r
390      * 個別の発言をWebブラウザで表示する。\r
391      */\r
392     private void actionShowWebTalk(){\r
393         TabBrowser browser = this.topView.getTabBrowser();\r
394         Village village = browser.getVillage();\r
395         if(village == null) return;\r
396 \r
397         PeriodView periodView = currentPeriodView();\r
398         if(periodView == null) return;\r
399 \r
400         Discussion discussion = periodView.getDiscussion();\r
401         Talk talk = discussion.getPopupedTalk();\r
402         if(talk == null) return;\r
403 \r
404         Period period = periodView.getPeriod();\r
405         if(period == null) return;\r
406 \r
407         Land land = village.getParentLand();\r
408         ServerAccess server = land.getServerAccess();\r
409 \r
410         URL url = server.getPeriodURL(period);\r
411 \r
412         String urlText = url.toString();\r
413         urlText += "#" + talk.getMessageID();\r
414         WebIPCDialog.showDialog(this.topFrame, urlText);\r
415 \r
416         return;\r
417     }\r
418 \r
419     /**\r
420      * ポータルサイトをWebブラウザで表示する。\r
421      */\r
422     private void actionShowPortal(){\r
423         WebIPCDialog.showDialog(this.topFrame, Jindolf.CONTACT);\r
424         return;\r
425     }\r
426 \r
427     /**\r
428      * 例外発生による警告ダイアログへの反応を促す。\r
429      * @param title タイトル文字列\r
430      * @param message メッセージ\r
431      * @param e 例外\r
432      */\r
433     private void warnDialog(String title, String message, Throwable e){\r
434         Jindolf.logger().warn(message, e);\r
435         JOptionPane.showMessageDialog(\r
436             this.topFrame,\r
437             message,\r
438             title + " - " + Jindolf.TITLE,\r
439             JOptionPane.WARNING_MESSAGE );\r
440         return;\r
441     }\r
442 \r
443     /**\r
444      * L&Fの変更を行う。\r
445      */\r
446     private void actionChangeLaF(){\r
447         String className = this.actionManager.getSelectedLookAndFeel();\r
448 \r
449         String warnTitle = "Look&Feel";\r
450         String warnMsg;\r
451 \r
452         Class<?> lnfClass;\r
453         warnMsg = "このLook&Feel[" + className + "]を読み込む事ができません。";\r
454         try{\r
455             lnfClass = Class.forName(className);\r
456         }catch(ClassNotFoundException e){\r
457             warnDialog(warnTitle, warnMsg, e);\r
458             return;\r
459         }\r
460 \r
461         LookAndFeel lnf;\r
462         warnMsg = "このLook&Feel[" + className + "]を生成する事ができません。";\r
463         try{\r
464             lnf = (LookAndFeel)( lnfClass.newInstance() );\r
465         }catch(InstantiationException e){\r
466             warnDialog(warnTitle, warnMsg, e);\r
467             return;\r
468         }catch(IllegalAccessException e){\r
469             warnDialog(warnTitle, warnMsg, e);\r
470             return;\r
471         }catch(ClassCastException e){\r
472             warnDialog(warnTitle, warnMsg, e);\r
473             return;\r
474         }\r
475 \r
476         warnMsg = "このLook&Feel[" + lnf.getName() + "]はサポートされていません。";\r
477         try{\r
478             UIManager.setLookAndFeel(lnf);\r
479         }catch(UnsupportedLookAndFeelException e){\r
480             warnDialog(warnTitle, warnMsg, e);\r
481             return;\r
482         }\r
483 \r
484         Jindolf.logger().info(\r
485                 "Look&Feelが["\r
486                 +lnf.getName()\r
487                 +"]に変更されました。");\r
488 \r
489         final Runnable updateUITask = new Runnable(){\r
490             public void run(){\r
491                 Set<Window> windows = Controller.this.windowMap.keySet();\r
492                 for(Window window : windows){\r
493                     SwingUtilities.updateComponentTreeUI(window);\r
494                     window.validate();\r
495                     boolean needPack = Controller.this.windowMap.get(window);\r
496                     if(needPack){\r
497                         window.pack();\r
498                     }\r
499                 }\r
500 \r
501                 return;\r
502             }\r
503         };\r
504 \r
505         Executor executor = Executors.newCachedThreadPool();\r
506         executor.execute(new Runnable(){\r
507             public void run(){\r
508                 setBusy(true);\r
509                 updateStatusBar("Look&Feelを更新中…");\r
510                 try{\r
511                     SwingUtilities.invokeAndWait(updateUITask);\r
512                 }catch(InvocationTargetException e){\r
513                     Jindolf.logger().warn(\r
514                             "Look&Feelの更新に失敗しました。", e);\r
515                 }catch(InterruptedException e){\r
516                     Jindolf.logger().warn(\r
517                             "Look&Feelの更新に失敗しました。", e);\r
518                 }finally{\r
519                     updateStatusBar("Look&Feelが更新されました");\r
520                     setBusy(false);\r
521                 }\r
522                 return;\r
523             }\r
524         });\r
525 \r
526         return;\r
527     }\r
528 \r
529     /**\r
530      * 発言フィルタ画面を表示する。\r
531      */\r
532     private void actionShowFilter(){\r
533         toggleWindow(this.filterFrame);\r
534         return;\r
535     }\r
536 \r
537     /**\r
538      * アカウント管理画面を表示する。\r
539      */\r
540     private void actionShowAccount(){\r
541         if(this.accountFrame != null){                 // show Toggle\r
542             toggleWindow(this.accountFrame);\r
543             return;\r
544         }\r
545 \r
546         this.accountFrame = new AccountPanel(this.topFrame, this.model);\r
547         this.accountFrame.pack();\r
548         this.accountFrame.setVisible(true);\r
549 \r
550         this.windowMap.put(this.accountFrame, true);\r
551 \r
552         return;\r
553     }\r
554 \r
555     /**\r
556      * ログ表示画面を表示する。\r
557      */\r
558     private void actionShowLog(){\r
559         toggleWindow(this.showlogFrame);\r
560         return;\r
561     }\r
562 \r
563     /**\r
564      * 発言エディタを表示する。\r
565      */\r
566     private void actionTalkPreview(){\r
567         toggleWindow(this.talkPreview);\r
568         return;\r
569     }\r
570 \r
571     /**\r
572      * オプション設定画面を表示する。\r
573      */\r
574     private void actionOption(){\r
575         AppSetting setting = Jindolf.getAppSetting();\r
576 \r
577         FontInfo fontInfo = setting.getFontInfo();\r
578         this.optionPanel.getFontChooser().setFontInfo(fontInfo);\r
579 \r
580         ProxyInfo proxyInfo = setting.getProxyInfo();\r
581         this.optionPanel.getProxyChooser().setProxyInfo(proxyInfo);\r
582 \r
583         DialogPref dialogPref = setting.getDialogPref();\r
584         this.optionPanel.getDialogPrefPanel().setDialogPref(dialogPref);\r
585 \r
586         this.optionPanel.setVisible(true);\r
587         if(this.optionPanel.isCanceled()) return;\r
588 \r
589         fontInfo = this.optionPanel.getFontChooser().getFontInfo();\r
590         updateFontInfo(fontInfo);\r
591 \r
592         proxyInfo = this.optionPanel.getProxyChooser().getProxyInfo();\r
593         updateProxyInfo(proxyInfo);\r
594 \r
595         dialogPref = this.optionPanel.getDialogPrefPanel().getDialogPref();\r
596         updateDialogPref(dialogPref);\r
597 \r
598         return;\r
599     }\r
600 \r
601     /**\r
602      * フォント設定を変更する。\r
603      * @param newFontInfo 新フォント設定\r
604      */\r
605     private void updateFontInfo(final FontInfo newFontInfo){\r
606         AppSetting setting = Jindolf.getAppSetting();\r
607         FontInfo oldInfo = setting.getFontInfo();\r
608 \r
609         if(newFontInfo.equals(oldInfo)) return;\r
610         setting.setFontInfo(newFontInfo);\r
611 \r
612         this.topView.getTabBrowser().setFontInfo(newFontInfo);\r
613         this.talkPreview.setFontInfo(newFontInfo);\r
614         this.optionPanel.getFontChooser().setFontInfo(newFontInfo);\r
615 \r
616         return;\r
617     }\r
618 \r
619     /**\r
620      * プロクシ設定を変更する。\r
621      * @param newProxyInfo 新プロクシ設定\r
622      */\r
623     private void updateProxyInfo(ProxyInfo newProxyInfo){\r
624         AppSetting setting = Jindolf.getAppSetting();\r
625         ProxyInfo oldProxyInfo = setting.getProxyInfo();\r
626 \r
627         if(newProxyInfo.equals(oldProxyInfo)) return;\r
628         setting.setProxyInfo(newProxyInfo);\r
629 \r
630         for(Land land : this.model.getLandList()){\r
631             ServerAccess server = land.getServerAccess();\r
632             server.setProxy(newProxyInfo.getProxy());\r
633         }\r
634 \r
635         return;\r
636     }\r
637 \r
638     /**\r
639      * 発言表示設定を更新する。\r
640      * @param newDialogPref 表示設定\r
641      */\r
642     private void updateDialogPref(DialogPref newDialogPref){\r
643         AppSetting setting = Jindolf.getAppSetting();\r
644         DialogPref oldDialogPref = setting.getDialogPref();\r
645 \r
646         if(newDialogPref.equals(oldDialogPref)) return;\r
647         setting.setDialogPref(newDialogPref);\r
648 \r
649         this.topView.getTabBrowser().setDialogPref(newDialogPref);\r
650 \r
651         return;\r
652     }\r
653 \r
654     /**\r
655      * 村ダイジェスト画面を表示する。\r
656      */\r
657     private void actionShowDigest(){\r
658         TabBrowser browser = this.topView.getTabBrowser();\r
659         final Village village = browser.getVillage();\r
660         if(village == null) return;\r
661 \r
662         VillageState villageState = village.getState();\r
663         if((   villageState != VillageState.EPILOGUE\r
664             && villageState != VillageState.GAMEOVER\r
665            ) || ! village.isValid() ){\r
666             String message = "エピローグを正常に迎えていない村は\n"\r
667                             +"ダイジェスト機能を利用できません";\r
668             String title = "ダイジェスト不可 - " + Jindolf.TITLE;\r
669             JOptionPane pane = new JOptionPane(message,\r
670                                                JOptionPane.WARNING_MESSAGE,\r
671                                                JOptionPane.DEFAULT_OPTION );\r
672             JDialog dialog = pane.createDialog(this.topFrame, title);\r
673             dialog.pack();\r
674             dialog.setVisible(true);\r
675             dialog.dispose();\r
676             return;\r
677         }\r
678 \r
679         if(this.digestPanel == null){\r
680             this.digestPanel = new VillageDigest(this.topFrame);\r
681             this.digestPanel.pack();\r
682             this.digestPanel.setSize(600, 550);\r
683             this.windowMap.put(this.digestPanel, false);\r
684         }\r
685 \r
686         final VillageDigest digest = this.digestPanel;\r
687         Executor executor = Executors.newCachedThreadPool();\r
688         executor.execute(new Runnable(){\r
689             public void run(){\r
690                 taskFullOpenAllPeriod();\r
691                 EventQueue.invokeLater(new Runnable(){\r
692                     public void run(){\r
693                         digest.setVillage(village);\r
694                         digest.setVisible(true);\r
695                         return;\r
696                     }\r
697                 });\r
698                 return;\r
699             }\r
700         });\r
701 \r
702         return;\r
703     }\r
704 \r
705     /**\r
706      * 全日程の一括フルオープン。ヘビータスク版。\r
707      */\r
708     // TODO taskLoadAllPeriodtと一体化したい。\r
709     private void taskFullOpenAllPeriod(){\r
710         setBusy(true);\r
711         updateStatusBar("一括読み込み開始");\r
712         try{\r
713             TabBrowser browser = this.topView.getTabBrowser();\r
714             Village village = browser.getVillage();\r
715             if(village == null) return;\r
716             for(PeriodView periodView : browser.getPeriodViewList()){\r
717                 Period period = periodView.getPeriod();\r
718                 if(period == null) continue;\r
719                 if(period.isFullOpen()) continue;\r
720                 String message =\r
721                         period.getDay()\r
722                         + "日目のデータを読み込んでいます";\r
723                 updateStatusBar(message);\r
724                 try{\r
725                     Period.parsePeriod(period, true);\r
726                 }catch(IOException e){\r
727                     showNetworkError(village, e);\r
728                     return;\r
729                 }\r
730                 periodView.showTopics();\r
731             }\r
732         }finally{\r
733             updateStatusBar("一括読み込み完了");\r
734             setBusy(false);\r
735         }\r
736         return;\r
737     }\r
738 \r
739     /**\r
740      * 検索パネルを表示する。\r
741      */\r
742     private void actionShowFind(){\r
743         this.findPanel.setVisible(true);\r
744         if(this.findPanel.isCanceled()){\r
745             updateFindPanel();\r
746             return;\r
747         }\r
748         if(this.findPanel.isBulkSearch()){\r
749             bulkSearch();\r
750         }else{\r
751             regexSearch();\r
752         }\r
753         return;\r
754     }\r
755 \r
756     /**\r
757      * 検索処理。\r
758      */\r
759     private void regexSearch(){\r
760         Discussion discussion = currentDiscussion();\r
761         if(discussion == null) return;\r
762 \r
763         RegexPattern regPattern = this.findPanel.getRegexPattern();\r
764         int hits = discussion.setRegexPattern(regPattern);\r
765 \r
766         String hitMessage = "[" + hits + "]件ヒットしました";\r
767         updateStatusBar(hitMessage);\r
768 \r
769         String loginfo = "";\r
770         if(regPattern != null){\r
771             Pattern pattern = regPattern.getPattern();\r
772             if(pattern != null){\r
773                 loginfo = "正規表現 " + pattern.pattern() + " に";\r
774             }\r
775         }\r
776         loginfo += hitMessage;\r
777         Jindolf.logger().info(loginfo);\r
778 \r
779         return;\r
780     }\r
781 \r
782     /**\r
783      * 一括検索処理。\r
784      */\r
785     private void bulkSearch(){\r
786         Executor executor = Executors.newCachedThreadPool();\r
787         executor.execute(new Runnable(){\r
788             public void run(){\r
789                 taskBulkSearch();\r
790                 return;\r
791             }\r
792         });\r
793     }\r
794 \r
795     /**\r
796      * 一括検索処理。ヘビータスク版。\r
797      */\r
798     private void taskBulkSearch(){\r
799         taskLoadAllPeriod();\r
800         int totalhits = 0;\r
801         RegexPattern regPattern = this.findPanel.getRegexPattern();\r
802         StringBuilder hitDesc = new StringBuilder();\r
803         TabBrowser browser = this.topView.getTabBrowser();\r
804         for(PeriodView periodView : browser.getPeriodViewList()){\r
805             Discussion discussion = periodView.getDiscussion();\r
806             int hits = discussion.setRegexPattern(regPattern);\r
807             totalhits += hits;\r
808 \r
809             if(hits > 0){\r
810                 Period period = discussion.getPeriod();\r
811                 hitDesc.append(' ').append(period.getDay()).append("d:");\r
812                 hitDesc.append(hits).append("件");\r
813             }\r
814         }\r
815         String hitMessage =\r
816                   "[" + totalhits + "]件ヒットしました。"\r
817                 + hitDesc.toString();\r
818         updateStatusBar(hitMessage);\r
819 \r
820         String loginfo = "";\r
821         if(regPattern != null){\r
822             Pattern pattern = regPattern.getPattern();\r
823             if(pattern != null){\r
824                 loginfo = "正規表現 " + pattern.pattern() + " に";\r
825             }\r
826         }\r
827         loginfo += hitMessage;\r
828         Jindolf.logger().info(loginfo);\r
829 \r
830         return;\r
831     }\r
832 \r
833     /**\r
834      * 検索パネルに現在選択中のPeriodを反映させる。\r
835      */\r
836     private void updateFindPanel(){\r
837         Discussion discussion = currentDiscussion();\r
838         if(discussion == null) return;\r
839         RegexPattern pattern = discussion.getRegexPattern();\r
840         this.findPanel.setRegexPattern(pattern);\r
841         return;\r
842     }\r
843 \r
844     /**\r
845      * 発言集計パネルを表示。\r
846      */\r
847     private void actionDaySummary(){\r
848         PeriodView periodView = currentPeriodView();\r
849         if(periodView == null) return;\r
850 \r
851         Period period = periodView.getPeriod();\r
852         if(period == null) return;\r
853 \r
854         if(this.daySummaryPanel == null){\r
855             this.daySummaryPanel = new DaySummary(this.topFrame);\r
856             this.daySummaryPanel.pack();\r
857             this.daySummaryPanel.setSize(400, 500);\r
858         }\r
859 \r
860         this.daySummaryPanel.summaryPeriod(period);\r
861         this.daySummaryPanel.setVisible(true);\r
862 \r
863         this.windowMap.put(this.daySummaryPanel, false);\r
864 \r
865         return;\r
866     }\r
867 \r
868     /**\r
869      * 表示中PeriodをCSVファイルへエクスポートする。\r
870      */\r
871     private void actionDayExportCsv(){\r
872         PeriodView periodView = currentPeriodView();\r
873         if(periodView == null) return;\r
874 \r
875         Period period = periodView.getPeriod();\r
876         if(period == null) return;\r
877 \r
878         File file = CsvExporter.exportPeriod(period, this.filterFrame);\r
879         if(file != null){\r
880             String message = "CSVファイル("\r
881                             +file.getName()\r
882                             +")へのエクスポートが完了しました";\r
883             updateStatusBar(message);\r
884         }\r
885 \r
886         // TODO 長そうなジョブなら別スレッドにした方がいいか?\r
887 \r
888         return;\r
889     }\r
890 \r
891     /**\r
892      * 検索結果の次候補へジャンプ。\r
893      */\r
894     private void actionSearchNext(){\r
895         Discussion discussion = currentDiscussion();\r
896         if(discussion == null) return;\r
897 \r
898         discussion.nextHotTarget();\r
899 \r
900         return;\r
901     }\r
902 \r
903     /**\r
904      * 検索結果の全候補へジャンプ。\r
905      */\r
906     private void actionSearchPrev(){\r
907         Discussion discussion = currentDiscussion();\r
908         if(discussion == null) return;\r
909 \r
910         discussion.prevHotTarget();\r
911 \r
912         return;\r
913     }\r
914 \r
915     /**\r
916      * Period表示の強制再更新処理。\r
917      */\r
918     private void actionReloadPeriod(){\r
919         updatePeriod(true);\r
920 \r
921         TabBrowser tabBrowser = this.topView.getTabBrowser();\r
922         Village village = tabBrowser.getVillage();\r
923         if(village == null) return;\r
924         if(village.getState() != VillageState.EPILOGUE) return;\r
925 \r
926         Discussion discussion = currentDiscussion();\r
927         if(discussion == null) return;\r
928         Period period = discussion.getPeriod();\r
929         if(period == null) return;\r
930         if(period.getTopics() > 1000){\r
931             JOptionPane.showMessageDialog(this.topFrame,\r
932                     "エピローグが1000発言を超えはじめたら、\n"\r
933                     +"負荷対策のためWebブラウザによるアクセスを"\r
934                     +"心がけましょう",\r
935                     "長大エピローグ警告",\r
936                     JOptionPane.WARNING_MESSAGE\r
937                     );\r
938         }\r
939 \r
940         return;\r
941     }\r
942 \r
943     /**\r
944      * 全日程の一括ロード。\r
945      */\r
946     private void actionLoadAllPeriod(){\r
947         Executor executor = Executors.newCachedThreadPool();\r
948         executor.execute(new Runnable(){\r
949             public void run(){\r
950                 taskLoadAllPeriod();\r
951                 return;\r
952             }\r
953         });\r
954 \r
955         return;\r
956     }\r
957 \r
958     /**\r
959      * 全日程の一括ロード。ヘビータスク版。\r
960      */\r
961     private void taskLoadAllPeriod(){\r
962         setBusy(true);\r
963         updateStatusBar("一括読み込み開始");\r
964         try{\r
965             TabBrowser browser = this.topView.getTabBrowser();\r
966             Village village = browser.getVillage();\r
967             if(village == null) return;\r
968             for(PeriodView periodView : browser.getPeriodViewList()){\r
969                 Period period = periodView.getPeriod();\r
970                 if(period == null) continue;\r
971                 String message =\r
972                         period.getDay()\r
973                         + "日目のデータを読み込んでいます";\r
974                 updateStatusBar(message);\r
975                 try{\r
976                     Period.parsePeriod(period, false);\r
977                 }catch(IOException e){\r
978                     showNetworkError(village, e);\r
979                     return;\r
980                 }\r
981                 periodView.showTopics();\r
982             }\r
983         }finally{\r
984             updateStatusBar("一括読み込み完了");\r
985             setBusy(false);\r
986         }\r
987         return;\r
988     }\r
989 \r
990     /**\r
991      * 村一覧の再読み込み。\r
992      */\r
993     private void actionReloadVillageList(){\r
994         JTree tree = this.topView.getTreeView();\r
995         TreePath path = tree.getSelectionPath();\r
996         if(path == null) return;\r
997 \r
998         Land land = null;\r
999         for(int ct = 0; ct < path.getPathCount(); ct++){\r
1000             Object obj = path.getPathComponent(ct);\r
1001             if(obj instanceof Land){\r
1002                 land = (Land) obj;\r
1003                 break;\r
1004             }\r
1005         }\r
1006         if(land == null) return;\r
1007 \r
1008         this.topView.showInitPanel();\r
1009 \r
1010         execReloadVillageList(land);\r
1011 \r
1012         return;\r
1013     }\r
1014 \r
1015     /**\r
1016      * 選択文字列をクリップボードにコピーする。\r
1017      */\r
1018     private void actionCopySelected(){\r
1019         Discussion discussion = currentDiscussion();\r
1020         if(discussion == null) return;\r
1021 \r
1022         CharSequence copied = discussion.copySelected();\r
1023         if(copied == null) return;\r
1024 \r
1025         copied = StringUtils.suppressString(copied);\r
1026         updateStatusBar(\r
1027                 "[" + copied + "]をクリップボードにコピーしました");\r
1028         return;\r
1029     }\r
1030 \r
1031     /**\r
1032      * 一発言のみクリップボードにコピーする。\r
1033      */\r
1034     private void actionCopyTalk(){\r
1035         Discussion discussion = currentDiscussion();\r
1036         if(discussion == null) return;\r
1037 \r
1038         CharSequence copied = discussion.copyTalk();\r
1039         if(copied == null) return;\r
1040 \r
1041         copied = StringUtils.suppressString(copied);\r
1042         updateStatusBar(\r
1043                 "[" + copied + "]をクリップボードにコピーしました");\r
1044         return;\r
1045     }\r
1046 \r
1047     /**\r
1048      * アンカーにジャンプする。\r
1049      */\r
1050     private void actionJumpAnchor(){\r
1051         PeriodView periodView = currentPeriodView();\r
1052         if(periodView == null) return;\r
1053         Discussion discussion = periodView.getDiscussion();\r
1054 \r
1055         final TabBrowser browser = this.topView.getTabBrowser();\r
1056         final Village village = browser.getVillage();\r
1057         final Anchor anchor = discussion.getPopupedAnchor();\r
1058         if(anchor == null) return;\r
1059 \r
1060         Executor executor = Executors.newCachedThreadPool();\r
1061         executor.execute(new Runnable(){\r
1062             public void run(){\r
1063                 setBusy(true);\r
1064                 updateStatusBar("ジャンプ先の読み込み中…");\r
1065 \r
1066                 if(anchor.hasTalkNo()){\r
1067                     // TODO もう少し賢くならない?\r
1068                     taskLoadAllPeriod();\r
1069                 }\r
1070 \r
1071                 final List<Talk> talkList;\r
1072                 try{\r
1073                     talkList = village.getTalkListFromAnchor(anchor);\r
1074                     if(talkList == null || talkList.size() <= 0){\r
1075                         updateStatusBar(\r
1076                                   "アンカーのジャンプ先["\r
1077                                 + anchor.toString()\r
1078                                 + "]が見つかりません");\r
1079                         return;\r
1080                     }\r
1081 \r
1082                     final Talk targetTalk = talkList.get(0);\r
1083                     final Period targetPeriod = targetTalk.getPeriod();\r
1084                     final int tabIndex = targetPeriod.getDay() + 1;\r
1085                     final PeriodView target = browser.getPeriodView(tabIndex);\r
1086 \r
1087                     EventQueue.invokeLater(new Runnable(){\r
1088                         public void run(){\r
1089                             browser.setSelectedIndex(tabIndex);\r
1090                             target.setPeriod(targetPeriod);\r
1091                             target.scrollToTalk(targetTalk);\r
1092                             return;\r
1093                         }\r
1094                     });\r
1095                     updateStatusBar(\r
1096                               "アンカー["\r
1097                             + anchor.toString()\r
1098                             + "]にジャンプしました");\r
1099                 }catch(IOException e){\r
1100                     updateStatusBar(\r
1101                             "アンカーの展開中にエラーが起きました");\r
1102                 }finally{\r
1103                     setBusy(false);\r
1104                 }\r
1105 \r
1106                 return;\r
1107             }\r
1108         });\r
1109 \r
1110         return;\r
1111     }\r
1112 \r
1113     /**\r
1114      * 指定した国の村一覧を読み込む。\r
1115      * @param land 国\r
1116      */\r
1117     private void execReloadVillageList(final Land land){\r
1118         final LandsTree treePanel = this.topView.getLandsTree();\r
1119         Executor executor = Executors.newCachedThreadPool();\r
1120         executor.execute(new Runnable(){\r
1121             public void run(){\r
1122                 setBusy(true);\r
1123                 updateStatusBar("村一覧を読み込み中…");\r
1124                 try{\r
1125                     try{\r
1126                         Controller.this.model.loadVillageList(land);\r
1127                     }catch(IOException e){\r
1128                         showNetworkError(land, e);\r
1129                     }\r
1130                     treePanel.expandLand(land);\r
1131                 }finally{\r
1132                     updateStatusBar("村一覧の読み込み完了");\r
1133                     setBusy(false);\r
1134                 }\r
1135                 return;\r
1136             }\r
1137         });\r
1138         return;\r
1139     }\r
1140 \r
1141     /**\r
1142      * Period表示の更新処理。\r
1143      * @param force trueならPeriodデータを強制再読み込み。\r
1144      */\r
1145     private void updatePeriod(final boolean force){\r
1146         final TabBrowser tabBrowser = this.topView.getTabBrowser();\r
1147         final Village village = tabBrowser.getVillage();\r
1148         if(village == null) return;\r
1149         setFrameTitle(village.getVillageFullName());\r
1150 \r
1151         final PeriodView periodView = currentPeriodView();\r
1152         Discussion discussion = currentDiscussion();\r
1153         if(discussion == null) return;\r
1154         discussion.setTopicFilter(this.filterFrame);\r
1155         final Period period = discussion.getPeriod();\r
1156         if(period == null) return;\r
1157 \r
1158         Executor executor = Executors.newCachedThreadPool();\r
1159         executor.execute(new Runnable(){\r
1160             public void run(){\r
1161                 setBusy(true);\r
1162                 try{\r
1163                     boolean wasHot = loadPeriod();\r
1164 \r
1165                     if(wasHot && ! period.isHot() ){\r
1166                         if( ! updatePeriodList() ) return;\r
1167                     }\r
1168 \r
1169                     renderBrowser();\r
1170                 }finally{\r
1171                     setBusy(false);\r
1172                 }\r
1173                 return;\r
1174             }\r
1175 \r
1176             private boolean loadPeriod(){\r
1177                 updateStatusBar("1日分のデータを読み込んでいます…");\r
1178                 boolean wasHot;\r
1179                 try{\r
1180                     wasHot = period.isHot();\r
1181                     try{\r
1182                         Period.parsePeriod(period, force);\r
1183                     }catch(IOException e){\r
1184                         showNetworkError(village, e);\r
1185                     }\r
1186                 }finally{\r
1187                     updateStatusBar("1日分のデータを読み終わりました");\r
1188                 }\r
1189                 return wasHot;\r
1190             }\r
1191 \r
1192             private boolean updatePeriodList(){\r
1193                 updateStatusBar("村情報を読み直しています…");\r
1194                 try{\r
1195                     Village.updateVillage(village);\r
1196                 }catch(IOException e){\r
1197                     showNetworkError(village, e);\r
1198                     return false;\r
1199                 }\r
1200                 try{\r
1201                     SwingUtilities.invokeAndWait(new Runnable(){\r
1202                         public void run(){\r
1203                             tabBrowser.setVillage(village);\r
1204                             return;\r
1205                         }\r
1206                     });\r
1207                 }catch(InvocationTargetException e){\r
1208                     Jindolf.logger().fatal(\r
1209                             "タブ操作で致命的な障害が発生しました", e);\r
1210                 }catch(InterruptedException e){\r
1211                     Jindolf.logger().fatal(\r
1212                             "タブ操作で致命的な障害が発生しました", e);\r
1213                 }\r
1214                 updateStatusBar("村情報を読み直しました…");\r
1215                 return true;\r
1216             }\r
1217 \r
1218             private void renderBrowser(){\r
1219                 updateStatusBar("レンダリング中…");\r
1220                 try{\r
1221                     final int lastPos = periodView.getVerticalPosition();\r
1222                     try{\r
1223                         SwingUtilities.invokeAndWait(new Runnable(){\r
1224                             public void run(){\r
1225                                 periodView.showTopics();\r
1226                                 return;\r
1227                             }\r
1228                         });\r
1229                     }catch(InvocationTargetException e){\r
1230                         Jindolf.logger().fatal(\r
1231                                 "ブラウザ表示で致命的な障害が発生しました", e);\r
1232                     }catch(InterruptedException e){\r
1233                         Jindolf.logger().fatal(\r
1234                                 "ブラウザ表示で致命的な障害が発生しました", e);\r
1235                     }\r
1236                     EventQueue.invokeLater(new Runnable(){\r
1237                         public void run(){\r
1238                             periodView.setVerticalPosition(lastPos);\r
1239                         }\r
1240                     });\r
1241                 }finally{\r
1242                     updateStatusBar("レンダリング完了");\r
1243                 }\r
1244                 return;\r
1245             }\r
1246         });\r
1247 \r
1248         return;\r
1249     }\r
1250 \r
1251     /**\r
1252      * 発言フィルタの操作による更新処理。\r
1253      */\r
1254     private void filterChanged(){\r
1255         final Discussion discussion = currentDiscussion();\r
1256         if(discussion == null) return;\r
1257         discussion.setTopicFilter(this.filterFrame);\r
1258 \r
1259         Executor executor = Executors.newCachedThreadPool();\r
1260         executor.execute(new Runnable(){\r
1261             public void run(){\r
1262                 setBusy(true);\r
1263                 updateStatusBar("フィルタリング中…");\r
1264                 try{\r
1265                     discussion.filtering();\r
1266                 }finally{\r
1267                     updateStatusBar("フィルタリング完了");\r
1268                     setBusy(false);\r
1269                 }\r
1270                 return;\r
1271             }\r
1272         });\r
1273 \r
1274         return;\r
1275     }\r
1276 \r
1277     /**\r
1278      * 現在選択中のPeriodを内包するPeriodViewを返す。\r
1279      * @return PeriodView\r
1280      */\r
1281     private PeriodView currentPeriodView(){\r
1282         TabBrowser tb = this.topView.getTabBrowser();\r
1283         PeriodView result = tb.currentPeriodView();\r
1284         return result;\r
1285     }\r
1286 \r
1287     /**\r
1288      * 現在選択中のPeriodを内包するDiscussionを返す。\r
1289      * @return Discussion\r
1290      */\r
1291     private Discussion currentDiscussion(){\r
1292         PeriodView periodView = currentPeriodView();\r
1293         if(periodView == null) return null;\r
1294         Discussion result = periodView.getDiscussion();\r
1295         return result;\r
1296     }\r
1297 \r
1298     /**\r
1299      * フレーム表示のトグル処理。\r
1300      * @param window フレーム\r
1301      */\r
1302     private void toggleWindow(Window window){\r
1303         if(window == null) return;\r
1304 \r
1305         if(window instanceof Frame){\r
1306             Frame frame = (Frame) window;\r
1307             int winState = frame.getExtendedState();\r
1308             boolean isIconified = (winState & Frame.ICONIFIED) != 0;\r
1309             if(isIconified){\r
1310                 winState &= ~(Frame.ICONIFIED);\r
1311                 frame.setExtendedState(winState);\r
1312                 frame.setVisible(true);\r
1313                 return;\r
1314             }\r
1315         }\r
1316 \r
1317         if(window.isVisible()){\r
1318             window.setVisible(false);\r
1319             window.dispose();\r
1320         }else{\r
1321             window.setVisible(true);\r
1322         }\r
1323         return;\r
1324     }\r
1325 \r
1326     /**\r
1327      * ネットワークエラーを通知するモーダルダイアログを表示する。\r
1328      * OKボタンを押すまでこのメソッドは戻ってこない。\r
1329      * @param village 村\r
1330      * @param e ネットワークエラー\r
1331      */\r
1332     public void showNetworkError(Village village, IOException e){\r
1333         Land land = village.getParentLand();\r
1334         showNetworkError(land, e);\r
1335         return;\r
1336     }\r
1337 \r
1338     /**\r
1339      * ネットワークエラーを通知するモーダルダイアログを表示する。\r
1340      * OKボタンを押すまでこのメソッドは戻ってこない。\r
1341      * @param land 国\r
1342      * @param e ネットワークエラー\r
1343      */\r
1344     public void showNetworkError(Land land, IOException e){\r
1345         Jindolf.logger().warn("ネットワークで障害が発生しました", e);\r
1346 \r
1347         ServerAccess server = land.getServerAccess();\r
1348         String message =\r
1349                 land.getLandDef().getLandName()\r
1350                 +"を運営するサーバとの間の通信で"\r
1351                 +"何らかのトラブルが発生しました。\n"\r
1352                 +"相手サーバのURLは [ " + server.getBaseURL() + " ] だよ。\n"\r
1353                 +"プロクシ設定は正しいかな?\n"\r
1354                 +"Webブラウザでも遊べないか確認してみてね!\n";\r
1355 \r
1356         JOptionPane pane = new JOptionPane(message,\r
1357                                            JOptionPane.WARNING_MESSAGE,\r
1358                                            JOptionPane.DEFAULT_OPTION );\r
1359 \r
1360         JDialog dialog = pane.createDialog(this.topFrame,\r
1361                                            "通信異常発生 - " + Jindolf.TITLE);\r
1362 \r
1363         dialog.pack();\r
1364         dialog.setVisible(true);\r
1365         dialog.dispose();\r
1366 \r
1367         return;\r
1368     }\r
1369 \r
1370     /**\r
1371      * {@inheritDoc}\r
1372      * ツリーリストで何らかの要素(国、村)がクリックされたときの処理。\r
1373      * @param event イベント {@inheritDoc}\r
1374      */\r
1375     @Override\r
1376     public void valueChanged(TreeSelectionEvent event){\r
1377         TreePath path = event.getNewLeadSelectionPath();\r
1378         if(path == null) return;\r
1379 \r
1380         Object selObj = path.getLastPathComponent();\r
1381 \r
1382         if( selObj instanceof Land ){\r
1383             Land land = (Land)selObj;\r
1384             setFrameTitle(land.getLandDef().getLandName());\r
1385             this.topView.showLandInfo(land);\r
1386             this.actionManager.appearVillage(false);\r
1387             this.actionManager.appearPeriod(false);\r
1388         }else if( selObj instanceof Village ){\r
1389             final Village village = (Village)selObj;\r
1390 \r
1391             Executor executor = Executors.newCachedThreadPool();\r
1392             executor.execute(new Runnable(){\r
1393                 public void run(){\r
1394                     setBusy(true);\r
1395                     updateStatusBar("村情報を読み込み中…");\r
1396 \r
1397                     try{\r
1398                         Village.updateVillage(village);\r
1399                     }catch(IOException e){\r
1400                         showNetworkError(village, e);\r
1401                         return;\r
1402                     }finally{\r
1403                         updateStatusBar("村情報の読み込み完了");\r
1404                         setBusy(false);\r
1405                     }\r
1406 \r
1407                     Controller.this.actionManager.appearVillage(true);\r
1408                     setFrameTitle(village.getVillageFullName());\r
1409 \r
1410                     EventQueue.invokeLater(new Runnable(){\r
1411                         public void run(){\r
1412                             Controller.this.topView.showVillageInfo(village);\r
1413                             return;\r
1414                         }\r
1415                     });\r
1416 \r
1417                     return;\r
1418                 }\r
1419             });\r
1420         }\r
1421 \r
1422         return;\r
1423     }\r
1424 \r
1425     /**\r
1426      * {@inheritDoc}\r
1427      * Periodがタブ選択されたときもしくは発言フィルタが操作されたときの処理。\r
1428      * @param event イベント {@inheritDoc}\r
1429      */\r
1430     @Override\r
1431     public void stateChanged(ChangeEvent event){\r
1432         Object source = event.getSource();\r
1433 \r
1434         if(source == this.filterFrame){\r
1435             filterChanged();\r
1436         }else if(source instanceof TabBrowser){\r
1437             updateFindPanel();\r
1438             updatePeriod(false);\r
1439             PeriodView periodView = currentPeriodView();\r
1440             if(periodView == null) this.actionManager.appearPeriod(false);\r
1441             else                   this.actionManager.appearPeriod(true);\r
1442         }\r
1443         return;\r
1444     }\r
1445 \r
1446     /**\r
1447      * {@inheritDoc}\r
1448      * 主にメニュー選択やボタン押下など。\r
1449      * @param e イベント {@inheritDoc}\r
1450      */\r
1451     @Override\r
1452     public void actionPerformed(ActionEvent e){\r
1453         if(this.isBusyNow) return;\r
1454 \r
1455         String cmd = e.getActionCommand();\r
1456         if(cmd.equals(ActionManager.CMD_ACCOUNT)){\r
1457             actionShowAccount();\r
1458         }else if(cmd.equals(ActionManager.CMD_EXIT)){\r
1459             actionExit();\r
1460         }else if(cmd.equals(ActionManager.CMD_COPY)){\r
1461             actionCopySelected();\r
1462         }else if(cmd.equals(ActionManager.CMD_SHOWFIND)){\r
1463             actionShowFind();\r
1464         }else if(cmd.equals(ActionManager.CMD_SEARCHNEXT)){\r
1465             actionSearchNext();\r
1466         }else if(cmd.equals(ActionManager.CMD_SEARCHPREV)){\r
1467             actionSearchPrev();\r
1468         }else if(cmd.equals(ActionManager.CMD_ALLPERIOD)){\r
1469             actionLoadAllPeriod();\r
1470         }else if(cmd.equals(ActionManager.CMD_SHOWDIGEST)){\r
1471             actionShowDigest();\r
1472         }else if(cmd.equals(ActionManager.CMD_WEBVILL)){\r
1473             actionShowWebVillage();\r
1474         }else if(cmd.equals(ActionManager.CMD_WEBWIKI)){\r
1475             actionShowWebWiki();\r
1476         }else if(cmd.equals(ActionManager.CMD_WEBCAST)){\r
1477             actionShowWebCast();\r
1478         }else if(cmd.equals(ActionManager.CMD_RELOAD)){\r
1479             actionReloadPeriod();\r
1480         }else if(cmd.equals(ActionManager.CMD_DAYSUMMARY)){\r
1481             actionDaySummary();\r
1482         }else if(cmd.equals(ActionManager.CMD_DAYEXPCSV)){\r
1483             actionDayExportCsv();\r
1484         }else if(cmd.equals(ActionManager.CMD_WEBDAY)){\r
1485             actionShowWebDay();\r
1486         }else if(cmd.equals(ActionManager.CMD_OPTION)){\r
1487             actionOption();\r
1488         }else if(cmd.equals(ActionManager.CMD_LANDF)){\r
1489             actionChangeLaF();\r
1490         }else if(cmd.equals(ActionManager.CMD_SHOWFILT)){\r
1491             actionShowFilter();\r
1492         }else if(cmd.equals(ActionManager.CMD_SHOWEDIT)){\r
1493             actionTalkPreview();\r
1494         }else if(cmd.equals(ActionManager.CMD_SHOWLOG)){\r
1495             actionShowLog();\r
1496         }else if(cmd.equals(ActionManager.CMD_HELPDOC)){\r
1497             actionHelp();\r
1498         }else if(cmd.equals(ActionManager.CMD_SHOWPORTAL)){\r
1499             actionShowPortal();\r
1500         }else if(cmd.equals(ActionManager.CMD_ABOUT)){\r
1501             actionAbout();\r
1502         }else if(cmd.equals(ActionManager.CMD_VILLAGELIST)){\r
1503             actionReloadVillageList();\r
1504         }else if(cmd.equals(ActionManager.CMD_COPYTALK)){\r
1505             actionCopyTalk();\r
1506         }else if(cmd.equals(ActionManager.CMD_JUMPANCHOR)){\r
1507             actionJumpAnchor();\r
1508         }else if(cmd.equals(ActionManager.CMD_WEBTALK)){\r
1509             actionShowWebTalk();\r
1510         }\r
1511         return;\r
1512     }\r
1513 \r
1514     /**\r
1515      * {@inheritDoc}\r
1516      * 村選択ツリーリストが畳まれるとき呼ばれる。\r
1517      * @param event ツリーイベント {@inheritDoc}\r
1518      */\r
1519     @Override\r
1520     public void treeWillCollapse(TreeExpansionEvent event){\r
1521         return;\r
1522     }\r
1523 \r
1524     /**\r
1525      * {@inheritDoc}\r
1526      * 村選択ツリーリストが展開されるとき呼ばれる。\r
1527      * @param event ツリーイベント {@inheritDoc}\r
1528      */\r
1529     @Override\r
1530     public void treeWillExpand(TreeExpansionEvent event){\r
1531         if(!(event.getSource() instanceof JTree)){\r
1532             return;\r
1533         }\r
1534 \r
1535         TreePath path = event.getPath();\r
1536         Object lastObj = path.getLastPathComponent();\r
1537         if(!(lastObj instanceof Land)){\r
1538             return;\r
1539         }\r
1540         final Land land = (Land) lastObj;\r
1541         if(land.getVillageCount() > 0){\r
1542             return;\r
1543         }\r
1544 \r
1545         execReloadVillageList(land);\r
1546 \r
1547         return;\r
1548     }\r
1549 \r
1550     /**\r
1551      * {@inheritDoc}\r
1552      * @param event {@inheritDoc}\r
1553      */\r
1554     @Override\r
1555     public void anchorHitted(AnchorHitEvent event){\r
1556         PeriodView periodView = currentPeriodView();\r
1557         if(periodView == null) return;\r
1558         Period period = periodView.getPeriod();\r
1559         if(period == null) return;\r
1560         final Village village = period.getVillage();\r
1561 \r
1562         final TalkDraw talkDraw = event.getTalkDraw();\r
1563         final Anchor anchor = event.getAnchor();\r
1564         final Discussion discussion = periodView.getDiscussion();\r
1565 \r
1566         Executor executor = Executors.newCachedThreadPool();\r
1567         executor.execute(new Runnable(){\r
1568             public void run(){\r
1569                 setBusy(true);\r
1570                 updateStatusBar("アンカーの展開中…");\r
1571 \r
1572                 if(anchor.hasTalkNo()){\r
1573                     // TODO もう少し賢くならない?\r
1574                     taskLoadAllPeriod();\r
1575                 }\r
1576 \r
1577                 final List<Talk> talkList;\r
1578                 try{\r
1579                     talkList = village.getTalkListFromAnchor(anchor);\r
1580                     if(talkList == null || talkList.size() <= 0){\r
1581                         updateStatusBar(\r
1582                                   "アンカーの展開先["\r
1583                                 + anchor.toString()\r
1584                                 + "]が見つかりません");\r
1585                         return;\r
1586                     }\r
1587                     EventQueue.invokeLater(new Runnable(){\r
1588                         public void run(){\r
1589                             talkDraw.showAnchorTalks(anchor, talkList);\r
1590                             discussion.layoutRows();\r
1591                             return;\r
1592                         }\r
1593                     });\r
1594                     updateStatusBar(\r
1595                               "アンカー["\r
1596                             + anchor.toString()\r
1597                             + "]の展開完了");\r
1598                 }catch(IOException e){\r
1599                     updateStatusBar(\r
1600                             "アンカーの展開中にエラーが起きました");\r
1601                 }finally{\r
1602                     setBusy(false);\r
1603                 }\r
1604 \r
1605                 return;\r
1606             }\r
1607         });\r
1608 \r
1609         return;\r
1610     }\r
1611 \r
1612     /**\r
1613      * ヘビーなタスク実行をアピール。\r
1614      * プログレスバーとカーソルの設定を行う。\r
1615      * @param isBusy trueならプログレスバーのアニメ開始&WAITカーソル。\r
1616      *                falseなら停止&通常カーソル。\r
1617      */\r
1618     private void setBusy(final boolean isBusy){\r
1619         this.isBusyNow = isBusy;\r
1620 \r
1621         Runnable microJob = new Runnable(){\r
1622             public void run(){\r
1623                 Cursor cursor;\r
1624                 if(isBusy){\r
1625                     cursor = Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR);\r
1626                 }else{\r
1627                     cursor = Cursor.getDefaultCursor();\r
1628                 }\r
1629 \r
1630                 Component glass = Controller.this.topFrame.getGlassPane();\r
1631                 glass.setCursor(cursor);\r
1632                 glass.setVisible(isBusy);\r
1633                 Controller.this.topView.setBusy(isBusy);\r
1634 \r
1635                 return;\r
1636             }\r
1637         };\r
1638 \r
1639         if(SwingUtilities.isEventDispatchThread()){\r
1640             microJob.run();\r
1641         }else{\r
1642             try{\r
1643                 SwingUtilities.invokeAndWait(microJob);\r
1644             }catch(InvocationTargetException e){\r
1645                 Jindolf.logger().fatal("ビジー処理で失敗", e);\r
1646             }catch(InterruptedException e){\r
1647                 Jindolf.logger().fatal("ビジー処理で失敗", e);\r
1648             }\r
1649         }\r
1650 \r
1651         return;\r
1652     }\r
1653 \r
1654     /**\r
1655      * ステータスバーを更新する。\r
1656      * @param message メッセージ\r
1657      */\r
1658     private void updateStatusBar(String message){\r
1659         this.topView.updateSysMessage(message);\r
1660     }\r
1661 \r
1662     /**\r
1663      * トップフレームのタイトルを設定する。\r
1664      * タイトルは指定された国or村名 + " - Jindolf"\r
1665      * @param name 国or村名\r
1666      */\r
1667     private void setFrameTitle(CharSequence name){\r
1668         String title = Jindolf.TITLE;\r
1669 \r
1670         if(name != null && name.length() > 0){\r
1671             title = name + " - " + title;\r
1672         }\r
1673 \r
1674         this.topFrame.setTitle(title);\r
1675 \r
1676         return;\r
1677     }\r
1678 \r
1679     /**\r
1680      * アプリ正常終了処理。\r
1681      */\r
1682     private void shutdown(){\r
1683         this.findPanel.saveHistory();\r
1684         this.talkPreview.saveDraft();\r
1685         Jindolf.getAppSetting().saveConfig();\r
1686         Jindolf.exit(0);\r
1687         return;\r
1688     }\r
1689 \r
1690 }\r