4 * Copyright(c) 2008 olyutorskii
\r
5 * $Id: Controller.java 999 2010-03-15 11:59:28Z 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.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
61 * いわゆるMVCでいうとこのコントローラ。
\r
63 public class Controller
\r
64 implements ActionListener,
\r
65 TreeWillExpandListener,
\r
66 TreeSelectionListener,
\r
70 private final ActionManager actionManager;
\r
71 private final TopView topView;
\r
72 private final LandsModel model;
\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
86 private volatile boolean isBusyNow;
\r
88 private JFrame topFrame = null;
\r
92 * @param actionManager アクション管理
\r
93 * @param topView 最上位ビュー
\r
94 * @param model 最上位データモデル
\r
96 public Controller(ActionManager actionManager,
\r
101 this.actionManager = actionManager;
\r
102 this.topView = topView;
\r
103 this.model = model;
\r
105 JToolBar toolbar = this.actionManager.getBrowseToolBar();
\r
106 this.topView.setBrowseToolBar(toolbar);
\r
108 this.actionManager.addActionListener(this);
\r
110 JTree treeView = this.topView.getTreeView();
\r
111 treeView.setModel(this.model);
\r
112 treeView.addTreeWillExpandListener(this);
\r
113 treeView.addTreeSelectionListener(this);
\r
115 this.topView.getTabBrowser().addChangeListener(this);
\r
116 this.topView.getTabBrowser().addActionListener(this);
\r
117 this.topView.getTabBrowser().addAnchorHitListener(this);
\r
119 JButton reloadVillageListButton = this.topView
\r
121 .getReloadVillageListButton();
\r
122 reloadVillageListButton.addActionListener(this);
\r
123 reloadVillageListButton.setEnabled(false);
\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
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
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
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
159 this.findPanel = new FindPanel(this.topFrame);
\r
160 this.findPanel.pack();
\r
161 this.findPanel.setVisible(false);
\r
162 this.findPanel.loadHistory();
\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
170 AppSetting setting = Jindolf.getAppSetting();
\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
177 ProxyInfo proxyInfo = setting.getProxyInfo();
\r
178 this.optionPanel.getProxyChooser().setProxyInfo(proxyInfo);
\r
180 DialogPref pref = setting.getDialogPref();
\r
181 this.topView.getTabBrowser().setDialogPref(pref);
\r
182 this.optionPanel.getDialogPrefPanel().setDialogPref(pref);
\r
191 @SuppressWarnings("serial")
\r
192 public JFrame createTopFrame(){
\r
193 this.topFrame = new JFrame();
\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
200 Component glassPane = new JComponent() {};
\r
201 glassPane.addMouseListener(new MouseAdapter() {});
\r
202 glassPane.addKeyListener(new KeyAdapter() {});
\r
203 this.topFrame.setGlassPane(glassPane);
\r
205 this.topFrame.setJMenuBar(this.actionManager.getMenuBar());
\r
206 setFrameTitle(null);
\r
208 this.windowMap.put(this.topFrame, false);
\r
210 this.topFrame.setDefaultCloseOperation(
\r
211 WindowConstants.DISPOSE_ON_CLOSE);
\r
212 this.topFrame.addWindowListener(new WindowAdapter(){
\r
214 public void windowClosed(WindowEvent event){
\r
219 return this.topFrame;
\r
225 private void actionAbout(){
\r
228 + " Version " + Jindolf.VERSION + "\n"
\r
229 + Jindolf.COPYRIGHT + "\n"
\r
230 + "ライセンス: " + Jindolf.LICENSE + "\n"
\r
231 + "連絡先: " + Jindolf.CONTACT;
\r
233 if(Jindolf.COMMENT.length() > 0){
\r
234 message += "\n" + Jindolf.COMMENT;
\r
237 JOptionPane pane = new JOptionPane(message,
\r
238 JOptionPane.INFORMATION_MESSAGE,
\r
239 JOptionPane.DEFAULT_OPTION,
\r
240 GUIUtils.getLogoIcon());
\r
242 JDialog dialog = pane.createDialog(this.topFrame,
\r
243 Jindolf.TITLE + "について");
\r
246 dialog.setVisible(true);
\r
255 private void actionExit(){
\r
263 private void actionHelp(){
\r
264 if(this.helpFrame != null){ // show Toggle
\r
265 toggleWindow(this.helpFrame);
\r
269 this.helpFrame = new HelpFrame();
\r
270 this.helpFrame.pack();
\r
271 this.helpFrame.setSize(450, 450);
\r
273 this.windowMap.put(this.helpFrame, false);
\r
275 this.helpFrame.setVisible(true);
\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
288 Land land = village.getParentLand();
\r
289 ServerAccess server = land.getServerAccess();
\r
291 URL url = server.getVillageURL(village);
\r
293 String urlText = url.toString();
\r
294 if(village.getState() != VillageState.GAMEOVER){
\r
295 urlText += "#bottom";
\r
298 WebIPCDialog.showDialog(this.topFrame, urlText);
\r
304 * 村に対応するまとめサイトをWebブラウザで表示する。
\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
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
318 villageName = village.getVillageName();
\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
327 WebIPCDialog.showDialog(this.topFrame, url.toString());
\r
333 * 村に対応するキャスト紹介表ジェネレーターをWebブラウザで表示する。
\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
340 Land land = village.getParentLand();
\r
341 ServerAccess server = land.getServerAccess();
\r
343 URL villageUrl = server.getVillageURL(village);
\r
345 StringBuilder url = new StringBuilder("http://hon5.com/jinro/");
\r
349 .append(URLEncoder.encode(villageUrl.toString(), "UTF-8"));
\r
350 }catch(UnsupportedEncodingException e){
\r
354 url.append("&s=1");
\r
356 WebIPCDialog.showDialog(this.topFrame, url.toString());
\r
362 * 日(Period)をWebブラウザで表示する。
\r
364 private void actionShowWebDay(){
\r
365 PeriodView periodView = currentPeriodView();
\r
366 if(periodView == null) return;
\r
368 Period period = periodView.getPeriod();
\r
369 if(period == null) return;
\r
371 TabBrowser browser = this.topView.getTabBrowser();
\r
372 Village village = browser.getVillage();
\r
373 if(village == null) return;
\r
375 Land land = village.getParentLand();
\r
376 ServerAccess server = land.getServerAccess();
\r
378 URL url = server.getPeriodURL(period);
\r
380 String urlText = url.toString();
\r
381 if(period.isHot()) urlText += "#bottom";
\r
383 WebIPCDialog.showDialog(this.topFrame, urlText);
\r
389 * 個別の発言をWebブラウザで表示する。
\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
396 PeriodView periodView = currentPeriodView();
\r
397 if(periodView == null) return;
\r
399 Discussion discussion = periodView.getDiscussion();
\r
400 Talk talk = discussion.getPopupedTalk();
\r
401 if(talk == null) return;
\r
403 Period period = periodView.getPeriod();
\r
404 if(period == null) return;
\r
406 Land land = village.getParentLand();
\r
407 ServerAccess server = land.getServerAccess();
\r
409 URL url = server.getPeriodURL(period);
\r
411 String urlText = url.toString();
\r
412 urlText += "#" + talk.getMessageID();
\r
413 WebIPCDialog.showDialog(this.topFrame, urlText);
\r
419 * ポータルサイトをWebブラウザで表示する。
\r
421 private void actionShowPortal(){
\r
422 WebIPCDialog.showDialog(this.topFrame, Jindolf.CONTACT);
\r
430 private void actionChangeLaF(){
\r
431 String className = this.actionManager.getSelectedLookAndFeel();
\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
440 + "]を読み込む事ができません。";
\r
441 Jindolf.logger().warn(message, e);
\r
442 JOptionPane.showMessageDialog(
\r
445 "Look&Feel - " + Jindolf.TITLE,
\r
446 JOptionPane.WARNING_MESSAGE );
\r
451 UIManager.setLookAndFeel(lnf);
\r
452 }catch(UnsupportedLookAndFeelException e){
\r
453 String message = "このLook&Feel["
\r
455 + "]はサポートされていません。";
\r
456 Jindolf.logger().warn(message, e);
\r
457 JOptionPane.showMessageDialog(
\r
460 "Look&Feel - " + Jindolf.TITLE,
\r
461 JOptionPane.WARNING_MESSAGE );
\r
465 Jindolf.logger().info(
\r
470 final Runnable updateUITask = new Runnable(){
\r
472 Set<Window> windows = Controller.this.windowMap.keySet();
\r
473 for(Window window : windows){
\r
474 SwingUtilities.updateComponentTreeUI(window);
\r
476 boolean needPack = Controller.this.windowMap.get(window);
\r
486 Executor executor = Executors.newCachedThreadPool();
\r
487 executor.execute(new Runnable(){
\r
490 updateStatusBar("Look&Feelを更新中…");
\r
492 SwingUtilities.invokeAndWait(updateUITask);
\r
493 }catch(Exception e){
\r
494 Jindolf.logger().warn(
\r
495 "Look&Feelの更新に失敗しました。", e);
\r
497 updateStatusBar("Look&Feelが更新されました");
\r
510 private void actionShowFilter(){
\r
511 toggleWindow(this.filterFrame);
\r
518 private void actionShowAccount(){
\r
519 if(this.accountFrame != null){ // show Toggle
\r
520 toggleWindow(this.accountFrame);
\r
524 this.accountFrame = new AccountPanel(this.topFrame, this.model);
\r
525 this.accountFrame.pack();
\r
526 this.accountFrame.setVisible(true);
\r
528 this.windowMap.put(this.accountFrame, true);
\r
536 private void actionShowLog(){
\r
537 toggleWindow(this.showlogFrame);
\r
544 private void actionTalkPreview(){
\r
545 toggleWindow(this.talkPreview);
\r
552 private void actionOption(){
\r
553 AppSetting setting = Jindolf.getAppSetting();
\r
555 FontInfo fontInfo = setting.getFontInfo();
\r
556 this.optionPanel.getFontChooser().setFontInfo(fontInfo);
\r
558 ProxyInfo proxyInfo = setting.getProxyInfo();
\r
559 this.optionPanel.getProxyChooser().setProxyInfo(proxyInfo);
\r
561 DialogPref dialogPref = setting.getDialogPref();
\r
562 this.optionPanel.getDialogPrefPanel().setDialogPref(dialogPref);
\r
564 this.optionPanel.setVisible(true);
\r
565 if(this.optionPanel.isCanceled()) return;
\r
567 fontInfo = this.optionPanel.getFontChooser().getFontInfo();
\r
568 updateFontInfo(fontInfo);
\r
570 proxyInfo = this.optionPanel.getProxyChooser().getProxyInfo();
\r
571 updateProxyInfo(proxyInfo);
\r
573 dialogPref = this.optionPanel.getDialogPrefPanel().getDialogPref();
\r
574 updateDialogPref(dialogPref);
\r
581 * @param newFontInfo 新フォント設定
\r
583 private void updateFontInfo(final FontInfo newFontInfo){
\r
584 AppSetting setting = Jindolf.getAppSetting();
\r
585 FontInfo oldInfo = setting.getFontInfo();
\r
587 if(newFontInfo.equals(oldInfo)) return;
\r
588 setting.setFontInfo(newFontInfo);
\r
590 this.topView.getTabBrowser().setFontInfo(newFontInfo);
\r
591 this.talkPreview.setFontInfo(newFontInfo);
\r
592 this.optionPanel.getFontChooser().setFontInfo(newFontInfo);
\r
599 * @param newProxyInfo 新プロクシ設定
\r
601 private void updateProxyInfo(ProxyInfo newProxyInfo){
\r
602 AppSetting setting = Jindolf.getAppSetting();
\r
603 ProxyInfo oldProxyInfo = setting.getProxyInfo();
\r
605 if(newProxyInfo.equals(oldProxyInfo)) return;
\r
606 setting.setProxyInfo(newProxyInfo);
\r
608 for(Land land : this.model.getLandList()){
\r
609 ServerAccess server = land.getServerAccess();
\r
610 server.setProxy(newProxyInfo.getProxy());
\r
618 * @param newDialogPref 表示設定
\r
620 private void updateDialogPref(DialogPref newDialogPref){
\r
621 AppSetting setting = Jindolf.getAppSetting();
\r
622 DialogPref oldDialogPref = setting.getDialogPref();
\r
624 if(newDialogPref.equals(oldDialogPref)) return;
\r
625 setting.setDialogPref(newDialogPref);
\r
627 this.topView.getTabBrowser().setDialogPref(newDialogPref);
\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
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
652 dialog.setVisible(true);
\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
664 final VillageDigest digest = this.digestPanel;
\r
665 Executor executor = Executors.newCachedThreadPool();
\r
666 executor.execute(new Runnable(){
\r
668 taskFullOpenAllPeriod();
\r
669 EventQueue.invokeLater(new Runnable(){
\r
671 digest.setVillage(village);
\r
672 digest.setVisible(true);
\r
684 * 全日程の一括フルオープン。ヘビータスク版。
\r
686 // TODO taskLoadAllPeriodtと一体化したい。
\r
687 private void taskFullOpenAllPeriod(){
\r
689 updateStatusBar("一括読み込み開始");
\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
700 + "日目のデータを読み込んでいます";
\r
701 updateStatusBar(message);
\r
703 Period.parsePeriod(period, true);
\r
704 }catch(IOException e){
\r
705 showNetworkError(village, e);
\r
708 periodView.showTopics();
\r
711 updateStatusBar("一括読み込み完了");
\r
720 private void actionShowFind(){
\r
721 this.findPanel.setVisible(true);
\r
722 if(this.findPanel.isCanceled()){
\r
726 if(this.findPanel.isBulkSearch()){
\r
737 private void regexSearch(){
\r
738 Discussion discussion = currentDiscussion();
\r
739 if(discussion == null) return;
\r
741 RegexPattern regPattern = this.findPanel.getRegexPattern();
\r
742 int hits = discussion.setRegexPattern(regPattern);
\r
744 String hitMessage = "[" + hits + "]件ヒットしました";
\r
745 updateStatusBar(hitMessage);
\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
754 loginfo += hitMessage;
\r
755 Jindolf.logger().info(loginfo);
\r
763 private void bulkSearch(){
\r
764 Executor executor = Executors.newCachedThreadPool();
\r
765 executor.execute(new Runnable(){
\r
776 private void taskBulkSearch(){
\r
777 taskLoadAllPeriod();
\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
788 Period period = discussion.getPeriod();
\r
789 hitDesc.append(' ').append(period.getDay()).append("d:");
\r
790 hitDesc.append(hits).append("件");
\r
793 String hitMessage =
\r
794 "[" + totalhits + "]件ヒットしました。"
\r
795 + hitDesc.toString();
\r
796 updateStatusBar(hitMessage);
\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
805 loginfo += hitMessage;
\r
806 Jindolf.logger().info(loginfo);
\r
812 * 検索パネルに現在選択中のPeriodを反映させる。
\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
825 private void actionDaySummary(){
\r
826 PeriodView periodView = currentPeriodView();
\r
827 if(periodView == null) return;
\r
829 Period period = periodView.getPeriod();
\r
830 if(period == null) return;
\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
838 this.daySummaryPanel.summaryPeriod(period);
\r
839 this.daySummaryPanel.setVisible(true);
\r
841 this.windowMap.put(this.daySummaryPanel, false);
\r
847 * 表示中PeriodをCSVファイルへエクスポートする。
\r
849 private void actionDayExportCsv(){
\r
850 PeriodView periodView = currentPeriodView();
\r
851 if(periodView == null) return;
\r
853 Period period = periodView.getPeriod();
\r
854 if(period == null) return;
\r
856 File file = CsvExporter.exportPeriod(period, this.filterFrame);
\r
858 String message = "CSVファイル("
\r
860 +")へのエクスポートが完了しました";
\r
861 updateStatusBar(message);
\r
864 // TODO 長そうなジョブなら別スレッドにした方がいいか?
\r
872 private void actionSearchNext(){
\r
873 Discussion discussion = currentDiscussion();
\r
874 if(discussion == null) return;
\r
876 discussion.nextHotTarget();
\r
884 private void actionSearchPrev(){
\r
885 Discussion discussion = currentDiscussion();
\r
886 if(discussion == null) return;
\r
888 discussion.prevHotTarget();
\r
894 * Period表示の強制再更新処理。
\r
896 private void actionReloadPeriod(){
\r
897 updatePeriod(true);
\r
904 private void actionLoadAllPeriod(){
\r
905 Executor executor = Executors.newCachedThreadPool();
\r
906 executor.execute(new Runnable(){
\r
908 taskLoadAllPeriod();
\r
917 * 全日程の一括ロード。ヘビータスク版。
\r
919 private void taskLoadAllPeriod(){
\r
921 updateStatusBar("一括読み込み開始");
\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
931 + "日目のデータを読み込んでいます";
\r
932 updateStatusBar(message);
\r
934 Period.parsePeriod(period, false);
\r
935 }catch(IOException e){
\r
936 showNetworkError(village, e);
\r
939 periodView.showTopics();
\r
942 updateStatusBar("一括読み込み完了");
\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
957 for(int ct = 0; ct < path.getPathCount(); ct++){
\r
958 Object obj = path.getPathComponent(ct);
\r
959 if(obj instanceof Land){
\r
964 if(land == null) return;
\r
966 this.topView.showInitPanel();
\r
968 execReloadVillageList(land);
\r
974 * 選択文字列をクリップボードにコピーする。
\r
976 private void actionCopySelected(){
\r
977 Discussion discussion = currentDiscussion();
\r
978 if(discussion == null) return;
\r
980 CharSequence copied = discussion.copySelected();
\r
981 if(copied == null) return;
\r
983 copied = StringUtils.suppressString(copied);
\r
985 "[" + copied + "]をクリップボードにコピーしました");
\r
990 * 一発言のみクリップボードにコピーする。
\r
992 private void actionCopyTalk(){
\r
993 Discussion discussion = currentDiscussion();
\r
994 if(discussion == null) return;
\r
996 CharSequence copied = discussion.copyTalk();
\r
997 if(copied == null) return;
\r
999 copied = StringUtils.suppressString(copied);
\r
1001 "[" + copied + "]をクリップボードにコピーしました");
\r
1008 private void actionJumpAnchor(){
\r
1009 PeriodView periodView = currentPeriodView();
\r
1010 if(periodView == null) return;
\r
1011 Discussion discussion = periodView.getDiscussion();
\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
1018 Executor executor = Executors.newCachedThreadPool();
\r
1019 executor.execute(new Runnable(){
\r
1020 public void run(){
\r
1022 updateStatusBar("ジャンプ先の読み込み中…");
\r
1024 if(anchor.hasTalkNo()){
\r
1025 // TODO もう少し賢くならない?
\r
1026 taskLoadAllPeriod();
\r
1029 final List<Talk> talkList;
\r
1031 talkList = village.getTalkListFromAnchor(anchor);
\r
1032 if(talkList == null || talkList.size() <= 0){
\r
1035 + anchor.toString()
\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
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
1055 + anchor.toString()
\r
1057 }catch(IOException e){
\r
1059 "アンカーの展開中にエラーが起きました");
\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
1081 updateStatusBar("村一覧を読み込み中…");
\r
1084 Controller.this.model.loadVillageList(land);
\r
1085 }catch(IOException e){
\r
1086 showNetworkError(land, e);
\r
1088 treePanel.expandLand(land);
\r
1090 updateStatusBar("村一覧の読み込み完了");
\r
1101 * @param force trueならPeriodデータを強制再読み込み。
\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
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
1116 Executor executor = Executors.newCachedThreadPool();
\r
1117 executor.execute(new Runnable(){
\r
1118 public void run(){
\r
1121 boolean wasHot = loadPeriod();
\r
1123 if(wasHot && ! period.isHot() ){
\r
1124 if( ! updatePeriodList() ) return;
\r
1134 private boolean loadPeriod(){
\r
1135 updateStatusBar("1日分のデータを読み込んでいます…");
\r
1138 wasHot = period.isHot();
\r
1140 Period.parsePeriod(period, force);
\r
1141 }catch(IOException e){
\r
1142 showNetworkError(village, e);
\r
1145 updateStatusBar("1日分のデータを読み終わりました");
\r
1150 private boolean updatePeriodList(){
\r
1151 updateStatusBar("村情報を読み直しています…");
\r
1153 Village.updateVillage(village);
\r
1154 }catch(IOException e){
\r
1155 showNetworkError(village, e);
\r
1159 SwingUtilities.invokeAndWait(new Runnable(){
\r
1160 public void run(){
\r
1161 tabBrowser.setVillage(village);
\r
1165 }catch(Exception e){
\r
1166 Jindolf.logger().fatal(
\r
1167 "タブ操作で致命的な障害が発生しました", e);
\r
1169 updateStatusBar("村情報を読み直しました…");
\r
1173 private void renderBrowser(){
\r
1174 updateStatusBar("レンダリング中…");
\r
1176 final int lastPos = periodView.getVerticalPosition();
\r
1178 SwingUtilities.invokeAndWait(new Runnable(){
\r
1179 public void run(){
\r
1180 periodView.showTopics();
\r
1184 }catch(Exception e){
\r
1185 Jindolf.logger().fatal(
\r
1186 "ブラウザ表示で致命的な障害が発生しました", e);
\r
1188 EventQueue.invokeLater(new Runnable(){
\r
1189 public void run(){
\r
1190 periodView.setVerticalPosition(lastPos);
\r
1194 updateStatusBar("レンダリング完了");
\r
1204 * 発言フィルタの操作による更新処理。
\r
1206 private void filterChanged(){
\r
1207 final Discussion discussion = currentDiscussion();
\r
1208 if(discussion == null) return;
\r
1209 discussion.setTopicFilter(this.filterFrame);
\r
1211 Executor executor = Executors.newCachedThreadPool();
\r
1212 executor.execute(new Runnable(){
\r
1213 public void run(){
\r
1215 updateStatusBar("フィルタリング中…");
\r
1217 discussion.filtering();
\r
1219 updateStatusBar("フィルタリング完了");
\r
1230 * 現在選択中のPeriodを内包するPeriodViewを返す。
\r
1231 * @return PeriodView
\r
1233 private PeriodView currentPeriodView(){
\r
1234 TabBrowser tb = this.topView.getTabBrowser();
\r
1235 PeriodView result = tb.currentPeriodView();
\r
1240 * 現在選択中のPeriodを内包するDiscussionを返す。
\r
1241 * @return Discussion
\r
1243 private Discussion currentDiscussion(){
\r
1244 PeriodView periodView = currentPeriodView();
\r
1245 if(periodView == null) return null;
\r
1246 Discussion result = periodView.getDiscussion();
\r
1252 * @param window フレーム
\r
1254 private void toggleWindow(Window window){
\r
1255 if(window == null) return;
\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
1262 winState &= ~(Frame.ICONIFIED);
\r
1263 frame.setExtendedState(winState);
\r
1264 frame.setVisible(true);
\r
1269 if(window.isVisible()){
\r
1270 window.setVisible(false);
\r
1273 window.setVisible(true);
\r
1279 * ネットワークエラーを通知するモーダルダイアログを表示する。
\r
1280 * OKボタンを押すまでこのメソッドは戻ってこない。
\r
1281 * @param village 村
\r
1282 * @param e ネットワークエラー
\r
1284 public void showNetworkError(Village village, IOException e){
\r
1285 Land land = village.getParentLand();
\r
1286 showNetworkError(land, e);
\r
1291 * ネットワークエラーを通知するモーダルダイアログを表示する。
\r
1292 * OKボタンを押すまでこのメソッドは戻ってこない。
\r
1294 * @param e ネットワークエラー
\r
1296 public void showNetworkError(Land land, IOException e){
\r
1297 Jindolf.logger().warn("ネットワークで障害が発生しました", e);
\r
1299 ServerAccess server = land.getServerAccess();
\r
1301 land.getLandDef().getLandName()
\r
1302 +"を運営するサーバとの間の通信で"
\r
1303 +"何らかのトラブルが発生しました。\n"
\r
1304 +"相手サーバのURLは [ " + server.getBaseURL() + " ] だよ。\n"
\r
1305 +"プロクシ設定は正しいかな?\n"
\r
1306 +"Webブラウザでも遊べないか確認してみてね!\n";
\r
1308 JOptionPane pane = new JOptionPane(message,
\r
1309 JOptionPane.WARNING_MESSAGE,
\r
1310 JOptionPane.DEFAULT_OPTION );
\r
1312 JDialog dialog = pane.createDialog(this.topFrame,
\r
1313 "通信異常発生 - " + Jindolf.TITLE);
\r
1316 dialog.setVisible(true);
\r
1324 * ツリーリストで何らかの要素(国、村)がクリックされたときの処理。
\r
1325 * @param event イベント {@inheritDoc}
\r
1327 public void valueChanged(TreeSelectionEvent event){
\r
1328 TreePath path = event.getNewLeadSelectionPath();
\r
1329 if(path == null) return;
\r
1331 Object selObj = path.getLastPathComponent();
\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
1342 Executor executor = Executors.newCachedThreadPool();
\r
1343 executor.execute(new Runnable(){
\r
1344 public void run(){
\r
1346 updateStatusBar("村情報を読み込み中…");
\r
1349 Village.updateVillage(village);
\r
1350 }catch(IOException e){
\r
1351 showNetworkError(village, e);
\r
1354 updateStatusBar("村情報の読み込み完了");
\r
1358 Controller.this.actionManager.appearVillage(true);
\r
1359 setFrameTitle(village.getVillageFullName());
\r
1361 EventQueue.invokeLater(new Runnable(){
\r
1362 public void run(){
\r
1363 Controller.this.topView.showVillageInfo(village);
\r
1378 * Periodがタブ選択されたときもしくは発言フィルタが操作されたときの処理。
\r
1379 * @param event イベント {@inheritDoc}
\r
1381 public void stateChanged(ChangeEvent event){
\r
1382 Object source = event.getSource();
\r
1384 if(source == this.filterFrame){
\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
1398 * 主にメニュー選択やボタン押下など。
\r
1399 * @param e イベント {@inheritDoc}
\r
1401 public void actionPerformed(ActionEvent e){
\r
1402 if(this.isBusyNow) return;
\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
1409 }else if(cmd.equals(ActionManager.CMD_COPY)){
\r
1410 actionCopySelected();
\r
1411 }else if(cmd.equals(ActionManager.CMD_SHOWFIND)){
\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
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
1445 }else if(cmd.equals(ActionManager.CMD_HELPDOC)){
\r
1447 }else if(cmd.equals(ActionManager.CMD_SHOWPORTAL)){
\r
1448 actionShowPortal();
\r
1449 }else if(cmd.equals(ActionManager.CMD_ABOUT)){
\r
1451 }else if(cmd.equals(ActionManager.CMD_VILLAGELIST)){
\r
1452 actionReloadVillageList();
\r
1453 }else if(cmd.equals(ActionManager.CMD_COPYTALK)){
\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
1465 * 村選択ツリーリストが畳まれるとき呼ばれる。
\r
1466 * @param event ツリーイベント {@inheritDoc}
\r
1468 public void treeWillCollapse(TreeExpansionEvent event){
\r
1474 * 村選択ツリーリストが展開されるとき呼ばれる。
\r
1475 * @param event ツリーイベント {@inheritDoc}
\r
1477 public void treeWillExpand(TreeExpansionEvent event){
\r
1478 if(!(event.getSource() instanceof JTree)){
\r
1482 TreePath path = event.getPath();
\r
1483 Object lastObj = path.getLastPathComponent();
\r
1484 if(!(lastObj instanceof Land)){
\r
1487 final Land land = (Land) lastObj;
\r
1488 if(land.getVillageCount() > 0){
\r
1492 execReloadVillageList(land);
\r
1499 * @param event {@inheritDoc}
\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
1508 final TalkDraw talkDraw = event.getTalkDraw();
\r
1509 final Anchor anchor = event.getAnchor();
\r
1510 final Discussion discussion = periodView.getDiscussion();
\r
1512 Executor executor = Executors.newCachedThreadPool();
\r
1513 executor.execute(new Runnable(){
\r
1514 public void run(){
\r
1516 updateStatusBar("アンカーの展開中…");
\r
1518 if(anchor.hasTalkNo()){
\r
1519 // TODO もう少し賢くならない?
\r
1520 taskLoadAllPeriod();
\r
1523 final List<Talk> talkList;
\r
1525 talkList = village.getTalkListFromAnchor(anchor);
\r
1526 if(talkList == null || talkList.size() <= 0){
\r
1529 + anchor.toString()
\r
1533 EventQueue.invokeLater(new Runnable(){
\r
1534 public void run(){
\r
1535 talkDraw.showAnchorTalks(anchor, talkList);
\r
1536 discussion.layoutRows();
\r
1542 + anchor.toString()
\r
1544 }catch(IOException e){
\r
1546 "アンカーの展開中にエラーが起きました");
\r
1560 * プログレスバーとカーソルの設定を行う。
\r
1561 * @param isBusy trueならプログレスバーのアニメ開始&WAITカーソル。
\r
1562 * falseなら停止&通常カーソル。
\r
1564 private void setBusy(final boolean isBusy){
\r
1565 this.isBusyNow = isBusy;
\r
1567 Runnable microJob = new Runnable(){
\r
1568 public void run(){
\r
1571 cursor = Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR);
\r
1573 cursor = Cursor.getDefaultCursor();
\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
1585 if(SwingUtilities.isEventDispatchThread()){
\r
1589 SwingUtilities.invokeAndWait(microJob);
\r
1590 }catch(Exception e){
\r
1591 Jindolf.logger().fatal("ビジー処理で失敗", e);
\r
1600 * @param message メッセージ
\r
1602 private void updateStatusBar(String message){
\r
1603 this.topView.updateSysMessage(message);
\r
1607 * トップフレームのタイトルを設定する。
\r
1608 * タイトルは指定された国or村名 + " - Jindolf"
\r
1609 * @param name 国or村名
\r
1611 private void setFrameTitle(CharSequence name){
\r
1612 String title = Jindolf.TITLE;
\r
1614 if(name != null && name.length() > 0){
\r
1615 title = name + " - " + title;
\r
1618 this.topFrame.setTitle(title);
\r
1626 private void shutdown(){
\r
1627 this.findPanel.saveHistory();
\r
1628 this.talkPreview.saveDraft();
\r
1629 Jindolf.getAppSetting().saveConfig();
\r