4 * License : The MIT License
\r
5 * Copyright(c) 2008 olyutorskii
\r
8 package jp.sourceforge.jindolf;
\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
62 * いわゆるMVCでいうとこのコントローラ。
\r
64 public class Controller
\r
65 implements ActionListener,
\r
66 TreeWillExpandListener,
\r
67 TreeSelectionListener,
\r
71 private final ActionManager actionManager;
\r
72 private final TopView topView;
\r
73 private final LandsModel model;
\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
87 private volatile boolean isBusyNow;
\r
89 private JFrame topFrame = null;
\r
93 * @param actionManager アクション管理
\r
94 * @param topView 最上位ビュー
\r
95 * @param model 最上位データモデル
\r
97 public Controller(ActionManager actionManager,
\r
102 this.actionManager = actionManager;
\r
103 this.topView = topView;
\r
104 this.model = model;
\r
106 JToolBar toolbar = this.actionManager.getBrowseToolBar();
\r
107 this.topView.setBrowseToolBar(toolbar);
\r
109 this.actionManager.addActionListener(this);
\r
111 JTree treeView = this.topView.getTreeView();
\r
112 treeView.setModel(this.model);
\r
113 treeView.addTreeWillExpandListener(this);
\r
114 treeView.addTreeSelectionListener(this);
\r
116 this.topView.getTabBrowser().addChangeListener(this);
\r
117 this.topView.getTabBrowser().addActionListener(this);
\r
118 this.topView.getTabBrowser().addAnchorHitListener(this);
\r
120 JButton reloadVillageListButton = this.topView
\r
122 .getReloadVillageListButton();
\r
123 reloadVillageListButton.addActionListener(this);
\r
124 reloadVillageListButton.setEnabled(false);
\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
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
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
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
160 this.findPanel = new FindPanel(this.topFrame);
\r
161 this.findPanel.pack();
\r
162 this.findPanel.setVisible(false);
\r
163 this.findPanel.loadHistory();
\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
171 AppSetting setting = Jindolf.getAppSetting();
\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
178 ProxyInfo proxyInfo = setting.getProxyInfo();
\r
179 this.optionPanel.getProxyChooser().setProxyInfo(proxyInfo);
\r
181 DialogPref pref = setting.getDialogPref();
\r
182 this.topView.getTabBrowser().setDialogPref(pref);
\r
183 this.optionPanel.getDialogPrefPanel().setDialogPref(pref);
\r
192 @SuppressWarnings("serial")
\r
193 public JFrame createTopFrame(){
\r
194 this.topFrame = new JFrame();
\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
201 Component glassPane = new JComponent() {};
\r
202 glassPane.addMouseListener(new MouseAdapter() {});
\r
203 glassPane.addKeyListener(new KeyAdapter() {});
\r
204 this.topFrame.setGlassPane(glassPane);
\r
206 this.topFrame.setJMenuBar(this.actionManager.getMenuBar());
\r
207 setFrameTitle(null);
\r
209 this.windowMap.put(this.topFrame, false);
\r
211 this.topFrame.setDefaultCloseOperation(
\r
212 WindowConstants.DISPOSE_ON_CLOSE);
\r
213 this.topFrame.addWindowListener(new WindowAdapter(){
\r
215 public void windowClosed(WindowEvent event){
\r
220 return this.topFrame;
\r
226 private void actionAbout(){
\r
229 + " Version " + Jindolf.VERSION + "\n"
\r
230 + Jindolf.COPYRIGHT + "\n"
\r
231 + "ライセンス: " + Jindolf.LICENSE + "\n"
\r
232 + "連絡先: " + Jindolf.CONTACT;
\r
234 if(Jindolf.COMMENT.length() > 0){
\r
235 message += "\n" + Jindolf.COMMENT;
\r
238 JOptionPane pane = new JOptionPane(message,
\r
239 JOptionPane.INFORMATION_MESSAGE,
\r
240 JOptionPane.DEFAULT_OPTION,
\r
241 GUIUtils.getLogoIcon());
\r
243 JDialog dialog = pane.createDialog(this.topFrame,
\r
244 Jindolf.TITLE + "について");
\r
247 dialog.setVisible(true);
\r
256 private void actionExit(){
\r
264 private void actionHelp(){
\r
265 if(this.helpFrame != null){ // show Toggle
\r
266 toggleWindow(this.helpFrame);
\r
270 this.helpFrame = new HelpFrame();
\r
271 this.helpFrame.pack();
\r
272 this.helpFrame.setSize(450, 450);
\r
274 this.windowMap.put(this.helpFrame, false);
\r
276 this.helpFrame.setVisible(true);
\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
289 Land land = village.getParentLand();
\r
290 ServerAccess server = land.getServerAccess();
\r
292 URL url = server.getVillageURL(village);
\r
294 String urlText = url.toString();
\r
295 if(village.getState() != VillageState.GAMEOVER){
\r
296 urlText += "#bottom";
\r
299 WebIPCDialog.showDialog(this.topFrame, urlText);
\r
305 * 村に対応するまとめサイトをWebブラウザで表示する。
\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
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
319 villageName = village.getVillageName();
\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
328 WebIPCDialog.showDialog(this.topFrame, url.toString());
\r
334 * 村に対応するキャスト紹介表ジェネレーターをWebブラウザで表示する。
\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
341 Land land = village.getParentLand();
\r
342 ServerAccess server = land.getServerAccess();
\r
344 URL villageUrl = server.getVillageURL(village);
\r
346 StringBuilder url = new StringBuilder("http://hon5.com/jinro/");
\r
350 .append(URLEncoder.encode(villageUrl.toString(), "UTF-8"));
\r
351 }catch(UnsupportedEncodingException e){
\r
355 url.append("&s=1");
\r
357 WebIPCDialog.showDialog(this.topFrame, url.toString());
\r
363 * 日(Period)をWebブラウザで表示する。
\r
365 private void actionShowWebDay(){
\r
366 PeriodView periodView = currentPeriodView();
\r
367 if(periodView == null) return;
\r
369 Period period = periodView.getPeriod();
\r
370 if(period == null) return;
\r
372 TabBrowser browser = this.topView.getTabBrowser();
\r
373 Village village = browser.getVillage();
\r
374 if(village == null) return;
\r
376 Land land = village.getParentLand();
\r
377 ServerAccess server = land.getServerAccess();
\r
379 URL url = server.getPeriodURL(period);
\r
381 String urlText = url.toString();
\r
382 if(period.isHot()) urlText += "#bottom";
\r
384 WebIPCDialog.showDialog(this.topFrame, urlText);
\r
390 * 個別の発言をWebブラウザで表示する。
\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
397 PeriodView periodView = currentPeriodView();
\r
398 if(periodView == null) return;
\r
400 Discussion discussion = periodView.getDiscussion();
\r
401 Talk talk = discussion.getPopupedTalk();
\r
402 if(talk == null) return;
\r
404 Period period = periodView.getPeriod();
\r
405 if(period == null) return;
\r
407 Land land = village.getParentLand();
\r
408 ServerAccess server = land.getServerAccess();
\r
410 URL url = server.getPeriodURL(period);
\r
412 String urlText = url.toString();
\r
413 urlText += "#" + talk.getMessageID();
\r
414 WebIPCDialog.showDialog(this.topFrame, urlText);
\r
420 * ポータルサイトをWebブラウザで表示する。
\r
422 private void actionShowPortal(){
\r
423 WebIPCDialog.showDialog(this.topFrame, Jindolf.CONTACT);
\r
428 * 例外発生による警告ダイアログへの反応を促す。
\r
429 * @param title タイトル文字列
\r
430 * @param message メッセージ
\r
433 private void warnDialog(String title, String message, Throwable e){
\r
434 Jindolf.logger().warn(message, e);
\r
435 JOptionPane.showMessageDialog(
\r
438 title + " - " + Jindolf.TITLE,
\r
439 JOptionPane.WARNING_MESSAGE );
\r
446 private void actionChangeLaF(){
\r
447 String className = this.actionManager.getSelectedLookAndFeel();
\r
449 String warnTitle = "Look&Feel";
\r
453 warnMsg = "このLook&Feel[" + className + "]を読み込む事ができません。";
\r
455 lnfClass = Class.forName(className);
\r
456 }catch(ClassNotFoundException e){
\r
457 warnDialog(warnTitle, warnMsg, e);
\r
462 warnMsg = "このLook&Feel[" + className + "]を生成する事ができません。";
\r
464 lnf = (LookAndFeel)( lnfClass.newInstance() );
\r
465 }catch(InstantiationException e){
\r
466 warnDialog(warnTitle, warnMsg, e);
\r
468 }catch(IllegalAccessException e){
\r
469 warnDialog(warnTitle, warnMsg, e);
\r
471 }catch(ClassCastException e){
\r
472 warnDialog(warnTitle, warnMsg, e);
\r
476 warnMsg = "このLook&Feel[" + lnf.getName() + "]はサポートされていません。";
\r
478 UIManager.setLookAndFeel(lnf);
\r
479 }catch(UnsupportedLookAndFeelException e){
\r
480 warnDialog(warnTitle, warnMsg, e);
\r
484 Jindolf.logger().info(
\r
489 final Runnable updateUITask = new Runnable(){
\r
491 Set<Window> windows = Controller.this.windowMap.keySet();
\r
492 for(Window window : windows){
\r
493 SwingUtilities.updateComponentTreeUI(window);
\r
495 boolean needPack = Controller.this.windowMap.get(window);
\r
505 Executor executor = Executors.newCachedThreadPool();
\r
506 executor.execute(new Runnable(){
\r
509 updateStatusBar("Look&Feelを更新中…");
\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
519 updateStatusBar("Look&Feelが更新されました");
\r
532 private void actionShowFilter(){
\r
533 toggleWindow(this.filterFrame);
\r
540 private void actionShowAccount(){
\r
541 if(this.accountFrame != null){ // show Toggle
\r
542 toggleWindow(this.accountFrame);
\r
546 this.accountFrame = new AccountPanel(this.topFrame, this.model);
\r
547 this.accountFrame.pack();
\r
548 this.accountFrame.setVisible(true);
\r
550 this.windowMap.put(this.accountFrame, true);
\r
558 private void actionShowLog(){
\r
559 toggleWindow(this.showlogFrame);
\r
566 private void actionTalkPreview(){
\r
567 toggleWindow(this.talkPreview);
\r
574 private void actionOption(){
\r
575 AppSetting setting = Jindolf.getAppSetting();
\r
577 FontInfo fontInfo = setting.getFontInfo();
\r
578 this.optionPanel.getFontChooser().setFontInfo(fontInfo);
\r
580 ProxyInfo proxyInfo = setting.getProxyInfo();
\r
581 this.optionPanel.getProxyChooser().setProxyInfo(proxyInfo);
\r
583 DialogPref dialogPref = setting.getDialogPref();
\r
584 this.optionPanel.getDialogPrefPanel().setDialogPref(dialogPref);
\r
586 this.optionPanel.setVisible(true);
\r
587 if(this.optionPanel.isCanceled()) return;
\r
589 fontInfo = this.optionPanel.getFontChooser().getFontInfo();
\r
590 updateFontInfo(fontInfo);
\r
592 proxyInfo = this.optionPanel.getProxyChooser().getProxyInfo();
\r
593 updateProxyInfo(proxyInfo);
\r
595 dialogPref = this.optionPanel.getDialogPrefPanel().getDialogPref();
\r
596 updateDialogPref(dialogPref);
\r
603 * @param newFontInfo 新フォント設定
\r
605 private void updateFontInfo(final FontInfo newFontInfo){
\r
606 AppSetting setting = Jindolf.getAppSetting();
\r
607 FontInfo oldInfo = setting.getFontInfo();
\r
609 if(newFontInfo.equals(oldInfo)) return;
\r
610 setting.setFontInfo(newFontInfo);
\r
612 this.topView.getTabBrowser().setFontInfo(newFontInfo);
\r
613 this.talkPreview.setFontInfo(newFontInfo);
\r
614 this.optionPanel.getFontChooser().setFontInfo(newFontInfo);
\r
621 * @param newProxyInfo 新プロクシ設定
\r
623 private void updateProxyInfo(ProxyInfo newProxyInfo){
\r
624 AppSetting setting = Jindolf.getAppSetting();
\r
625 ProxyInfo oldProxyInfo = setting.getProxyInfo();
\r
627 if(newProxyInfo.equals(oldProxyInfo)) return;
\r
628 setting.setProxyInfo(newProxyInfo);
\r
630 for(Land land : this.model.getLandList()){
\r
631 ServerAccess server = land.getServerAccess();
\r
632 server.setProxy(newProxyInfo.getProxy());
\r
640 * @param newDialogPref 表示設定
\r
642 private void updateDialogPref(DialogPref newDialogPref){
\r
643 AppSetting setting = Jindolf.getAppSetting();
\r
644 DialogPref oldDialogPref = setting.getDialogPref();
\r
646 if(newDialogPref.equals(oldDialogPref)) return;
\r
647 setting.setDialogPref(newDialogPref);
\r
649 this.topView.getTabBrowser().setDialogPref(newDialogPref);
\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
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
674 dialog.setVisible(true);
\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
686 final VillageDigest digest = this.digestPanel;
\r
687 Executor executor = Executors.newCachedThreadPool();
\r
688 executor.execute(new Runnable(){
\r
690 taskFullOpenAllPeriod();
\r
691 EventQueue.invokeLater(new Runnable(){
\r
693 digest.setVillage(village);
\r
694 digest.setVisible(true);
\r
706 * 全日程の一括フルオープン。ヘビータスク版。
\r
708 // TODO taskLoadAllPeriodtと一体化したい。
\r
709 private void taskFullOpenAllPeriod(){
\r
711 updateStatusBar("一括読み込み開始");
\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
722 + "日目のデータを読み込んでいます";
\r
723 updateStatusBar(message);
\r
725 Period.parsePeriod(period, true);
\r
726 }catch(IOException e){
\r
727 showNetworkError(village, e);
\r
730 periodView.showTopics();
\r
733 updateStatusBar("一括読み込み完了");
\r
742 private void actionShowFind(){
\r
743 this.findPanel.setVisible(true);
\r
744 if(this.findPanel.isCanceled()){
\r
748 if(this.findPanel.isBulkSearch()){
\r
759 private void regexSearch(){
\r
760 Discussion discussion = currentDiscussion();
\r
761 if(discussion == null) return;
\r
763 RegexPattern regPattern = this.findPanel.getRegexPattern();
\r
764 int hits = discussion.setRegexPattern(regPattern);
\r
766 String hitMessage = "[" + hits + "]件ヒットしました";
\r
767 updateStatusBar(hitMessage);
\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
776 loginfo += hitMessage;
\r
777 Jindolf.logger().info(loginfo);
\r
785 private void bulkSearch(){
\r
786 Executor executor = Executors.newCachedThreadPool();
\r
787 executor.execute(new Runnable(){
\r
798 private void taskBulkSearch(){
\r
799 taskLoadAllPeriod();
\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
810 Period period = discussion.getPeriod();
\r
811 hitDesc.append(' ').append(period.getDay()).append("d:");
\r
812 hitDesc.append(hits).append("件");
\r
815 String hitMessage =
\r
816 "[" + totalhits + "]件ヒットしました。"
\r
817 + hitDesc.toString();
\r
818 updateStatusBar(hitMessage);
\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
827 loginfo += hitMessage;
\r
828 Jindolf.logger().info(loginfo);
\r
834 * 検索パネルに現在選択中のPeriodを反映させる。
\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
847 private void actionDaySummary(){
\r
848 PeriodView periodView = currentPeriodView();
\r
849 if(periodView == null) return;
\r
851 Period period = periodView.getPeriod();
\r
852 if(period == null) return;
\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
860 this.daySummaryPanel.summaryPeriod(period);
\r
861 this.daySummaryPanel.setVisible(true);
\r
863 this.windowMap.put(this.daySummaryPanel, false);
\r
869 * 表示中PeriodをCSVファイルへエクスポートする。
\r
871 private void actionDayExportCsv(){
\r
872 PeriodView periodView = currentPeriodView();
\r
873 if(periodView == null) return;
\r
875 Period period = periodView.getPeriod();
\r
876 if(period == null) return;
\r
878 File file = CsvExporter.exportPeriod(period, this.filterFrame);
\r
880 String message = "CSVファイル("
\r
882 +")へのエクスポートが完了しました";
\r
883 updateStatusBar(message);
\r
886 // TODO 長そうなジョブなら別スレッドにした方がいいか?
\r
894 private void actionSearchNext(){
\r
895 Discussion discussion = currentDiscussion();
\r
896 if(discussion == null) return;
\r
898 discussion.nextHotTarget();
\r
906 private void actionSearchPrev(){
\r
907 Discussion discussion = currentDiscussion();
\r
908 if(discussion == null) return;
\r
910 discussion.prevHotTarget();
\r
916 * Period表示の強制再更新処理。
\r
918 private void actionReloadPeriod(){
\r
919 updatePeriod(true);
\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
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
936 JOptionPane.WARNING_MESSAGE
\r
946 private void actionLoadAllPeriod(){
\r
947 Executor executor = Executors.newCachedThreadPool();
\r
948 executor.execute(new Runnable(){
\r
950 taskLoadAllPeriod();
\r
959 * 全日程の一括ロード。ヘビータスク版。
\r
961 private void taskLoadAllPeriod(){
\r
963 updateStatusBar("一括読み込み開始");
\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
973 + "日目のデータを読み込んでいます";
\r
974 updateStatusBar(message);
\r
976 Period.parsePeriod(period, false);
\r
977 }catch(IOException e){
\r
978 showNetworkError(village, e);
\r
981 periodView.showTopics();
\r
984 updateStatusBar("一括読み込み完了");
\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
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
1006 if(land == null) return;
\r
1008 this.topView.showInitPanel();
\r
1010 execReloadVillageList(land);
\r
1016 * 選択文字列をクリップボードにコピーする。
\r
1018 private void actionCopySelected(){
\r
1019 Discussion discussion = currentDiscussion();
\r
1020 if(discussion == null) return;
\r
1022 CharSequence copied = discussion.copySelected();
\r
1023 if(copied == null) return;
\r
1025 copied = StringUtils.suppressString(copied);
\r
1027 "[" + copied + "]をクリップボードにコピーしました");
\r
1032 * 一発言のみクリップボードにコピーする。
\r
1034 private void actionCopyTalk(){
\r
1035 Discussion discussion = currentDiscussion();
\r
1036 if(discussion == null) return;
\r
1038 CharSequence copied = discussion.copyTalk();
\r
1039 if(copied == null) return;
\r
1041 copied = StringUtils.suppressString(copied);
\r
1043 "[" + copied + "]をクリップボードにコピーしました");
\r
1050 private void actionJumpAnchor(){
\r
1051 PeriodView periodView = currentPeriodView();
\r
1052 if(periodView == null) return;
\r
1053 Discussion discussion = periodView.getDiscussion();
\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
1060 Executor executor = Executors.newCachedThreadPool();
\r
1061 executor.execute(new Runnable(){
\r
1062 public void run(){
\r
1064 updateStatusBar("ジャンプ先の読み込み中…");
\r
1066 if(anchor.hasTalkNo()){
\r
1067 // TODO もう少し賢くならない?
\r
1068 taskLoadAllPeriod();
\r
1071 final List<Talk> talkList;
\r
1073 talkList = village.getTalkListFromAnchor(anchor);
\r
1074 if(talkList == null || talkList.size() <= 0){
\r
1077 + anchor.toString()
\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
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
1097 + anchor.toString()
\r
1099 }catch(IOException e){
\r
1101 "アンカーの展開中にエラーが起きました");
\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
1123 updateStatusBar("村一覧を読み込み中…");
\r
1126 Controller.this.model.loadVillageList(land);
\r
1127 }catch(IOException e){
\r
1128 showNetworkError(land, e);
\r
1130 treePanel.expandLand(land);
\r
1132 updateStatusBar("村一覧の読み込み完了");
\r
1143 * @param force trueならPeriodデータを強制再読み込み。
\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
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
1158 Executor executor = Executors.newCachedThreadPool();
\r
1159 executor.execute(new Runnable(){
\r
1160 public void run(){
\r
1163 boolean wasHot = loadPeriod();
\r
1165 if(wasHot && ! period.isHot() ){
\r
1166 if( ! updatePeriodList() ) return;
\r
1176 private boolean loadPeriod(){
\r
1177 updateStatusBar("1日分のデータを読み込んでいます…");
\r
1180 wasHot = period.isHot();
\r
1182 Period.parsePeriod(period, force);
\r
1183 }catch(IOException e){
\r
1184 showNetworkError(village, e);
\r
1187 updateStatusBar("1日分のデータを読み終わりました");
\r
1192 private boolean updatePeriodList(){
\r
1193 updateStatusBar("村情報を読み直しています…");
\r
1195 Village.updateVillage(village);
\r
1196 }catch(IOException e){
\r
1197 showNetworkError(village, e);
\r
1201 SwingUtilities.invokeAndWait(new Runnable(){
\r
1202 public void run(){
\r
1203 tabBrowser.setVillage(village);
\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
1214 updateStatusBar("村情報を読み直しました…");
\r
1218 private void renderBrowser(){
\r
1219 updateStatusBar("レンダリング中…");
\r
1221 final int lastPos = periodView.getVerticalPosition();
\r
1223 SwingUtilities.invokeAndWait(new Runnable(){
\r
1224 public void run(){
\r
1225 periodView.showTopics();
\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
1236 EventQueue.invokeLater(new Runnable(){
\r
1237 public void run(){
\r
1238 periodView.setVerticalPosition(lastPos);
\r
1242 updateStatusBar("レンダリング完了");
\r
1252 * 発言フィルタの操作による更新処理。
\r
1254 private void filterChanged(){
\r
1255 final Discussion discussion = currentDiscussion();
\r
1256 if(discussion == null) return;
\r
1257 discussion.setTopicFilter(this.filterFrame);
\r
1259 Executor executor = Executors.newCachedThreadPool();
\r
1260 executor.execute(new Runnable(){
\r
1261 public void run(){
\r
1263 updateStatusBar("フィルタリング中…");
\r
1265 discussion.filtering();
\r
1267 updateStatusBar("フィルタリング完了");
\r
1278 * 現在選択中のPeriodを内包するPeriodViewを返す。
\r
1279 * @return PeriodView
\r
1281 private PeriodView currentPeriodView(){
\r
1282 TabBrowser tb = this.topView.getTabBrowser();
\r
1283 PeriodView result = tb.currentPeriodView();
\r
1288 * 現在選択中のPeriodを内包するDiscussionを返す。
\r
1289 * @return Discussion
\r
1291 private Discussion currentDiscussion(){
\r
1292 PeriodView periodView = currentPeriodView();
\r
1293 if(periodView == null) return null;
\r
1294 Discussion result = periodView.getDiscussion();
\r
1300 * @param window フレーム
\r
1302 private void toggleWindow(Window window){
\r
1303 if(window == null) return;
\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
1310 winState &= ~(Frame.ICONIFIED);
\r
1311 frame.setExtendedState(winState);
\r
1312 frame.setVisible(true);
\r
1317 if(window.isVisible()){
\r
1318 window.setVisible(false);
\r
1321 window.setVisible(true);
\r
1327 * ネットワークエラーを通知するモーダルダイアログを表示する。
\r
1328 * OKボタンを押すまでこのメソッドは戻ってこない。
\r
1329 * @param village 村
\r
1330 * @param e ネットワークエラー
\r
1332 public void showNetworkError(Village village, IOException e){
\r
1333 Land land = village.getParentLand();
\r
1334 showNetworkError(land, e);
\r
1339 * ネットワークエラーを通知するモーダルダイアログを表示する。
\r
1340 * OKボタンを押すまでこのメソッドは戻ってこない。
\r
1342 * @param e ネットワークエラー
\r
1344 public void showNetworkError(Land land, IOException e){
\r
1345 Jindolf.logger().warn("ネットワークで障害が発生しました", e);
\r
1347 ServerAccess server = land.getServerAccess();
\r
1349 land.getLandDef().getLandName()
\r
1350 +"を運営するサーバとの間の通信で"
\r
1351 +"何らかのトラブルが発生しました。\n"
\r
1352 +"相手サーバのURLは [ " + server.getBaseURL() + " ] だよ。\n"
\r
1353 +"プロクシ設定は正しいかな?\n"
\r
1354 +"Webブラウザでも遊べないか確認してみてね!\n";
\r
1356 JOptionPane pane = new JOptionPane(message,
\r
1357 JOptionPane.WARNING_MESSAGE,
\r
1358 JOptionPane.DEFAULT_OPTION );
\r
1360 JDialog dialog = pane.createDialog(this.topFrame,
\r
1361 "通信異常発生 - " + Jindolf.TITLE);
\r
1364 dialog.setVisible(true);
\r
1372 * ツリーリストで何らかの要素(国、村)がクリックされたときの処理。
\r
1373 * @param event イベント {@inheritDoc}
\r
1376 public void valueChanged(TreeSelectionEvent event){
\r
1377 TreePath path = event.getNewLeadSelectionPath();
\r
1378 if(path == null) return;
\r
1380 Object selObj = path.getLastPathComponent();
\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
1391 Executor executor = Executors.newCachedThreadPool();
\r
1392 executor.execute(new Runnable(){
\r
1393 public void run(){
\r
1395 updateStatusBar("村情報を読み込み中…");
\r
1398 Village.updateVillage(village);
\r
1399 }catch(IOException e){
\r
1400 showNetworkError(village, e);
\r
1403 updateStatusBar("村情報の読み込み完了");
\r
1407 Controller.this.actionManager.appearVillage(true);
\r
1408 setFrameTitle(village.getVillageFullName());
\r
1410 EventQueue.invokeLater(new Runnable(){
\r
1411 public void run(){
\r
1412 Controller.this.topView.showVillageInfo(village);
\r
1427 * Periodがタブ選択されたときもしくは発言フィルタが操作されたときの処理。
\r
1428 * @param event イベント {@inheritDoc}
\r
1431 public void stateChanged(ChangeEvent event){
\r
1432 Object source = event.getSource();
\r
1434 if(source == this.filterFrame){
\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
1448 * 主にメニュー選択やボタン押下など。
\r
1449 * @param e イベント {@inheritDoc}
\r
1452 public void actionPerformed(ActionEvent e){
\r
1453 if(this.isBusyNow) return;
\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
1460 }else if(cmd.equals(ActionManager.CMD_COPY)){
\r
1461 actionCopySelected();
\r
1462 }else if(cmd.equals(ActionManager.CMD_SHOWFIND)){
\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
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
1496 }else if(cmd.equals(ActionManager.CMD_HELPDOC)){
\r
1498 }else if(cmd.equals(ActionManager.CMD_SHOWPORTAL)){
\r
1499 actionShowPortal();
\r
1500 }else if(cmd.equals(ActionManager.CMD_ABOUT)){
\r
1502 }else if(cmd.equals(ActionManager.CMD_VILLAGELIST)){
\r
1503 actionReloadVillageList();
\r
1504 }else if(cmd.equals(ActionManager.CMD_COPYTALK)){
\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
1516 * 村選択ツリーリストが畳まれるとき呼ばれる。
\r
1517 * @param event ツリーイベント {@inheritDoc}
\r
1520 public void treeWillCollapse(TreeExpansionEvent event){
\r
1526 * 村選択ツリーリストが展開されるとき呼ばれる。
\r
1527 * @param event ツリーイベント {@inheritDoc}
\r
1530 public void treeWillExpand(TreeExpansionEvent event){
\r
1531 if(!(event.getSource() instanceof JTree)){
\r
1535 TreePath path = event.getPath();
\r
1536 Object lastObj = path.getLastPathComponent();
\r
1537 if(!(lastObj instanceof Land)){
\r
1540 final Land land = (Land) lastObj;
\r
1541 if(land.getVillageCount() > 0){
\r
1545 execReloadVillageList(land);
\r
1552 * @param event {@inheritDoc}
\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
1562 final TalkDraw talkDraw = event.getTalkDraw();
\r
1563 final Anchor anchor = event.getAnchor();
\r
1564 final Discussion discussion = periodView.getDiscussion();
\r
1566 Executor executor = Executors.newCachedThreadPool();
\r
1567 executor.execute(new Runnable(){
\r
1568 public void run(){
\r
1570 updateStatusBar("アンカーの展開中…");
\r
1572 if(anchor.hasTalkNo()){
\r
1573 // TODO もう少し賢くならない?
\r
1574 taskLoadAllPeriod();
\r
1577 final List<Talk> talkList;
\r
1579 talkList = village.getTalkListFromAnchor(anchor);
\r
1580 if(talkList == null || talkList.size() <= 0){
\r
1583 + anchor.toString()
\r
1587 EventQueue.invokeLater(new Runnable(){
\r
1588 public void run(){
\r
1589 talkDraw.showAnchorTalks(anchor, talkList);
\r
1590 discussion.layoutRows();
\r
1596 + anchor.toString()
\r
1598 }catch(IOException e){
\r
1600 "アンカーの展開中にエラーが起きました");
\r
1614 * プログレスバーとカーソルの設定を行う。
\r
1615 * @param isBusy trueならプログレスバーのアニメ開始&WAITカーソル。
\r
1616 * falseなら停止&通常カーソル。
\r
1618 private void setBusy(final boolean isBusy){
\r
1619 this.isBusyNow = isBusy;
\r
1621 Runnable microJob = new Runnable(){
\r
1622 public void run(){
\r
1625 cursor = Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR);
\r
1627 cursor = Cursor.getDefaultCursor();
\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
1639 if(SwingUtilities.isEventDispatchThread()){
\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
1656 * @param message メッセージ
\r
1658 private void updateStatusBar(String message){
\r
1659 this.topView.updateSysMessage(message);
\r
1663 * トップフレームのタイトルを設定する。
\r
1664 * タイトルは指定された国or村名 + " - Jindolf"
\r
1665 * @param name 国or村名
\r
1667 private void setFrameTitle(CharSequence name){
\r
1668 String title = Jindolf.TITLE;
\r
1670 if(name != null && name.length() > 0){
\r
1671 title = name + " - " + title;
\r
1674 this.topFrame.setTitle(title);
\r
1682 private void shutdown(){
\r
1683 this.findPanel.saveHistory();
\r
1684 this.talkPreview.saveDraft();
\r
1685 Jindolf.getAppSetting().saveConfig();
\r