OSDN Git Service

Merge release/v3.303.106
[jindolf/Jindolf.git] / src / main / java / jp / sfjp / jindolf / editor / TalkPreview.java
1 /*
2  * 発言エディットパネル
3  *
4  * License : The MIT License
5  * Copyright(c) 2008 olyutorskii
6  */
7
8 package jp.sfjp.jindolf.editor;
9
10 import java.awt.BorderLayout;
11 import java.awt.Color;
12 import java.awt.Container;
13 import java.awt.Font;
14 import java.awt.GridBagConstraints;
15 import java.awt.GridBagLayout;
16 import java.awt.Insets;
17 import java.awt.LayoutManager;
18 import java.awt.event.ActionEvent;
19 import java.awt.event.ActionListener;
20 import java.io.File;
21 import javax.swing.BorderFactory;
22 import javax.swing.JButton;
23 import javax.swing.JComponent;
24 import javax.swing.JFrame;
25 import javax.swing.JLabel;
26 import javax.swing.JOptionPane;
27 import javax.swing.JPanel;
28 import javax.swing.JPopupMenu;
29 import javax.swing.JScrollPane;
30 import javax.swing.JSplitPane;
31 import javax.swing.JViewport;
32 import javax.swing.border.BevelBorder;
33 import javax.swing.border.Border;
34 import javax.swing.border.TitledBorder;
35 import javax.swing.event.ChangeEvent;
36 import javax.swing.event.ChangeListener;
37 import javax.swing.text.JTextComponent;
38 import jp.sfjp.jindolf.dxchg.ClipboardAction;
39 import jp.sfjp.jindolf.dxchg.TextPopup;
40 import jp.sfjp.jindolf.glyph.FontInfo;
41 import jp.sfjp.jindolf.util.GUIUtils;
42 import jp.sourceforge.jovsonz.JsArray;
43 import jp.sourceforge.jovsonz.JsComposition;
44 import jp.sourceforge.jovsonz.JsObject;
45 import jp.sourceforge.jovsonz.JsString;
46 import jp.sourceforge.jovsonz.JsTypes;
47 import jp.sourceforge.jovsonz.JsValue;
48
49 /**
50  * 発言エディットパネル。
51  */
52 @SuppressWarnings("serial")
53 public class TalkPreview extends JFrame
54         implements ActionListener, ChangeListener{
55
56     /** 原稿ファイル。 */
57     public static final File DRAFT_FILE = new File("draft.json");
58
59     private static final Color COLOR_EDITORBACK = Color.BLACK;
60
61     private final JTextComponent freeMemo = new TextEditor();
62
63     private final EditArray editArray = new EditArray();
64
65     private final JButton cutButton      = new JButton("カット");
66     private final JButton copyButton     = new JButton("コピー");
67     private final JButton clearButton    = new JButton("クリア");
68     private final JButton cutAllButton   = new JButton("全カット");
69     private final JButton copyAllButton  = new JButton("全コピー");
70     private final JButton clearAllButton = new JButton("全クリア");
71     private final JButton closeButton    = new JButton("閉じる");
72     private final TitledBorder numberBorder =
73             BorderFactory.createTitledBorder("");
74     private final JComponent singleGroup = buildSingleGroup();
75     private final JComponent multiGroup  = buildMultiGroup();
76     private final JLabel letsBrowser =
77             new JLabel("投稿はWebブラウザからどうぞ");
78
79     private JsObject loadedDraft = null;
80
81     /**
82      * コンストラクタ。
83      */
84     @SuppressWarnings("LeakingThisInConstructor")
85     public TalkPreview(){
86         super();
87
88         GUIUtils.modifyWindowAttributes(this, true, false, true);
89
90         setDefaultCloseOperation(HIDE_ON_CLOSE);
91
92         this.cutButton      .addActionListener(this);
93         this.copyButton     .addActionListener(this);
94         this.clearButton    .addActionListener(this);
95         this.cutAllButton   .addActionListener(this);
96         this.copyAllButton  .addActionListener(this);
97         this.clearAllButton .addActionListener(this);
98         this.closeButton    .addActionListener(this);
99
100         this.editArray.addChangeListener(this);
101
102         Container content = getContentPane();
103         design(content);
104
105         setBorderNumber(1);
106
107         return;
108     }
109
110     /**
111      * レイアウトを行う。
112      * @param content コンテナ
113      */
114     private void design(Container content){
115         JComponent freeNotePanel = buildFreeNotePanel();
116
117         JScrollPane scrollPane  = new JScrollPane();
118         scrollPane.setHorizontalScrollBarPolicy(
119                 JScrollPane.HORIZONTAL_SCROLLBAR_NEVER
120         );
121         JViewport viewPort = new JViewport();
122         viewPort.setBackground(COLOR_EDITORBACK);
123         viewPort.setView(this.editArray);
124         scrollPane.setViewport(viewPort);
125
126         LayoutManager layout;
127         Border border;
128
129         JComponent editPanel = new JPanel();
130         layout = new BorderLayout();
131         editPanel.setLayout(layout);
132         editPanel.add(scrollPane, BorderLayout.CENTER);
133         JComponent buttonPanel = buildButtonPanel();
134         editPanel.add(buttonPanel, BorderLayout.EAST);
135         border = BorderFactory.createTitledBorder("発言編集");
136         editPanel.setBorder(border);
137
138         JSplitPane split = new JSplitPane();
139         split.setOrientation(JSplitPane.HORIZONTAL_SPLIT);
140         split.setContinuousLayout(false);
141         split.setDividerSize(10);
142         split.setDividerLocation(200);
143         split.setOneTouchExpandable(true);
144         split.setLeftComponent(freeNotePanel);
145         split.setRightComponent(editPanel);
146
147         Border inside  = BorderFactory.createBevelBorder(BevelBorder.LOWERED);
148         Border outside = BorderFactory.createEmptyBorder(2, 5, 2, 2);
149         border = BorderFactory.createCompoundBorder(inside, outside);
150         this.letsBrowser.setBorder(border);
151
152         layout = new BorderLayout();
153         content.setLayout(layout);
154         content.add(split, BorderLayout.CENTER);
155         content.add(this.letsBrowser, BorderLayout.SOUTH);
156
157         return;
158     }
159
160     /**
161      * ボタン群を生成する。
162      * @return ボタン群
163      */
164     private JComponent buildButtonPanel(){
165         JPanel panel = new JPanel();
166
167         LayoutManager layout = new GridBagLayout();
168         GridBagConstraints constraints = new GridBagConstraints();
169
170         panel.setLayout(layout);
171
172         constraints.weightx = 1.0;
173         constraints.weighty = 0.0;
174         constraints.fill = GridBagConstraints.NONE;
175         constraints.anchor = GridBagConstraints.WEST;
176         constraints.gridx = 1;
177         constraints.gridy = GridBagConstraints.RELATIVE;
178         constraints.gridwidth = 1;
179         constraints.gridheight = 1;
180
181
182         constraints.insets = new Insets(3, 3, 3, 3);
183         panel.add(this.singleGroup, constraints);
184
185         constraints.insets = new Insets(10, 3, 3, 3);
186         panel.add(this.multiGroup, constraints);
187
188         constraints.weighty = 1.0;
189         constraints.anchor = GridBagConstraints.SOUTH;
190         constraints.insets = new Insets(3, 3, 10, 3);
191         panel.add(this.closeButton, constraints);
192
193         return panel;
194     }
195
196     /**
197      * アクティブ発言操作ボタン群を生成する。
198      * @return ボタン群
199      */
200     private JComponent buildSingleGroup(){
201         JComponent panel = new JPanel();
202
203         LayoutManager layout = new GridBagLayout();
204         GridBagConstraints constraints = new GridBagConstraints();
205
206         panel.setLayout(layout);
207
208         constraints.weightx = 1.0;
209         constraints.weighty = 0.0;
210         constraints.fill = GridBagConstraints.HORIZONTAL;
211         constraints.gridx = 1;
212         constraints.gridy = GridBagConstraints.RELATIVE;
213         constraints.gridwidth = 1;
214         constraints.gridheight = 1;
215         constraints.insets = new Insets(3, 3, 3, 3);
216
217         panel.add(this.cutButton,   constraints);
218         panel.add(this.copyButton,  constraints);
219         panel.add(this.clearButton, constraints);
220
221         panel.setBorder(this.numberBorder);
222
223         return panel;
224     }
225
226     /**
227      * 全発言操作ボタン群を生成する。
228      * @return ボタン群
229      */
230     private JComponent buildMultiGroup(){
231         JComponent panel = new JPanel();
232
233         LayoutManager layout = new GridBagLayout();
234         GridBagConstraints constraints = new GridBagConstraints();
235
236         panel.setLayout(layout);
237
238         constraints.weightx = 1.0;
239         constraints.weighty = 0.0;
240         constraints.fill = GridBagConstraints.HORIZONTAL;
241         constraints.gridx = 1;
242         constraints.gridy = GridBagConstraints.RELATIVE;
243         constraints.gridwidth = 1;
244         constraints.gridheight = 1;
245         constraints.insets = new Insets(3, 3, 3, 3);
246
247         panel.add(this.cutAllButton,   constraints);
248         panel.add(this.copyAllButton,  constraints);
249         panel.add(this.clearAllButton, constraints);
250
251         Border border = BorderFactory.createTitledBorder("全発言を");
252         panel.setBorder(border);
253
254         return panel;
255     }
256
257     /**
258      * フリーノート部を生成する。
259      * @return フリーノート部
260      */
261     private JComponent buildFreeNotePanel(){
262         Insets margin = new Insets(3, 3, 3, 3);
263         this.freeMemo.setMargin(margin);
264         JPopupMenu popup = new TextPopup();
265         this.freeMemo.setComponentPopupMenu(popup);
266
267         JScrollPane scrollPane = new JScrollPane();
268         scrollPane.setHorizontalScrollBarPolicy(
269                 JScrollPane.HORIZONTAL_SCROLLBAR_NEVER
270         );
271         JViewport viewPort = new JViewport();
272         viewPort.setView(this.freeMemo);
273         scrollPane.setViewport(viewPort);
274
275         JComponent panel = new JPanel();
276
277         LayoutManager layout = new GridBagLayout();
278         GridBagConstraints constraints = new GridBagConstraints();
279
280         panel.setLayout(layout);
281
282         constraints.weightx = 1.0;
283         constraints.weighty = 1.0;
284         constraints.fill = GridBagConstraints.BOTH;
285         constraints.insets = new Insets(1, 1, 1, 1);
286         panel.add(scrollPane, constraints);
287
288         Border border = BorderFactory.createTitledBorder("フリーメモ");
289         panel.setBorder(border);
290
291         return panel;
292     }
293
294     /**
295      * アクティブ発言の通し番号表示を更新。
296      * @param num 通し番号
297      */
298     private void setBorderNumber(int num){
299         String title = "発言#"+num+" を";
300         this.numberBorder.setTitle(title);
301         this.singleGroup.revalidate();
302         this.singleGroup.repaint();
303         return;
304     }
305
306     /**
307      * テキスト編集用フォントを指定する。
308      * 描画属性は無視される。
309      * @param fontInfo フォント設定
310      */
311     public void setFontInfo(FontInfo fontInfo){
312         setTextFont(fontInfo.getFont());
313         return;
314     }
315
316     /**
317      * テキスト編集用フォントを指定する。
318      * @param textFont フォント
319      */
320     public void setTextFont(Font textFont){
321         this.freeMemo.setFont(textFont);
322         this.editArray.setTextFont(textFont);
323         return;
324     }
325
326     /**
327      * テキスト編集用フォントを取得する。
328      * @return フォント
329      */
330     public Font getTextFont(){
331         return this.editArray.getTextFont();
332     }
333
334     /**
335      * 発言クリア操作の確認ダイアログを表示する。
336      * @return OKなら0, Cancelなら2
337      */
338     private int warnClear(){
339         int result = JOptionPane.showConfirmDialog(
340                 this,
341                 "本当に発言をクリアしてもよいですか?",
342                 "発言クリア確認",
343                 JOptionPane.OK_CANCEL_OPTION,
344                 JOptionPane.QUESTION_MESSAGE );
345         return result;
346     }
347
348     /**
349      * JSON形式の原稿情報を返す。
350      * @return JSON形式の原稿情報
351      */
352     public JsObject getJson(){
353         JsObject result = new JsObject();
354         JsString memo = new JsString(this.freeMemo.getText());
355         result.putValue("freeMemo", memo);
356
357         JsArray array = new JsArray();
358         JsString text = new JsString(this.editArray.getAllText());
359         array.add(text);
360         result.putValue("drafts", array);
361
362         return result;
363     }
364
365     /**
366      * JSON形式の原稿情報を反映させる。
367      * @param root JSON形式の原稿情報。nullが来たら何もしない
368      */
369     public void putJson(JsObject root){
370         if(root == null) return;
371
372         JsValue value;
373
374         value = root.getValue("freeMemo");
375         if(value.getJsTypes() == JsTypes.STRING){
376             JsString memo = (JsString) value;
377             this.freeMemo.setText(memo.toRawString());
378         }
379
380         value = root.getValue("drafts");
381         if(value.getJsTypes() != JsTypes.ARRAY) return;
382         JsArray array = (JsArray) value;
383
384         StringBuilder draftAll = new StringBuilder();
385         for(JsValue elem : array){
386             if(elem.getJsTypes() != JsTypes.STRING) continue;
387             JsString draft = (JsString) elem;
388             draftAll.append(draft.toRawString());
389         }
390         this.editArray.clearAllEditor();
391         this.editArray.setAllText(draftAll);
392
393         this.loadedDraft = root;
394
395         return;
396     }
397
398     /**
399      * 起動時の原稿と等価か判定する。
400      * @param conf 比較対象
401      * @return 等価ならtrue
402      */
403     public boolean hasConfChanged(JsComposition<?> conf){
404         if(this.loadedDraft != null){
405             if(this.loadedDraft.equals(conf)) return true;
406         }
407         return false;
408     }
409
410     /**
411      * アクティブな発言をカットしクリップボードへコピーする。
412      */
413     private void actionCutActive(){
414         actionCopyActive();
415         actionClearActive(false);
416         return;
417     }
418
419     /**
420      * アクティブな発言をクリップボードにコピーする。
421      */
422     private void actionCopyActive(){
423         TalkEditor activeEditor = this.editArray.getActiveEditor();
424         if(activeEditor == null) return;
425
426         CharSequence text = activeEditor.getText();
427         ClipboardAction.copyToClipboard(text);
428
429         return;
430     }
431
432     /**
433      * アクティブな発言をクリアする。
434      * @param confirm trueなら確認ダイアログを出す
435      */
436     private void actionClearActive(boolean confirm){
437         if(confirm && warnClear() != 0 ) return;
438
439         TalkEditor activeEditor = this.editArray.getActiveEditor();
440         if(activeEditor == null) return;
441
442         activeEditor.clearText();
443
444         return;
445     }
446
447     /**
448      * 全発言をカットしクリップボードへコピーする。
449      */
450     private void actionCutAll(){
451         actionCopyAll();
452         actionClearAll(false);
453         return;
454     }
455
456     /**
457      * 全発言をクリップボードにコピーする。
458      */
459     private void actionCopyAll(){
460         CharSequence text = this.editArray.getAllText();
461         ClipboardAction.copyToClipboard(text);
462         return;
463     }
464
465     /**
466      * 全発言をクリアする。
467      * @param confirm trueなら確認ダイアログを出す
468      */
469     private void actionClearAll(boolean confirm){
470         if(confirm && warnClear() != 0 ) return;
471         this.editArray.clearAllEditor();
472         return;
473     }
474
475     /**
476      * 上位ウィンドウをクローズする。
477      */
478     private void actionClose(){
479         setVisible(false);
480         return;
481     }
482
483     /**
484      * {@inheritDoc}
485      * 各種ボタン操作の処理。
486      * @param event {@inheritDoc}
487      */
488     @Override
489     public void actionPerformed(ActionEvent event){
490         Object source = event.getSource();
491         if     (source == this.cutButton)      actionCutActive();
492         else if(source == this.copyButton)     actionCopyActive();
493         else if(source == this.clearButton)    actionClearActive(true);
494         else if(source == this.cutAllButton)   actionCutAll();
495         else if(source == this.copyAllButton)  actionCopyAll();
496         else if(source == this.clearAllButton) actionClearAll(true);
497         else if(source == this.closeButton)    actionClose();
498         return;
499     }
500
501     /**
502      * アクティブなエディタが変更された時の処理。
503      * @param event イベント情報
504      */
505     @Override
506     public void stateChanged(ChangeEvent event){
507         TalkEditor activeEditor = this.editArray.getActiveEditor();
508         int seqNo = activeEditor.getSequenceNumber();
509         setBorderNumber(seqNo);
510         return;
511     }
512
513     // TODO アンドゥ・リドゥ機能
514     // TODO バルーンの雰囲気を選択できるようにしたい。(白、赤、青、灰)
515     // TODO アンカーの表記揺れの指摘
516 }