OSDN Git Service

Merge branch 'Branch_release-'
[jindolf/Jindolf.git] / src / main / java / jp / sfjp / jindolf / editor / TalkPreview.java
-/*\r
- * 発言エディットパネル\r
- *\r
- * Copyright(c) 2008 olyutorskii\r
- * $Id: TalkPreview.java 953 2009-12-06 16:42:14Z olyutorskii $\r
- */\r
-\r
-package jp.sourceforge.jindolf;\r
-\r
-import java.awt.BorderLayout;\r
-import java.awt.Color;\r
-import java.awt.Container;\r
-import java.awt.Font;\r
-import java.awt.GridBagConstraints;\r
-import java.awt.GridBagLayout;\r
-import java.awt.Insets;\r
-import java.awt.LayoutManager;\r
-import java.awt.event.ActionEvent;\r
-import java.awt.event.ActionListener;\r
-import java.io.File;\r
-import javax.swing.BorderFactory;\r
-import javax.swing.JButton;\r
-import javax.swing.JComponent;\r
-import javax.swing.JFrame;\r
-import javax.swing.JLabel;\r
-import javax.swing.JOptionPane;\r
-import javax.swing.JPanel;\r
-import javax.swing.JPopupMenu;\r
-import javax.swing.JScrollPane;\r
-import javax.swing.JSplitPane;\r
-import javax.swing.JViewport;\r
-import javax.swing.border.BevelBorder;\r
-import javax.swing.border.Border;\r
-import javax.swing.border.TitledBorder;\r
-import javax.swing.event.ChangeEvent;\r
-import javax.swing.event.ChangeListener;\r
-import javax.swing.text.JTextComponent;\r
-import jp.sourceforge.jindolf.json.JsArray;\r
-import jp.sourceforge.jindolf.json.JsObject;\r
-import jp.sourceforge.jindolf.json.JsString;\r
-import jp.sourceforge.jindolf.json.JsValue;\r
-\r
-/**\r
- * 発言エディットパネル。\r
- */\r
-@SuppressWarnings("serial")\r
-public class TalkPreview extends JFrame\r
-        implements ActionListener, ChangeListener{\r
-\r
-    private static final String FRAMETITLE = "発言エディタ - " + Jindolf.TITLE;\r
-    private static final Color COLOR_EDITORBACK = Color.BLACK;\r
-    private static final String DRAFT_FILE = "draft.json";\r
-\r
-    private final JTextComponent freeMemo = new TextEditor();\r
-\r
-    private final EditArray editArray = new EditArray();\r
-\r
-    private final JButton cutButton      = new JButton("カット");\r
-    private final JButton copyButton     = new JButton("コピー");\r
-    private final JButton clearButton    = new JButton("クリア");\r
-    private final JButton cutAllButton   = new JButton("全カット");\r
-    private final JButton copyAllButton  = new JButton("全コピー");\r
-    private final JButton clearAllButton = new JButton("全クリア");\r
-    private final JButton closeButton    = new JButton("閉じる");\r
-    private final TitledBorder numberBorder =\r
-            BorderFactory.createTitledBorder("");\r
-    private final JComponent singleGroup = buildSingleGroup();\r
-    private final JComponent multiGroup  = buildMultiGroup();\r
-    private final JLabel letsBrowser =\r
-            new JLabel("投稿はWebブラウザからどうぞ");\r
-\r
-    private JsObject loadedDraft = null;\r
-\r
-    /**\r
-     * コンストラクタ。\r
-     */\r
-    public TalkPreview(){\r
-        super(FRAMETITLE);\r
-\r
-        GUIUtils.modifyWindowAttributes(this, true, false, true);\r
-\r
-        setDefaultCloseOperation(HIDE_ON_CLOSE);\r
-\r
-        this.cutButton      .addActionListener(this);\r
-        this.copyButton     .addActionListener(this);\r
-        this.clearButton    .addActionListener(this);\r
-        this.cutAllButton   .addActionListener(this);\r
-        this.copyAllButton  .addActionListener(this);\r
-        this.clearAllButton .addActionListener(this);\r
-        this.closeButton    .addActionListener(this);\r
-\r
-        this.editArray.addChangeListener(this);\r
-\r
-        Container content = getContentPane();\r
-        design(content);\r
-\r
-        setBorderNumber(1);\r
-\r
-        return;\r
-    }\r
-\r
-    /**\r
-     * レイアウトを行う。\r
-     * @param content コンテナ\r
-     */\r
-    private void design(Container content){\r
-        JComponent freeNotePanel = buildFreeNotePanel();\r
-\r
-        JScrollPane scrollPane  = new JScrollPane();\r
-        scrollPane.setHorizontalScrollBarPolicy(\r
-                JScrollPane.HORIZONTAL_SCROLLBAR_NEVER\r
-        );\r
-        JViewport viewPort = new JViewport();\r
-        viewPort.setBackground(COLOR_EDITORBACK);\r
-        viewPort.setView(this.editArray);\r
-        scrollPane.setViewport(viewPort);\r
-\r
-        LayoutManager layout;\r
-        Border border;\r
-\r
-        JComponent editPanel = new JPanel();\r
-        layout = new BorderLayout();\r
-        editPanel.setLayout(layout);\r
-        editPanel.add(scrollPane, BorderLayout.CENTER);\r
-        JComponent buttonPanel = buildButtonPanel();\r
-        editPanel.add(buttonPanel, BorderLayout.EAST);\r
-        border = BorderFactory.createTitledBorder("発言編集");\r
-        editPanel.setBorder(border);\r
-\r
-        JSplitPane split = new JSplitPane();\r
-        split.setOrientation(JSplitPane.HORIZONTAL_SPLIT);\r
-        split.setContinuousLayout(false);\r
-        split.setDividerSize(10);\r
-        split.setDividerLocation(200);\r
-        split.setOneTouchExpandable(true);\r
-        split.setLeftComponent(freeNotePanel);\r
-        split.setRightComponent(editPanel);\r
-\r
-        Border inside  = BorderFactory.createBevelBorder(BevelBorder.LOWERED);\r
-        Border outside = BorderFactory.createEmptyBorder(2, 5, 2, 2);\r
-        border = BorderFactory.createCompoundBorder(inside, outside);\r
-        this.letsBrowser.setBorder(border);\r
-\r
-        layout = new BorderLayout();\r
-        content.setLayout(layout);\r
-        content.add(split, BorderLayout.CENTER);\r
-        content.add(this.letsBrowser, BorderLayout.SOUTH);\r
-\r
-        return;\r
-    }\r
-\r
-    /**\r
-     * ボタン群を生成する。\r
-     * @return ボタン群\r
-     */\r
-    private JComponent buildButtonPanel(){\r
-        JPanel panel = new JPanel();\r
-\r
-        LayoutManager layout = new GridBagLayout();\r
-        GridBagConstraints constraints = new GridBagConstraints();\r
-\r
-        panel.setLayout(layout);\r
-\r
-        constraints.weightx = 1.0;\r
-        constraints.weighty = 0.0;\r
-        constraints.fill = GridBagConstraints.NONE;\r
-        constraints.anchor = GridBagConstraints.WEST;\r
-        constraints.gridx = 1;\r
-        constraints.gridy = GridBagConstraints.RELATIVE;\r
-        constraints.gridwidth = 1;\r
-        constraints.gridheight = 1;\r
-\r
-\r
-        constraints.insets = new Insets(3, 3, 3, 3);\r
-        panel.add(this.singleGroup, constraints);\r
-\r
-        constraints.insets = new Insets(10, 3, 3, 3);\r
-        panel.add(this.multiGroup, constraints);\r
-\r
-        constraints.weighty = 1.0;\r
-        constraints.anchor = GridBagConstraints.SOUTH;\r
-        constraints.insets = new Insets(3, 3, 10, 3);\r
-        panel.add(this.closeButton, constraints);\r
-\r
-        return panel;\r
-    }\r
-\r
-    /**\r
-     * アクティブ発言操作ボタン群を生成する。\r
-     * @return ボタン群\r
-     */\r
-    private JComponent buildSingleGroup(){\r
-        JComponent panel = new JPanel();\r
-\r
-        LayoutManager layout = new GridBagLayout();\r
-        GridBagConstraints constraints = new GridBagConstraints();\r
-\r
-        panel.setLayout(layout);\r
-\r
-        constraints.weightx = 1.0;\r
-        constraints.weighty = 0.0;\r
-        constraints.fill = GridBagConstraints.HORIZONTAL;\r
-        constraints.gridx = 1;\r
-        constraints.gridy = GridBagConstraints.RELATIVE;\r
-        constraints.gridwidth = 1;\r
-        constraints.gridheight = 1;\r
-        constraints.insets = new Insets(3, 3, 3, 3);\r
-\r
-        panel.add(this.cutButton,   constraints);\r
-        panel.add(this.copyButton,  constraints);\r
-        panel.add(this.clearButton, constraints);\r
-\r
-        panel.setBorder(this.numberBorder);\r
-\r
-        return panel;\r
-    }\r
-\r
-    /**\r
-     * 全発言操作ボタン群を生成する。\r
-     * @return ボタン群\r
-     */\r
-    private JComponent buildMultiGroup(){\r
-        JComponent panel = new JPanel();\r
-\r
-        LayoutManager layout = new GridBagLayout();\r
-        GridBagConstraints constraints = new GridBagConstraints();\r
-\r
-        panel.setLayout(layout);\r
-\r
-        constraints.weightx = 1.0;\r
-        constraints.weighty = 0.0;\r
-        constraints.fill = GridBagConstraints.HORIZONTAL;\r
-        constraints.gridx = 1;\r
-        constraints.gridy = GridBagConstraints.RELATIVE;\r
-        constraints.gridwidth = 1;\r
-        constraints.gridheight = 1;\r
-        constraints.insets = new Insets(3, 3, 3, 3);\r
-\r
-        panel.add(this.cutAllButton,   constraints);\r
-        panel.add(this.copyAllButton,  constraints);\r
-        panel.add(this.clearAllButton, constraints);\r
-\r
-        Border border = BorderFactory.createTitledBorder("全発言を");\r
-        panel.setBorder(border);\r
-\r
-        return panel;\r
-    }\r
-\r
-    /**\r
-     * フリーノート部を生成する。\r
-     * @return フリーノート部\r
-     */\r
-    private JComponent buildFreeNotePanel(){\r
-        Insets margin = new Insets(3, 3, 3, 3);\r
-        this.freeMemo.setMargin(margin);\r
-        JPopupMenu popup = new TextPopup();\r
-        this.freeMemo.setComponentPopupMenu(popup);\r
-\r
-        JScrollPane scrollPane = new JScrollPane();\r
-        scrollPane.setHorizontalScrollBarPolicy(\r
-                JScrollPane.HORIZONTAL_SCROLLBAR_NEVER\r
-        );\r
-        JViewport viewPort = new JViewport();\r
-        viewPort.setView(this.freeMemo);\r
-        scrollPane.setViewport(viewPort);\r
-\r
-        JComponent panel = new JPanel();\r
-\r
-        LayoutManager layout = new GridBagLayout();\r
-        GridBagConstraints constraints = new GridBagConstraints();\r
-\r
-        panel.setLayout(layout);\r
-\r
-        constraints.weightx = 1.0;\r
-        constraints.weighty = 1.0;\r
-        constraints.fill = GridBagConstraints.BOTH;\r
-        constraints.insets = new Insets(1, 1, 1, 1);\r
-        panel.add(scrollPane, constraints);\r
-\r
-        Border border = BorderFactory.createTitledBorder("フリーメモ");\r
-        panel.setBorder(border);\r
-\r
-        return panel;\r
-    }\r
-\r
-    /**\r
-     * アクティブ発言の通し番号表示を更新。\r
-     * @param num 通し番号\r
-     */\r
-    private void setBorderNumber(int num){\r
-        String title = "発言#"+num+" を";\r
-        this.numberBorder.setTitle(title);\r
-        this.singleGroup.revalidate();\r
-        this.singleGroup.repaint();\r
-        return;\r
-    }\r
-\r
-    /**\r
-     * テキスト編集用フォントを指定する。\r
-     * 描画属性は無視される。\r
-     * @param fontInfo フォント設定\r
-     */\r
-    public void setFontInfo(FontInfo fontInfo){\r
-        setTextFont(fontInfo.getFont());\r
-        return;\r
-    }\r
-\r
-    /**\r
-     * テキスト編集用フォントを指定する。\r
-     * @param textFont フォント\r
-     */\r
-    public void setTextFont(Font textFont){\r
-        this.freeMemo.setFont(textFont);\r
-        this.editArray.setTextFont(textFont);\r
-        return;\r
-    }\r
-\r
-    /**\r
-     * テキスト編集用フォントを取得する。\r
-     * @return フォント\r
-     */\r
-    public Font getTextFont(){\r
-        return this.editArray.getTextFont();\r
-    }\r
-\r
-    /**\r
-     * 発言クリア操作の確認ダイアログを表示する。\r
-     * @return OKなら0, Cancelなら2\r
-     */\r
-    private int warnClear(){\r
-        int result = JOptionPane.showConfirmDialog(\r
-                this,\r
-                "本当に発言をクリアしてもよいですか?",\r
-                "発言クリア確認",\r
-                JOptionPane.OK_CANCEL_OPTION,\r
-                JOptionPane.QUESTION_MESSAGE );\r
-        return result;\r
-    }\r
-\r
-    /**\r
-     * 原稿のロード。\r
-     */\r
-    public void loadDraft(){\r
-        JsValue value = ConfigFile.loadJson(new File(DRAFT_FILE));\r
-        if(value == null) return;\r
-\r
-        if( ! (value instanceof JsObject) ) return;\r
-        JsObject root = (JsObject) value;\r
-\r
-        value = root.getValue("freeMemo");\r
-        if(value instanceof JsString){\r
-            JsString memo = (JsString) value;\r
-            this.freeMemo.setText(memo.toRawString());\r
-        }\r
-\r
-        value = root.getValue("drafts");\r
-        if( ! (value instanceof JsArray) ) return;\r
-        JsArray array = (JsArray) value;\r
-\r
-        StringBuilder draftAll = new StringBuilder();\r
-        for(JsValue elem : array){\r
-            if( ! (elem instanceof JsString) ) continue;\r
-            JsString draft = (JsString) elem;\r
-            draftAll.append(draft.toRawString());\r
-        }\r
-        this.editArray.clearAllEditor();\r
-        this.editArray.setAllText(draftAll);\r
-\r
-        this.loadedDraft = root;\r
-\r
-        return;\r
-    }\r
-\r
-    /**\r
-     * 原稿のセーブ。\r
-     */\r
-    public void saveDraft(){\r
-        AppSetting setting = Jindolf.getAppSetting();\r
-        if( ! setting.useConfigPath() ) return;\r
-        File configPath = setting.getConfigPath();\r
-        if(configPath == null) return;\r
-\r
-        JsObject root = new JsObject();\r
-        JsString memo = new JsString(this.freeMemo.getText());\r
-        root.putValue("freeMemo", memo);\r
-\r
-        JsArray array = new JsArray();\r
-        JsString text = new JsString(this.editArray.getAllText());\r
-        array.add(text);\r
-        root.putValue("drafts", array);\r
-\r
-        if(this.loadedDraft != null){\r
-            if(this.loadedDraft.equals(root)) return;\r
-        }\r
-\r
-        ConfigFile.saveJson(new File(DRAFT_FILE), root);\r
-\r
-        return;\r
-    }\r
-\r
-    /**\r
-     * アクティブな発言をカットしクリップボードへコピーする。\r
-     */\r
-    private void actionCutActive(){\r
-        actionCopyActive();\r
-        actionClearActive(false);\r
-        return;\r
-    }\r
-\r
-    /**\r
-     * アクティブな発言をクリップボードにコピーする。\r
-     */\r
-    private void actionCopyActive(){\r
-        TalkEditor activeEditor = this.editArray.getActiveEditor();\r
-        if(activeEditor == null) return;\r
-\r
-        CharSequence text = activeEditor.getText();\r
-        ClipboardAction.copyToClipboard(text);\r
-\r
-        return;\r
-    }\r
-\r
-    /**\r
-     * アクティブな発言をクリアする。\r
-     * @param confirm trueなら確認ダイアログを出す\r
-     */\r
-    private void actionClearActive(boolean confirm){\r
-        if(confirm && warnClear() != 0 ) return;\r
-\r
-        TalkEditor activeEditor = this.editArray.getActiveEditor();\r
-        if(activeEditor == null) return;\r
-\r
-        activeEditor.clearText();\r
-\r
-        return;\r
-    }\r
-\r
-    /**\r
-     * 全発言をカットしクリップボードへコピーする。\r
-     */\r
-    private void actionCutAll(){\r
-        actionCopyAll();\r
-        actionClearAll(false);\r
-        return;\r
-    }\r
-\r
-    /**\r
-     * 全発言をクリップボードにコピーする。\r
-     */\r
-    private void actionCopyAll(){\r
-        CharSequence text = this.editArray.getAllText();\r
-        ClipboardAction.copyToClipboard(text);\r
-        return;\r
-    }\r
-\r
-    /**\r
-     * 全発言をクリアする。\r
-     * @param confirm trueなら確認ダイアログを出す\r
-     */\r
-    private void actionClearAll(boolean confirm){\r
-        if(confirm && warnClear() != 0 ) return;\r
-        this.editArray.clearAllEditor();\r
-        return;\r
-    }\r
-\r
-    /**\r
-     * 上位ウィンドウをクローズする。\r
-     */\r
-    private void actionClose(){\r
-        setVisible(false);\r
-        return;\r
-    }\r
-\r
-    /**\r
-     * {@inheritDoc}\r
-     * 各種ボタン操作の処理。\r
-     * @param event {@inheritDoc}\r
-     */\r
-    public void actionPerformed(ActionEvent event){\r
-        Object source = event.getSource();\r
-        if     (source == this.cutButton)      actionCutActive();\r
-        else if(source == this.copyButton)     actionCopyActive();\r
-        else if(source == this.clearButton)    actionClearActive(true);\r
-        else if(source == this.cutAllButton)   actionCutAll();\r
-        else if(source == this.copyAllButton)  actionCopyAll();\r
-        else if(source == this.clearAllButton) actionClearAll(true);\r
-        else if(source == this.closeButton)    actionClose();\r
-        return;\r
-    }\r
-\r
-    /**\r
-     * アクティブなエディタが変更された時の処理。\r
-     * @param event イベント情報\r
-     */\r
-    public void stateChanged(ChangeEvent event){\r
-        TalkEditor activeEditor = this.editArray.getActiveEditor();\r
-        int seqNo = activeEditor.getSequenceNumber();\r
-        setBorderNumber(seqNo);\r
-        return;\r
-    }\r
-\r
-    // TODO アンドゥ・リドゥ機能\r
-    // TODO バルーンの雰囲気を選択できるようにしたい。(白、赤、青、灰)\r
-    // TODO アンカーの表記揺れの指摘\r
-}\r
+/*
+ * 発言エディットパネル
+ *
+ * License : The MIT License
+ * Copyright(c) 2008 olyutorskii
+ */
+
+package jp.sfjp.jindolf.editor;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Container;
+import java.awt.Font;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.LayoutManager;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.File;
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+import javax.swing.JComponent;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+import javax.swing.JScrollPane;
+import javax.swing.JSplitPane;
+import javax.swing.JViewport;
+import javax.swing.border.BevelBorder;
+import javax.swing.border.Border;
+import javax.swing.border.TitledBorder;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import javax.swing.text.JTextComponent;
+import jp.sfjp.jindolf.dxchg.ClipboardAction;
+import jp.sfjp.jindolf.dxchg.TextPopup;
+import jp.sfjp.jindolf.glyph.FontInfo;
+import jp.sfjp.jindolf.util.GUIUtils;
+import jp.sourceforge.jovsonz.JsArray;
+import jp.sourceforge.jovsonz.JsComposition;
+import jp.sourceforge.jovsonz.JsObject;
+import jp.sourceforge.jovsonz.JsString;
+import jp.sourceforge.jovsonz.JsTypes;
+import jp.sourceforge.jovsonz.JsValue;
+
+/**
+ * 発言エディットパネル。
+ */
+@SuppressWarnings("serial")
+public class TalkPreview extends JFrame
+        implements ActionListener, ChangeListener{
+
+    /** 原稿ファイル。 */
+    public static final File DRAFT_FILE = new File("draft.json");
+
+    private static final Color COLOR_EDITORBACK = Color.BLACK;
+
+    private final JTextComponent freeMemo = new TextEditor();
+
+    private final EditArray editArray = new EditArray();
+
+    private final JButton cutButton      = new JButton("カット");
+    private final JButton copyButton     = new JButton("コピー");
+    private final JButton clearButton    = new JButton("クリア");
+    private final JButton cutAllButton   = new JButton("全カット");
+    private final JButton copyAllButton  = new JButton("全コピー");
+    private final JButton clearAllButton = new JButton("全クリア");
+    private final JButton closeButton    = new JButton("閉じる");
+    private final TitledBorder numberBorder =
+            BorderFactory.createTitledBorder("");
+    private final JComponent singleGroup = buildSingleGroup();
+    private final JComponent multiGroup  = buildMultiGroup();
+    private final JLabel letsBrowser =
+            new JLabel("投稿はWebブラウザからどうぞ");
+
+    private JsObject loadedDraft = null;
+
+    /**
+     * コンストラクタ。
+     */
+    @SuppressWarnings("LeakingThisInConstructor")
+    public TalkPreview(){
+        super();
+
+        GUIUtils.modifyWindowAttributes(this, true, false, true);
+
+        setDefaultCloseOperation(HIDE_ON_CLOSE);
+
+        this.cutButton      .addActionListener(this);
+        this.copyButton     .addActionListener(this);
+        this.clearButton    .addActionListener(this);
+        this.cutAllButton   .addActionListener(this);
+        this.copyAllButton  .addActionListener(this);
+        this.clearAllButton .addActionListener(this);
+        this.closeButton    .addActionListener(this);
+
+        this.editArray.addChangeListener(this);
+
+        Container content = getContentPane();
+        design(content);
+
+        setBorderNumber(1);
+
+        return;
+    }
+
+    /**
+     * レイアウトを行う。
+     * @param content コンテナ
+     */
+    private void design(Container content){
+        JComponent freeNotePanel = buildFreeNotePanel();
+
+        JScrollPane scrollPane  = new JScrollPane();
+        scrollPane.setHorizontalScrollBarPolicy(
+                JScrollPane.HORIZONTAL_SCROLLBAR_NEVER
+        );
+        JViewport viewPort = new JViewport();
+        viewPort.setBackground(COLOR_EDITORBACK);
+        viewPort.setView(this.editArray);
+        scrollPane.setViewport(viewPort);
+
+        LayoutManager layout;
+        Border border;
+
+        JComponent editPanel = new JPanel();
+        layout = new BorderLayout();
+        editPanel.setLayout(layout);
+        editPanel.add(scrollPane, BorderLayout.CENTER);
+        JComponent buttonPanel = buildButtonPanel();
+        editPanel.add(buttonPanel, BorderLayout.EAST);
+        border = BorderFactory.createTitledBorder("発言編集");
+        editPanel.setBorder(border);
+
+        JSplitPane split = new JSplitPane();
+        split.setOrientation(JSplitPane.HORIZONTAL_SPLIT);
+        split.setContinuousLayout(false);
+        split.setDividerSize(10);
+        split.setDividerLocation(200);
+        split.setOneTouchExpandable(true);
+        split.setLeftComponent(freeNotePanel);
+        split.setRightComponent(editPanel);
+
+        Border inside  = BorderFactory.createBevelBorder(BevelBorder.LOWERED);
+        Border outside = BorderFactory.createEmptyBorder(2, 5, 2, 2);
+        border = BorderFactory.createCompoundBorder(inside, outside);
+        this.letsBrowser.setBorder(border);
+
+        layout = new BorderLayout();
+        content.setLayout(layout);
+        content.add(split, BorderLayout.CENTER);
+        content.add(this.letsBrowser, BorderLayout.SOUTH);
+
+        return;
+    }
+
+    /**
+     * ボタン群を生成する。
+     * @return ボタン群
+     */
+    private JComponent buildButtonPanel(){
+        JPanel panel = new JPanel();
+
+        LayoutManager layout = new GridBagLayout();
+        GridBagConstraints constraints = new GridBagConstraints();
+
+        panel.setLayout(layout);
+
+        constraints.weightx = 1.0;
+        constraints.weighty = 0.0;
+        constraints.fill = GridBagConstraints.NONE;
+        constraints.anchor = GridBagConstraints.WEST;
+        constraints.gridx = 1;
+        constraints.gridy = GridBagConstraints.RELATIVE;
+        constraints.gridwidth = 1;
+        constraints.gridheight = 1;
+
+
+        constraints.insets = new Insets(3, 3, 3, 3);
+        panel.add(this.singleGroup, constraints);
+
+        constraints.insets = new Insets(10, 3, 3, 3);
+        panel.add(this.multiGroup, constraints);
+
+        constraints.weighty = 1.0;
+        constraints.anchor = GridBagConstraints.SOUTH;
+        constraints.insets = new Insets(3, 3, 10, 3);
+        panel.add(this.closeButton, constraints);
+
+        return panel;
+    }
+
+    /**
+     * アクティブ発言操作ボタン群を生成する。
+     * @return ボタン群
+     */
+    private JComponent buildSingleGroup(){
+        JComponent panel = new JPanel();
+
+        LayoutManager layout = new GridBagLayout();
+        GridBagConstraints constraints = new GridBagConstraints();
+
+        panel.setLayout(layout);
+
+        constraints.weightx = 1.0;
+        constraints.weighty = 0.0;
+        constraints.fill = GridBagConstraints.HORIZONTAL;
+        constraints.gridx = 1;
+        constraints.gridy = GridBagConstraints.RELATIVE;
+        constraints.gridwidth = 1;
+        constraints.gridheight = 1;
+        constraints.insets = new Insets(3, 3, 3, 3);
+
+        panel.add(this.cutButton,   constraints);
+        panel.add(this.copyButton,  constraints);
+        panel.add(this.clearButton, constraints);
+
+        panel.setBorder(this.numberBorder);
+
+        return panel;
+    }
+
+    /**
+     * 全発言操作ボタン群を生成する。
+     * @return ボタン群
+     */
+    private JComponent buildMultiGroup(){
+        JComponent panel = new JPanel();
+
+        LayoutManager layout = new GridBagLayout();
+        GridBagConstraints constraints = new GridBagConstraints();
+
+        panel.setLayout(layout);
+
+        constraints.weightx = 1.0;
+        constraints.weighty = 0.0;
+        constraints.fill = GridBagConstraints.HORIZONTAL;
+        constraints.gridx = 1;
+        constraints.gridy = GridBagConstraints.RELATIVE;
+        constraints.gridwidth = 1;
+        constraints.gridheight = 1;
+        constraints.insets = new Insets(3, 3, 3, 3);
+
+        panel.add(this.cutAllButton,   constraints);
+        panel.add(this.copyAllButton,  constraints);
+        panel.add(this.clearAllButton, constraints);
+
+        Border border = BorderFactory.createTitledBorder("全発言を");
+        panel.setBorder(border);
+
+        return panel;
+    }
+
+    /**
+     * フリーノート部を生成する。
+     * @return フリーノート部
+     */
+    private JComponent buildFreeNotePanel(){
+        Insets margin = new Insets(3, 3, 3, 3);
+        this.freeMemo.setMargin(margin);
+        JPopupMenu popup = new TextPopup();
+        this.freeMemo.setComponentPopupMenu(popup);
+
+        JScrollPane scrollPane = new JScrollPane();
+        scrollPane.setHorizontalScrollBarPolicy(
+                JScrollPane.HORIZONTAL_SCROLLBAR_NEVER
+        );
+        JViewport viewPort = new JViewport();
+        viewPort.setView(this.freeMemo);
+        scrollPane.setViewport(viewPort);
+
+        JComponent panel = new JPanel();
+
+        LayoutManager layout = new GridBagLayout();
+        GridBagConstraints constraints = new GridBagConstraints();
+
+        panel.setLayout(layout);
+
+        constraints.weightx = 1.0;
+        constraints.weighty = 1.0;
+        constraints.fill = GridBagConstraints.BOTH;
+        constraints.insets = new Insets(1, 1, 1, 1);
+        panel.add(scrollPane, constraints);
+
+        Border border = BorderFactory.createTitledBorder("フリーメモ");
+        panel.setBorder(border);
+
+        return panel;
+    }
+
+    /**
+     * アクティブ発言の通し番号表示を更新。
+     * @param num 通し番号
+     */
+    private void setBorderNumber(int num){
+        String title = "発言#"+num+" を";
+        this.numberBorder.setTitle(title);
+        this.singleGroup.revalidate();
+        this.singleGroup.repaint();
+        return;
+    }
+
+    /**
+     * テキスト編集用フォントを指定する。
+     * 描画属性は無視される。
+     * @param fontInfo フォント設定
+     */
+    public void setFontInfo(FontInfo fontInfo){
+        setTextFont(fontInfo.getFont());
+        return;
+    }
+
+    /**
+     * テキスト編集用フォントを指定する。
+     * @param textFont フォント
+     */
+    public void setTextFont(Font textFont){
+        this.freeMemo.setFont(textFont);
+        this.editArray.setTextFont(textFont);
+        return;
+    }
+
+    /**
+     * テキスト編集用フォントを取得する。
+     * @return フォント
+     */
+    public Font getTextFont(){
+        return this.editArray.getTextFont();
+    }
+
+    /**
+     * 発言クリア操作の確認ダイアログを表示する。
+     * @return OKなら0, Cancelなら2
+     */
+    private int warnClear(){
+        int result = JOptionPane.showConfirmDialog(
+                this,
+                "本当に発言をクリアしてもよいですか?",
+                "発言クリア確認",
+                JOptionPane.OK_CANCEL_OPTION,
+                JOptionPane.QUESTION_MESSAGE );
+        return result;
+    }
+
+    /**
+     * JSON形式の原稿情報を返す。
+     * @return JSON形式の原稿情報
+     */
+    public JsObject getJson(){
+        JsObject result = new JsObject();
+        JsString memo = new JsString(this.freeMemo.getText());
+        result.putValue("freeMemo", memo);
+
+        JsArray array = new JsArray();
+        JsString text = new JsString(this.editArray.getAllText());
+        array.add(text);
+        result.putValue("drafts", array);
+
+        return result;
+    }
+
+    /**
+     * JSON形式の原稿情報を反映させる。
+     * @param root JSON形式の原稿情報。nullが来たら何もしない
+     */
+    public void putJson(JsObject root){
+        if(root == null) return;
+
+        JsValue value;
+
+        value = root.getValue("freeMemo");
+        if(value.getJsTypes() == JsTypes.STRING){
+            JsString memo = (JsString) value;
+            this.freeMemo.setText(memo.toRawString());
+        }
+
+        value = root.getValue("drafts");
+        if(value.getJsTypes() != JsTypes.ARRAY) return;
+        JsArray array = (JsArray) value;
+
+        StringBuilder draftAll = new StringBuilder();
+        for(JsValue elem : array){
+            if(elem.getJsTypes() != JsTypes.STRING) continue;
+            JsString draft = (JsString) elem;
+            draftAll.append(draft.toRawString());
+        }
+        this.editArray.clearAllEditor();
+        this.editArray.setAllText(draftAll);
+
+        this.loadedDraft = root;
+
+        return;
+    }
+
+    /**
+     * 起動時の原稿と等価か判定する。
+     * @param conf 比較対象
+     * @return 等価ならtrue
+     */
+    public boolean hasConfChanged(JsComposition<?> conf){
+        if(this.loadedDraft != null){
+            if(this.loadedDraft.equals(conf)) return true;
+        }
+        return false;
+    }
+
+    /**
+     * アクティブな発言をカットしクリップボードへコピーする。
+     */
+    private void actionCutActive(){
+        actionCopyActive();
+        actionClearActive(false);
+        return;
+    }
+
+    /**
+     * アクティブな発言をクリップボードにコピーする。
+     */
+    private void actionCopyActive(){
+        TalkEditor activeEditor = this.editArray.getActiveEditor();
+        if(activeEditor == null) return;
+
+        CharSequence text = activeEditor.getText();
+        ClipboardAction.copyToClipboard(text);
+
+        return;
+    }
+
+    /**
+     * アクティブな発言をクリアする。
+     * @param confirm trueなら確認ダイアログを出す
+     */
+    private void actionClearActive(boolean confirm){
+        if(confirm && warnClear() != 0 ) return;
+
+        TalkEditor activeEditor = this.editArray.getActiveEditor();
+        if(activeEditor == null) return;
+
+        activeEditor.clearText();
+
+        return;
+    }
+
+    /**
+     * 全発言をカットしクリップボードへコピーする。
+     */
+    private void actionCutAll(){
+        actionCopyAll();
+        actionClearAll(false);
+        return;
+    }
+
+    /**
+     * 全発言をクリップボードにコピーする。
+     */
+    private void actionCopyAll(){
+        CharSequence text = this.editArray.getAllText();
+        ClipboardAction.copyToClipboard(text);
+        return;
+    }
+
+    /**
+     * 全発言をクリアする。
+     * @param confirm trueなら確認ダイアログを出す
+     */
+    private void actionClearAll(boolean confirm){
+        if(confirm && warnClear() != 0 ) return;
+        this.editArray.clearAllEditor();
+        return;
+    }
+
+    /**
+     * 上位ウィンドウをクローズする。
+     */
+    private void actionClose(){
+        setVisible(false);
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * 各種ボタン操作の処理。
+     * @param event {@inheritDoc}
+     */
+    @Override
+    public void actionPerformed(ActionEvent event){
+        Object source = event.getSource();
+        if     (source == this.cutButton)      actionCutActive();
+        else if(source == this.copyButton)     actionCopyActive();
+        else if(source == this.clearButton)    actionClearActive(true);
+        else if(source == this.cutAllButton)   actionCutAll();
+        else if(source == this.copyAllButton)  actionCopyAll();
+        else if(source == this.clearAllButton) actionClearAll(true);
+        else if(source == this.closeButton)    actionClose();
+        return;
+    }
+
+    /**
+     * アクティブなエディタが変更された時の処理。
+     * @param event イベント情報
+     */
+    @Override
+    public void stateChanged(ChangeEvent event){
+        TalkEditor activeEditor = this.editArray.getActiveEditor();
+        int seqNo = activeEditor.getSequenceNumber();
+        setBorderNumber(seqNo);
+        return;
+    }
+
+    // TODO アンドゥ・リドゥ機能
+    // TODO バルーンの雰囲気を選択できるようにしたい。(白、赤、青、灰)
+    // TODO アンカーの表記揺れの指摘
+}