OSDN Git Service

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