OSDN Git Service

リファクタリング
authorAkiyoshi Kamide <kamide@yk.rim.or.jp>
Sat, 30 Nov 2013 17:31:17 +0000 (17:31 +0000)
committerAkiyoshi Kamide <kamide@yk.rim.or.jp>
Sat, 30 Nov 2013 17:31:17 +0000 (17:31 +0000)
git-svn-id: https://svn.sourceforge.jp/svnroot/midichordhelper/MIDIChordHelper@20 302f1594-2db2-43b1-aaa4-6307b5a2a2de

src/Base64Dialog.java
src/ChordHelperApplet.java
src/MIDIDevice.java
src/MIDIEditor.java
src/MIDISequencer.java
src/NewSequenceDialog.java

index f9c85e2..6d6677e 100644 (file)
@@ -1,8 +1,10 @@
 import java.awt.Dimension;\r
 import java.awt.Insets;\r
 import java.awt.event.ActionEvent;\r
+import java.io.IOException;\r
 import java.util.regex.Pattern;\r
 \r
+import javax.sound.midi.InvalidMidiDataException;\r
 import javax.swing.AbstractAction;\r
 import javax.swing.Action;\r
 import javax.swing.Box;\r
@@ -36,9 +38,12 @@ public class Base64Dialog extends JDialog {
                        );\r
                }\r
                @Override\r
-               public void actionPerformed(ActionEvent e) {\r
-                       int lastIndex = midiEditor.addSequence(getMIDIData(), null);\r
-                       if( lastIndex < 0 ) {\r
+               public void actionPerformed(ActionEvent event) {\r
+                       int lastIndex;\r
+                       try {\r
+                               lastIndex = midiEditor.sequenceListTableModel.addSequence(getMIDIData(), null);\r
+                       } catch(IOException | InvalidMidiDataException e) {\r
+                               midiEditor.showWarning(e.getMessage());\r
                                base64TextArea.requestFocusInWindow();\r
                                lastIndex = midiEditor.sequenceListTableModel.getRowCount() - 1;\r
                        }\r
index 929dd3e..11669fb 100644 (file)
@@ -20,11 +20,14 @@ import java.io.UnsupportedEncodingException;
 import java.net.URI;\r
 import java.net.URISyntaxException;\r
 import java.net.URL;\r
+import java.security.AccessControlException;\r
 import java.util.Arrays;\r
 import java.util.Vector;\r
 \r
+import javax.sound.midi.InvalidMidiDataException;\r
 import javax.sound.midi.MetaEventListener;\r
 import javax.sound.midi.MetaMessage;\r
+import javax.sound.midi.Sequence;\r
 import javax.sound.midi.Sequencer;\r
 import javax.swing.Box;\r
 import javax.swing.BoxLayout;\r
@@ -81,9 +84,8 @@ public class ChordHelperApplet extends JApplet {
         */\r
        public int addRandomSongToPlaylist(int measureLength) {\r
                editorDialog.newSequenceDialog.setRandomChordProgression(measureLength);\r
-               return editorDialog.addSequenceAndPlay(\r
-                       editorDialog.newSequenceDialog.getMidiSequence()\r
-               );\r
+               Sequence sequence = editorDialog.newSequenceDialog.getMidiSequence();\r
+               return editorDialog.sequenceListTableModel.addSequenceAndPlay(sequence);\r
        }\r
        /**\r
         * URLで指定されたMIDIファイルをプレイリストへ追加します。\r
@@ -95,7 +97,15 @@ public class ChordHelperApplet extends JApplet {
         * @return 追加先のインデックス値(0から始まる)。追加できなかったときは -1\r
         */\r
        public int addToPlaylist(String midiFileUrl) {\r
-               return editorDialog.addSequenceFromURL(midiFileUrl);\r
+               try {\r
+                       return editorDialog.sequenceListTableModel.addSequenceFromURL(midiFileUrl);\r
+               } catch( URISyntaxException|IOException|InvalidMidiDataException e ) {\r
+                       editorDialog.showWarning(e.getMessage());\r
+               } catch( AccessControlException e ) {\r
+                       e.printStackTrace();\r
+                       editorDialog.showError(e.getMessage());\r
+               }\r
+               return -1;\r
        }\r
        /**\r
         * Base64 エンコードされた MIDI ファイルをプレイリストへ追加します。\r
@@ -117,7 +127,13 @@ public class ChordHelperApplet extends JApplet {
        public int addToPlaylistBase64(String base64EncodedText, String filename) {\r
                Base64Dialog d = editorDialog.base64Dialog;\r
                d.setBase64Data(base64EncodedText);\r
-               return editorDialog.addSequence(d.getMIDIData(), filename);\r
+               try {\r
+                       return editorDialog.sequenceListTableModel.addSequence(d.getMIDIData(), filename);\r
+               } catch (IOException | InvalidMidiDataException e) {\r
+                       e.printStackTrace();\r
+                       editorDialog.showWarning(e.getMessage());\r
+                       return -1;\r
+               }\r
        }\r
        /**\r
         * プレイリスト上で現在選択されているMIDIシーケンスを、\r
@@ -252,7 +268,7 @@ public class ChordHelperApplet extends JApplet {
         */\r
        public static class VersionInfo {\r
                public static final String      NAME = "MIDI Chord Helper";\r
-               public static final String      VERSION = "Ver.20131129.1";\r
+               public static final String      VERSION = "Ver.20131201.2";\r
                public static final String      COPYRIGHT = "Copyright (C) 2004-2013";\r
                public static final String      AUTHER = "@きよし - Akiyoshi Kamide";\r
                public static final String      URL = "http://www.yk.rim.or.jp/~kamide/music/chordhelper/";\r
@@ -431,7 +447,7 @@ public class ChordHelperApplet extends JApplet {
                editorDialog.setIconImage(iconImage);\r
                deviceModelList.setMidiEditor(editorDialog);\r
                new DropTarget(this, DnDConstants.ACTION_COPY_OR_MOVE, editorDialog, true);\r
-               keyboardPanel.eventDialog = editorDialog.eventCellEditor.eventDialog;\r
+               keyboardPanel.eventDialog = editorDialog.eventListTableView.eventDialog;\r
                midiConnectionDialog = new MidiDeviceDialog(deviceModelList);\r
                midiConnectionDialog.setIconImage(iconImage);\r
                lyricDisplay = new ChordTextField() {\r
index de7a1d6..b493031 100644 (file)
@@ -1258,9 +1258,8 @@ class MidiDeviceModelList extends Vector<MidiConnecterListModel> {
         */\r
        MidiSequencerModel sequencerModel;\r
        /**\r
-        * MIDIデータ編集ダイアログ\r
+        * 最初のMIDI出力\r
         */\r
-       private MidiEditor editorDialog;\r
        private MidiConnecterListModel firstMidiOutModel;\r
        /**\r
         * MIDIデバイスモデルリストを生成します。\r
@@ -1366,7 +1365,6 @@ class MidiDeviceModelList extends Vector<MidiConnecterListModel> {
         * @param editorDialog MIDIエディタ\r
         */\r
        public void setMidiEditor(MidiEditor editorDialog) {\r
-               this.editorDialog = editorDialog;\r
                MidiConnecterListModel mclm = addMidiDevice(editorDialog.virtualMidiDevice);\r
                try {\r
                        mclm.openDevice();\r
@@ -1375,47 +1373,23 @@ class MidiDeviceModelList extends Vector<MidiConnecterListModel> {
                }\r
                mclm.connectToReceiverOf(firstMidiOutModel);\r
        }\r
-       /**\r
-        * シーケンサを開始します。\r
-        * <p>録音するMIDIチャンネルがMIDIエディタで指定されている場合、\r
-        * 録音スタート時のタイムスタンプが正しく0になるよう、\r
-        * 各MIDIデバイスのタイムスタンプをすべてリセットします。\r
-        * </p>\r
-        */\r
-       public void startSequencerWithResetTimestamps() {\r
-               Sequencer sequencer = sequencerModel.getSequencer();\r
-               if( editorDialog != null && editorDialog.isRecordable() ) {\r
-                       for( MidiConnecterListModel m : this )\r
-                               m.resetMicrosecondPosition();\r
-                       System.gc();\r
-                       sequencer.startRecording();\r
-               }\r
-               else {\r
-                       System.gc();\r
-                       sequencer.start();\r
-               }\r
-       }\r
 }\r
 \r
 /**\r
  * MIDIデバイスダイアログ (View)\r
  */\r
 class MidiDeviceDialog extends JDialog implements ActionListener {\r
-       MidiDeviceTree deviceTree;\r
-       JEditorPane deviceInfoPane = new JEditorPane("text/html","<html></html>") {\r
-               {\r
-                       setEditable(false);\r
-               }\r
-       };\r
+       JEditorPane deviceInfoPane = new JEditorPane("text/html","<html></html>") {{\r
+               setEditable(false);\r
+       }};\r
        MidiDesktopPane desktopPane;\r
+       MidiDeviceTree deviceTree;\r
        public MidiDeviceDialog(List<MidiConnecterListModel> deviceModelList) {\r
                setTitle("MIDI device connection");\r
                setBounds( 300, 300, 800, 500 );\r
-               desktopPane = new MidiDesktopPane(\r
-                       deviceTree = new MidiDeviceTree(\r
-                               new MidiDeviceTreeModel(deviceModelList)\r
-                       )\r
-               );\r
+               desktopPane = new MidiDesktopPane(deviceTree = new MidiDeviceTree(\r
+                       new MidiDeviceTreeModel(deviceModelList)\r
+               ));\r
                deviceTree.addTreeSelectionListener(\r
                        new TreeSelectionListener() {\r
                                public void valueChanged(TreeSelectionEvent e) {\r
@@ -1456,17 +1430,13 @@ class MidiDeviceDialog extends JDialog implements ActionListener {
                JSplitPane sideSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT,\r
                        new JScrollPane(deviceTree),\r
                        new JScrollPane(deviceInfoPane)\r
-               ) {\r
-                       {\r
-                               setDividerLocation(300);\r
-                       }\r
-               };\r
-               add(new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, sideSplitPane, desktopPane) {\r
-                       {\r
-                               setOneTouchExpandable(true);\r
-                               setDividerLocation(250);\r
-                       }\r
-               });\r
+               ){{\r
+                       setDividerLocation(300);\r
+               }};\r
+               add(new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, sideSplitPane, desktopPane) {{\r
+                       setOneTouchExpandable(true);\r
+                       setDividerLocation(250);\r
+               }});\r
        }\r
        @Override\r
        public void actionPerformed(ActionEvent event) {\r
@@ -1480,7 +1450,7 @@ class MidiDeviceDialog extends JDialog implements ActionListener {
 class MidiDesktopPane extends JDesktopPane implements DropTargetListener {\r
        MidiCablePane cablePane = new MidiCablePane(this);\r
        public MidiDesktopPane(MidiDeviceTree deviceTree) {\r
-               add( cablePane, JLayeredPane.PALETTE_LAYER );\r
+               add(cablePane, JLayeredPane.PALETTE_LAYER);\r
                int i=0;\r
                MidiDeviceTreeModel treeModel = (MidiDeviceTreeModel)deviceTree.getModel();\r
                List<MidiConnecterListModel> deviceModelList = treeModel.deviceModelList;\r
index 43ad62c..a9b806e 100644 (file)
@@ -82,7 +82,6 @@ import javax.swing.table.AbstractTableModel;
 import javax.swing.table.TableCellEditor;\r
 import javax.swing.table.TableCellRenderer;\r
 import javax.swing.table.TableColumn;\r
-import javax.swing.table.TableColumnModel;\r
 import javax.swing.table.TableModel;\r
 \r
 /**\r
@@ -93,8 +92,6 @@ import javax.swing.table.TableModel;
  *     http://www.yk.rim.or.jp/~kamide/music/chordhelper/\r
  */\r
 class MidiEditor extends JDialog implements DropTargetListener {\r
-       public static final Insets ZERO_INSETS = new Insets(0,0,0,0);\r
-       static final Icon deleteIcon = new ButtonIcon(ButtonIcon.X_ICON);\r
        /**\r
         * このMIDIエディタの仮想MIDIデバイス\r
         */\r
@@ -130,7 +127,7 @@ class MidiEditor extends JDialog implements DropTargetListener {
         * エラーメッセージダイアログを表示します。\r
         * @param message エラーメッセージ\r
         */\r
-       private void showError(String message) {\r
+       void showError(String message) {\r
                JOptionPane.showMessageDialog(\r
                        this, message,\r
                        ChordHelperApplet.VersionInfo.NAME,\r
@@ -141,7 +138,7 @@ class MidiEditor extends JDialog implements DropTargetListener {
         * 警告メッセージダイアログを表示します。\r
         * @param message 警告メッセージ\r
         */\r
-       private void showWarning(String message) {\r
+       void showWarning(String message) {\r
                JOptionPane.showMessageDialog(\r
                        this, message,\r
                        ChordHelperApplet.VersionInfo.NAME,\r
@@ -162,14 +159,41 @@ class MidiEditor extends JDialog implements DropTargetListener {
                ) == JOptionPane.YES_OPTION ;\r
        }\r
 \r
-       /**\r
-        * 新しいMIDIシーケンスを生成するダイアログ\r
-        */\r
-       NewSequenceDialog newSequenceDialog = new NewSequenceDialog(this);\r
-       /**\r
-        * BASE64テキスト入力ダイアログ\r
-        */\r
-       Base64Dialog base64Dialog = new Base64Dialog(this);\r
+       @Override\r
+       public void dragEnter(DropTargetDragEvent event) {\r
+               if( event.isDataFlavorSupported(DataFlavor.javaFileListFlavor) ) {\r
+                       event.acceptDrag(DnDConstants.ACTION_COPY_OR_MOVE);\r
+               }\r
+       }\r
+       @Override\r
+       public void dragExit(DropTargetEvent event) {}\r
+       @Override\r
+       public void dragOver(DropTargetDragEvent event) {}\r
+       @Override\r
+       public void dropActionChanged(DropTargetDragEvent event) {}\r
+       @Override\r
+       @SuppressWarnings("unchecked")\r
+       public void drop(DropTargetDropEvent event) {\r
+               event.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);\r
+               try {\r
+                       int action = event.getDropAction();\r
+                       if ( (action & DnDConstants.ACTION_COPY_OR_MOVE) != 0 ) {\r
+                               Transferable t = event.getTransferable();\r
+                               Object data = t.getTransferData(DataFlavor.javaFileListFlavor);\r
+                               loadAndPlay((List<File>)data);\r
+                               event.dropComplete(true);\r
+                               return;\r
+                       }\r
+                       event.dropComplete(false);\r
+               }\r
+               catch (Exception ex) {\r
+                       ex.printStackTrace();\r
+                       event.dropComplete(false);\r
+               }\r
+       }\r
+\r
+       public static final Insets ZERO_INSETS = new Insets(0,0,0,0);\r
+       static final Icon deleteIcon = new ButtonIcon(ButtonIcon.X_ICON);\r
        /**\r
         * プレイリストのデータモデル\r
         */\r
@@ -179,6 +203,14 @@ class MidiEditor extends JDialog implements DropTargetListener {
         */\r
        SequenceListTable sequenceListTableView;\r
        /**\r
+        * 新しいMIDIシーケンスを生成するダイアログ\r
+        */\r
+       NewSequenceDialog newSequenceDialog = new NewSequenceDialog(this);\r
+       /**\r
+        * BASE64テキスト入力ダイアログ\r
+        */\r
+       Base64Dialog base64Dialog = new Base64Dialog(this);\r
+       /**\r
         * プレイリストビュー\r
         */\r
        private class SequenceListTable extends JTable {\r
@@ -196,59 +228,17 @@ class MidiEditor extends JDialog implements DropTargetListener {
                                // アクセスできないので、ファイル選択ダイアログは使用不可。\r
                                midiFileChooser = null;\r
                        }\r
-                       model.selectionModel.addListSelectionListener(\r
-                               new ListSelectionListener() {\r
-                                       @Override\r
-                                       public void valueChanged(ListSelectionEvent e) {\r
-                                               if( e.getValueIsAdjusting() )\r
-                                                       return;\r
-                                               updateButtonStatus();\r
-                                       }\r
-                               }\r
-                       );\r
-                       model.addTableModelListener(new TableModelListener() {\r
-                               /**\r
-                                * 全シーケンスの合計時間長をヘッダータイトルに反映します。\r
-                                * @param e テーブルモデルイベント\r
-                                */\r
-                               @Override\r
-                               public void tableChanged(TableModelEvent e) {\r
-                                       int sec = getModel().getTotalSeconds();\r
-                                       SequenceListTableModel.Column c = SequenceListTableModel.Column.SEQ_LENGTH;\r
-                                       TableColumn tc = getColumnModel().getColumn(c.ordinal());\r
-                                       tc.setHeaderValue(String.format(c.title+" [%02d:%02d]", sec/60, sec%60));\r
-                                       //\r
-                                       // シーケンス削除時など、合計シーケンス長が変わっても\r
-                                       // 列モデルからではヘッダタイトルが再描画されないことがある。\r
-                                       // そこで、ヘッダビューから repaint() で突っついて再描画させる。\r
-                                       getTableHeader().repaint();\r
-                               }\r
-                       });\r
+                       // プレイリストの変更時に合計時間を再表示\r
+                       model.addTableModelListener(totalLengthListener);\r
+                       //\r
+                       // プレイボタンの埋め込み\r
                        SequenceListTableModel.Column c = SequenceListTableModel.Column.SEQ_PLAY;\r
                        TableColumn tc = getColumnModel().getColumn(c.ordinal());\r
-                       tc.setCellRenderer(new TableCellRenderer() {\r
-                               @Override\r
-                               public Component getTableCellRendererComponent(\r
-                                       JTable table, Object value, boolean isSelected,\r
-                                       boolean hasFocus, int row, int column\r
-                               ) {\r
-                                       if(getModel().sequenceList.get(row).isOnSequencer()) {\r
-                                               // すでにロードされていたらボタンをレンダリング\r
-                                               return playButton;\r
-                                       }\r
-                                       // ロードされていなかった場合、\r
-                                       // デフォルトレンダラーでレンダリングする。こうすれば\r
-                                       // レンダラーを設定しなかった場合と全く同じ動作になる。\r
-                                       Class<?> columnClass = getModel().getColumnClass(column);\r
-                                       TableCellRenderer defaultRenderer =\r
-                                               table.getDefaultRenderer(columnClass);\r
-                                       return defaultRenderer.getTableCellRendererComponent(\r
-                                               table, value, isSelected, hasFocus, row, column\r
-                                       );\r
-                               }\r
-                       });\r
+                       tc.setCellRenderer(playButtonCellRenderer);\r
                        tc.setCellEditor(new PlayButtonCellEditor());\r
                        setAutoCreateColumnsFromModel(false);\r
+                       //\r
+                       // Base64エンコードアクションの生成\r
                        if( base64Dialog.isBase64Available() ) {\r
                                base64EncodeAction = getModel().new SelectedSequenceAction(\r
                                        "Base64 Encode",\r
@@ -262,11 +252,46 @@ class MidiEditor extends JDialog implements DropTargetListener {
                                        }\r
                                };\r
                        }\r
+                       //\r
+                       // 曲の選択が切り替わったらリスナーに通知\r
+                       model.selectionModel.addListSelectionListener(\r
+                               new ListSelectionListener() {\r
+                                       @Override\r
+                                       public void valueChanged(ListSelectionEvent e) {\r
+                                               if( e.getValueIsAdjusting() )\r
+                                                       return;\r
+                                               SequenceListTableModel model = getModel();\r
+                                               SequenceTrackListTableModel sequenceModel = model.getSelectedSequenceModel();\r
+                                               if(sequenceModel == null) {\r
+                                                       sequenceModel = model.emptySequenceModel;\r
+                                                       trackListTableView.addTrackAction.setEnabled(false);\r
+                                                       trackListTableView.deleteTrackAction.setEnabled(false);\r
+                                                       eventListTableView.copyEventAction.setEnabled(false);\r
+                                                       eventListTableView.deleteEventAction.setEnabled(false);\r
+                                                       eventListTableView.cutEventAction.setEnabled(false);\r
+                                                       eventListTableView.queryJumpEventAction.setEnabled(false);\r
+                                                       eventListTableView.queryAddEventAction.setEnabled(false);\r
+                                                       eventListTableView.queryPasteEventAction.setEnabled(false);\r
+                                               }\r
+                                               else {\r
+                                                       trackListTableView.addTrackAction.setEnabled(true);\r
+                                               }\r
+                                               trackListTableView.setModel(sequenceModel);\r
+                                       }\r
+                               }\r
+                       );\r
                }\r
-               @Override\r
-               public SequenceListTableModel getModel() {\r
-                       return (SequenceListTableModel)(super.getModel());\r
-               }\r
+               /**\r
+                * セル埋め込み用プレイボタン\r
+                */\r
+               private JToggleButton playButton = new JToggleButton(\r
+                       getModel().sequencerModel.startStopAction\r
+               ){{\r
+                       setMargin(ZERO_INSETS);\r
+               }};\r
+               /**\r
+                * プレイボタンを埋め込んだセルエディタ\r
+                */\r
                class PlayButtonCellEditor extends AbstractCellEditor implements TableCellEditor {\r
                        @Override\r
                        public Object getCellEditorValue() { return ""; }\r
@@ -275,15 +300,56 @@ class MidiEditor extends JDialog implements DropTargetListener {
                                JTable table, Object value, boolean isSelected,\r
                                int row, int column\r
                        ) {\r
-                               return getModel().sequenceList.get(row).isOnSequencer() ? playButton : null;\r
+                               SequenceListTableModel model = getModel();\r
+                               SequenceTrackListTableModel sm = model.sequenceList.get(row);\r
+                               return sm.isOnSequencer() ? playButton : null;\r
                        }\r
                }\r
                /**\r
-                * ã\82»ã\83«å\9f\8bã\82\81è¾¼ã\81¿ç\94¨ã\83\97ã\83¬ã\82¤ã\83\9cã\82¿ã\83³\r
+                * ã\83\97ã\83¬ã\82¤ã\83\9cã\82¿ã\83³ã\82\92å\9f\8bã\82\81è¾¼ã\82\93ã\81 ã\82»ã\83«ã\83¬ã\83³ã\83\80ã\83©ã\83¼\r
                 */\r
-               private JToggleButton playButton = new JToggleButton(\r
-                       getModel().sequencerModel.startStopAction\r
-               ){{ setMargin(ZERO_INSETS); }};\r
+               private TableCellRenderer playButtonCellRenderer = new TableCellRenderer() {\r
+                       @Override\r
+                       public Component getTableCellRendererComponent(\r
+                               JTable table, Object value, boolean isSelected,\r
+                               boolean hasFocus, int row, int column\r
+                       ) {\r
+                               SequenceListTableModel model = getModel();\r
+                               if(model.sequenceList.get(row).isOnSequencer()) {\r
+                                       // すでにロードされていたらボタンをレンダリング\r
+                                       return playButton;\r
+                               }\r
+                               // ロードされていなかった場合、\r
+                               // デフォルトレンダラーでレンダリングする。こうすれば\r
+                               // レンダラーを設定しなかった場合と全く同じ動作になる。\r
+                               Class<?> cc = model.getColumnClass(column);\r
+                               TableCellRenderer defaultRenderer = table.getDefaultRenderer(cc);\r
+                               return defaultRenderer.getTableCellRendererComponent(\r
+                                       table, value, isSelected, hasFocus, row, column\r
+                               );\r
+                       }\r
+               };\r
+               /**\r
+                * 全シーケンスの合計時間長をヘッダータイトルに反映するリスナー\r
+                */\r
+               private TableModelListener totalLengthListener = new TableModelListener() {\r
+                       @Override\r
+                       public void tableChanged(TableModelEvent event) {\r
+                               int sec = getModel().getTotalSeconds();\r
+                               SequenceListTableModel.Column c = SequenceListTableModel.Column.SEQ_LENGTH;\r
+                               String title = String.format(c.title+" [%02d:%02d]", sec/60, sec%60);\r
+                               getColumnModel().getColumn(c.ordinal()).setHeaderValue(title);\r
+                               //\r
+                               // シーケンス削除時など、合計シーケンス長が変わっても\r
+                               // 列モデルからではヘッダタイトルが再描画されないことがある。\r
+                               // そこで、ヘッダビューから repaint() で突っついて再描画させる。\r
+                               getTableHeader().repaint();\r
+                       }\r
+               };\r
+               @Override\r
+               public SequenceListTableModel getModel() {\r
+                       return (SequenceListTableModel)(super.getModel());\r
+               }\r
                /**\r
                 * BASE64エンコードボタン(ライブラリが見えている場合のみ有効)\r
                 */\r
@@ -415,186 +481,276 @@ class MidiEditor extends JDialog implements DropTargetListener {
                        };\r
                };\r
        }\r
-       //\r
-       // Drag & drop\r
-       public void dragEnter(DropTargetDragEvent event) {\r
-               if( event.isDataFlavorSupported(DataFlavor.javaFileListFlavor) ) {\r
-                       event.acceptDrag(DnDConstants.ACTION_COPY_OR_MOVE);\r
-               }\r
-       }\r
-       public void dragExit(DropTargetEvent event) {}\r
-       public void dragOver(DropTargetDragEvent event) {}\r
-       public void dropActionChanged(DropTargetDragEvent event) {}\r
-       @SuppressWarnings("unchecked")\r
-       public void drop(DropTargetDropEvent event) {\r
-               event.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);\r
-               try {\r
-                       int action = event.getDropAction();\r
-                       if ( (action & DnDConstants.ACTION_COPY_OR_MOVE) != 0 ) {\r
-                               Transferable t = event.getTransferable();\r
-                               Object data = t.getTransferData(DataFlavor.javaFileListFlavor);\r
-                               loadAndPlay((List<File>)data);\r
-                               event.dropComplete(true);\r
-                               return;\r
-                       }\r
-                       event.dropComplete(false);\r
-               }\r
-               catch (Exception ex) {\r
-                       ex.printStackTrace();\r
-                       event.dropComplete(false);\r
-               }\r
-       }\r
 \r
-       SequenceTrackListTableModel sequenceTrackListTableModel;\r
        /**\r
-        * MIDIトラック選択状態\r
+        * MIDIトラックリストテーブルビュー(選択中のシーケンスの中身)\r
         */\r
-       ListSelectionModel trackSelectionModel;\r
+       private TrackListTable trackListTableView;\r
        /**\r
-        * ã\83\88ã\83©ã\83\83ã\82¯è¿½å\8a ã\82¢ã\82¯ã\82·ã\83§ã\83³\r
+        * ã\82·ã\83¼ã\82±ã\83³ã\82¹ï¼\88ã\83\88ã\83©ã\83\83ã\82¯ã\83ªã\82¹ã\83\88ï¼\89ã\83\86ã\83¼ã\83\96ã\83«ã\83\93ã\83¥ã\83¼\r
         */\r
-       public Action addTrackAction = new AbstractAction("New") {\r
-               {\r
-                       String tooltip = "Append new track - 新しいトラックの追加";\r
-                       putValue(Action.SHORT_DESCRIPTION, tooltip);\r
+       private class TrackListTable extends JTable {\r
+               /**\r
+                * トラックリストテーブルビューを構築します。\r
+                * @param model シーケンスモデル\r
+                */\r
+               public TrackListTable(SequenceTrackListTableModel model) {\r
+                       super(model, null, model.selectionModel);\r
+                       model.parent.selectionModel.addListSelectionListener(titleLabel);\r
+                       model.selectionModel.addListSelectionListener(trackSelectionListener);\r
+                       //\r
+                       // 録音対象のMIDIチャンネルをコンボボックスで選択できるよう、\r
+                       // 列モデルにセルエディタを設定\r
+                       int colIndex = SequenceTrackListTableModel.Column.RECORD_CHANNEL.ordinal();\r
+                       TableColumn tc = getColumnModel().getColumn(colIndex);\r
+                       tc.setCellEditor(new DefaultCellEditor(new JComboBox<String>(){{\r
+                               addItem("OFF");\r
+                               for(int i=1; i <= MIDISpec.MAX_CHANNELS; i++)\r
+                                       addItem(String.format("%d", i));\r
+                               addItem("ALL");\r
+                       }}));\r
+                       // データモデルが差し替えられると、列モデルが自動作成されて\r
+                       // セルエディタがデフォルトに戻ってしまうので、\r
+                       // 一度列モデルを作ったあとは自動作成しない。\r
+                       setAutoCreateColumnsFromModel(false);\r
                }\r
                @Override\r
-               public void actionPerformed(ActionEvent e) {\r
-                       int index = sequenceListTableModel.getSelectedSequenceModel().createTrack();\r
-                       trackSelectionModel.setSelectionInterval(index, index);\r
+               public SequenceTrackListTableModel getModel() {\r
+                       return (SequenceTrackListTableModel) super.getModel();\r
                }\r
-       };\r
-       /**\r
-        * トラック削除アクション\r
-        */\r
-       public Action deleteTrackAction = new AbstractAction("Delete", deleteIcon) {\r
-               {\r
-                       String tooltip = "Delete selected track - 選択したトラックを削除";\r
-                       putValue(Action.SHORT_DESCRIPTION, tooltip);\r
+               /**\r
+                * このテーブルのデータモデルを設定し、\r
+                * そのモデルにリスナーを追加します。\r
+                * データモデルに内蔵している選択モデルも設定し直されます。\r
+                *\r
+                * @param newModel 新しく設定するデータモデル\r
+                * @see {@link JTable#setModel(TableModel)}\r
+                */\r
+               public void setModel(SequenceTrackListTableModel newModel) {\r
+                       getSelectionModel().clearSelection();\r
+                       getSelectionModel().removeListSelectionListener(trackSelectionListener);\r
+                       getModel().parent.selectionModel.removeListSelectionListener(titleLabel);\r
+                       super.setModel(newModel);\r
+                       setSelectionModel(newModel.selectionModel);\r
+                       newModel.parent.selectionModel.addListSelectionListener(titleLabel);\r
+                       newModel.selectionModel.addListSelectionListener(trackSelectionListener);\r
                }\r
-               @Override\r
-               public void actionPerformed(ActionEvent e) {\r
-                       if( ! confirm("Do you want to delete selected track ?\n選択したトラックを削除しますか?"))\r
-                               return;\r
-                       sequenceListTableModel.getSelectedSequenceModel().deleteTracks(\r
-                               trackSelectionModel\r
-                       );\r
+               /**\r
+                * トラックの選択変更時にイベントリストビューの対象モデルを切り替えるリスナー\r
+                */\r
+               private ListSelectionListener trackSelectionListener = new ListSelectionListener() {\r
+                       @Override\r
+                       public void valueChanged(ListSelectionEvent e) {\r
+                               if( e.getValueIsAdjusting() )\r
+                                       return;\r
+                               SequenceTrackListTableModel sequenceModel = getModel();\r
+                               TrackEventListTableModel trackModel;\r
+                               if( sequenceModel == null ) {\r
+                                       trackModel = getModel().parent.emptySequenceModel.emptyTrackModel;\r
+                                       deleteTrackAction.setEnabled(false);\r
+                                       eventListTableView.queryJumpEventAction.setEnabled(false);\r
+                                       eventListTableView.queryAddEventAction.setEnabled(false);\r
+                                       eventListTableView.queryPasteEventAction.setEnabled(false);\r
+                                       eventListTableView.copyEventAction.setEnabled(false);\r
+                                       eventListTableView.deleteEventAction.setEnabled(false);\r
+                                       eventListTableView.cutEventAction.setEnabled(false);\r
+                               }\r
+                               else {\r
+                                       trackModel = sequenceModel.getSelectedTrackModel();\r
+                                       if( trackModel == null ) {\r
+                                               trackModel = sequenceModel.emptyTrackModel;\r
+                                       }\r
+                                       deleteTrackAction.setEnabled(true);\r
+                                       eventListTableView.queryJumpEventAction.setEnabled(true);\r
+                                       eventListTableView.queryAddEventAction.setEnabled(true);\r
+                               }\r
+                               eventListTableView.setModel(trackModel);\r
+                       }\r
+               };\r
+               /**\r
+                * 曲番号を表示するタイトルラベル\r
+                */\r
+               TitleLabel titleLabel = new TitleLabel();\r
+               /**\r
+                * 曲番号を表示するタイトルラベル\r
+                */\r
+               private class TitleLabel extends JLabel implements ListSelectionListener {\r
+                       private static final String TITLE = "Tracks";\r
+                       public TitleLabel() {\r
+                               setText(TITLE);\r
+                               getModel().parent.selectionModel.addListSelectionListener(this);\r
+                       }\r
+                       @Override\r
+                       public void valueChanged(ListSelectionEvent event) {\r
+                               if( event.getValueIsAdjusting() )\r
+                                       return;\r
+                               ListSelectionModel selectionModel = (ListSelectionModel)event.getSource();\r
+                               int index = selectionModel.getMinSelectionIndex();\r
+                               String text = TITLE;\r
+                               if( index >= 0 ) {\r
+                                       text = String.format(text+" - MIDI file No.%d", index);\r
+                               }\r
+                               setText(text);\r
+                       }\r
                }\r
-       };\r
-       /**\r
-        * MIDIトラックリストテーブルビュー(選択中のシーケンスの中身)\r
-        */\r
-       private JTable trackListTableView;\r
+               /**\r
+                * トラック追加アクション\r
+                */\r
+               Action addTrackAction = new AbstractAction("New") {\r
+                       {\r
+                               String tooltip = "Append new track - 新しいトラックの追加";\r
+                               putValue(Action.SHORT_DESCRIPTION, tooltip);\r
+                               setEnabled(false);\r
+                       }\r
+                       @Override\r
+                       public void actionPerformed(ActionEvent e) {\r
+                               getModel().createTrack();\r
+                       }\r
+               };\r
+               /**\r
+                * トラック削除アクション\r
+                */\r
+               Action deleteTrackAction = new AbstractAction("Delete", deleteIcon) {\r
+                       {\r
+                               String tooltip = "Delete selected track - 選択したトラックを削除";\r
+                               putValue(Action.SHORT_DESCRIPTION, tooltip);\r
+                               setEnabled(false);\r
+                       }\r
+                       @Override\r
+                       public void actionPerformed(ActionEvent e) {\r
+                               String message = "Do you want to delete selected track ?\n"\r
+                                       + "選択したトラックを削除しますか?";\r
+                               if( confirm(message) )\r
+                                       getModel().deleteSelectedTracks();\r
+                       }\r
+               };\r
+       }\r
 \r
        /**\r
-        * MIDIイベント選択状態\r
+        * MIDIイベントリストテーブルビュー(選択中のトラックの中身)\r
         */\r
-       private ListSelectionModel eventSelectionModel = new DefaultListSelectionModel() {{\r
-               setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);\r
-               addListSelectionListener(new ListSelectionListener() {\r
+       class EventListTable extends JTable {\r
+               public EventListTable(TrackEventListTableModel model) {\r
+                       super(model, null, model.selectionModel);\r
+                       //\r
+                       // 列モデルにセルエディタを設定\r
+                       int index = TrackEventListTableModel.Column.MESSAGE.ordinal();\r
+                       getColumnModel().getColumn(index).setCellEditor(eventCellEditor);\r
+                       // 以後、データモデルが変わっても列モデルを作り直さない\r
+                       setAutoCreateColumnsFromModel(false);\r
+                       //\r
+                       // 選択されているトラックやイベントが変わったときのリスナーを設定\r
+                       model.parent.selectionModel.addListSelectionListener(titleLabel);\r
+                       model.selectionModel.addListSelectionListener(eventSelectionListener);\r
+               }\r
+               /**\r
+                * データモデルを設定し、リスナーを付け替えます。\r
+                *\r
+                * @param model トラック(イベントリスト)データモデル\r
+                */\r
+               public void setModel(TrackEventListTableModel model) {\r
+                       getSelectionModel().clearSelection();\r
+                       getSelectionModel().removeListSelectionListener(eventSelectionListener);\r
+                       getModel().parent.selectionModel.removeListSelectionListener(titleLabel);\r
+                       super.setModel(model);\r
+                       super.setSelectionModel(model.selectionModel);\r
+                       model.parent.selectionModel.addListSelectionListener(titleLabel);\r
+                       model.selectionModel.addListSelectionListener(eventSelectionListener);\r
+               }\r
+               @Override\r
+               public TrackEventListTableModel getModel() {\r
+                       return (TrackEventListTableModel) super.getModel();\r
+               }\r
+               /**\r
+                * トラック番号を表示するタイトルラベル\r
+                */\r
+               TitleLabel titleLabel = new TitleLabel();\r
+               /**\r
+                * トラック番号を表示するタイトルラベル\r
+                */\r
+               private class TitleLabel extends JLabel implements ListSelectionListener {\r
+                       private static final String TITLE = "MIDI Events";\r
+                       public TitleLabel() { super(TITLE); }\r
+                       @Override\r
+                       public void valueChanged(ListSelectionEvent event) {\r
+                               String text = TITLE;\r
+                               ListSelectionModel selectionModel = (ListSelectionModel)event.getSource();\r
+                               int index = selectionModel.getMinSelectionIndex();\r
+                               if( index >= 0 ) {\r
+                                       text = String.format(TITLE+" - track No.%d", index);\r
+                               }\r
+                               setText(text);\r
+                       }\r
+               }\r
+               ListSelectionListener eventSelectionListener = new ListSelectionListener() {\r
                        @Override\r
                        public void valueChanged(ListSelectionEvent e) {\r
-                               if( e.getValueIsAdjusting() ) return;\r
-                               if( ! isSelectionEmpty() ) {\r
-                                       TrackEventListTableModel trackModel = (TrackEventListTableModel)trackEventListTableView.getModel();\r
-                                       int minIndex = getMinSelectionIndex();\r
-                                       if( trackModel.hasTrack() ) {\r
-                                               MidiEvent midiEvent = trackModel.getMidiEvent(minIndex);\r
-                                               MidiMessage msg = midiEvent.getMessage();\r
-                                               if( msg instanceof ShortMessage ) {\r
-                                                       ShortMessage sm = (ShortMessage)msg;\r
-                                                       int cmd = sm.getCommand();\r
-                                                       if( cmd == 0x80 || cmd == 0x90 || cmd == 0xA0 ) {\r
-                                                               // ノート番号を持つ場合、音を鳴らす。\r
-                                                               MidiChannel outMidiChannels[] = virtualMidiDevice.getChannels();\r
-                                                               int ch = sm.getChannel();\r
-                                                               int note = sm.getData1();\r
-                                                               int vel = sm.getData2();\r
-                                                               outMidiChannels[ch].noteOn(note, vel);\r
-                                                               outMidiChannels[ch].noteOff(note, vel);\r
-                                                       }\r
+                               if( e.getValueIsAdjusting() )\r
+                                       return;\r
+                               if( getSelectionModel().isSelectionEmpty() ) {\r
+                                       queryPasteEventAction.setEnabled(false);\r
+                                       copyEventAction.setEnabled(false);\r
+                                       deleteEventAction.setEnabled(false);\r
+                                       cutEventAction.setEnabled(false);\r
+                               }\r
+                               else {\r
+                                       copyEventAction.setEnabled(true);\r
+                                       deleteEventAction.setEnabled(true);\r
+                                       cutEventAction.setEnabled(true);\r
+                                       TrackEventListTableModel trackModel = getModel();\r
+                                       int minIndex = getSelectionModel().getMinSelectionIndex();\r
+                                       MidiEvent midiEvent = trackModel.getMidiEvent(minIndex);\r
+                                       MidiMessage msg = midiEvent.getMessage();\r
+                                       if( msg instanceof ShortMessage ) {\r
+                                               ShortMessage sm = (ShortMessage)msg;\r
+                                               int cmd = sm.getCommand();\r
+                                               if( cmd == 0x80 || cmd == 0x90 || cmd == 0xA0 ) {\r
+                                                       // ノート番号を持つ場合、音を鳴らす。\r
+                                                       MidiChannel outMidiChannels[] = virtualMidiDevice.getChannels();\r
+                                                       int ch = sm.getChannel();\r
+                                                       int note = sm.getData1();\r
+                                                       int vel = sm.getData2();\r
+                                                       outMidiChannels[ch].noteOn(note, vel);\r
+                                                       outMidiChannels[ch].noteOff(note, vel);\r
                                                }\r
                                        }\r
-                                       if( pairNoteCheckbox.isSelected() ) {\r
-                                               int maxIndex = getMaxSelectionIndex();\r
+                                       if( pairNoteOnOffModel.isSelected() ) {\r
+                                               int maxIndex = getSelectionModel().getMaxSelectionIndex();\r
                                                int partnerIndex;\r
-                                               for( int i=minIndex; i<=maxIndex; i++ )\r
-                                                       if(\r
-                                                               isSelectedIndex(i) &&\r
-                                                               (partnerIndex = trackModel.getIndexOfPartnerFor(i)) >= 0 &&\r
-                                                               ! isSelectedIndex(partnerIndex)\r
-                                                       ) addSelectionInterval(partnerIndex, partnerIndex);\r
+                                               for( int i=minIndex; i<=maxIndex; i++ ) {\r
+                                                       if( ! getSelectionModel().isSelectedIndex(i) ) continue;\r
+                                                       partnerIndex = trackModel.getIndexOfPartnerFor(i);\r
+                                                       if( partnerIndex >= 0 && ! getSelectionModel().isSelectedIndex(partnerIndex) )\r
+                                                               getSelectionModel().addSelectionInterval(partnerIndex, partnerIndex);\r
+                                               }\r
                                        }\r
                                }\r
-                               updateButtonStatus();\r
                        }\r
-               });\r
-       }};\r
-       /**\r
-        * MIDIイベントリストテーブルビュー\r
-        */\r
-       private JTable trackEventListTableView;\r
-       private class MidiEventsLabel extends JLabel implements ListSelectionListener {\r
-               private static final String TITLE = "MIDI Events";\r
-               public MidiEventsLabel() {\r
-                       super(TITLE);\r
-                       trackSelectionModel.addListSelectionListener(this);\r
-               }\r
-               @Override\r
-               public void valueChanged(ListSelectionEvent e) {\r
-                       String text = TITLE;\r
-                       int index = trackSelectionModel.getMinSelectionIndex();\r
-                       if( index >= 0 )\r
-                               text = String.format(TITLE+" - track No.%d", index);\r
-                       setText(text);\r
-               }\r
-       }\r
-       /**\r
-        * スクロール可能なMIDIイベントテーブルビュー\r
-        */\r
-       private JScrollPane scrollableEventTableView;\r
-       /**\r
-        * 指定の MIDI tick のイベントへスクロールします。\r
-        * @param tick MIDI tick\r
-        */\r
-       public void scrollToEventAt(long tick) {\r
-               TrackEventListTableModel trackModel = (TrackEventListTableModel)trackEventListTableView.getModel();\r
-               int index = trackModel.tickToIndex(tick);\r
-               scrollableEventTableView.getVerticalScrollBar().setValue(\r
-                       index * trackEventListTableView.getRowHeight()\r
-               );\r
-               eventSelectionModel.setSelectionInterval(index, index);\r
-       }\r
-       /**\r
-        * MIDIイベント表のセルエディタ\r
-        */\r
-       MidiEventCellEditor eventCellEditor = new MidiEventCellEditor();\r
-       /**\r
-        * Pair note on/off チェックボックス\r
-        */\r
-       private JCheckBox pairNoteCheckbox = new JCheckBox("Pair NoteON/OFF") {\r
-               { setModel(eventCellEditor.pairNoteOnOffModel); }\r
-       };\r
-       /**\r
-        * MIDIイベント表のセルエディタ\r
-        */\r
-       class MidiEventCellEditor extends AbstractCellEditor implements TableCellEditor {\r
+               };\r
                /**\r
                 * MIDIイベント入力ダイアログ\r
                 */\r
                MidiEventDialog eventDialog = new MidiEventDialog();\r
                /**\r
-                * 削除対象にする変更前イベント(null可)\r
-                */\r
-               private MidiEvent[] midiEventsToBeRemoved;\r
-               /**\r
-                * 対象トラック\r
+                * Pair noteON/OFF トグルボタンモデル\r
                 */\r
-               private TrackEventListTableModel midiTrackTableModel;\r
+               private JToggleButton.ToggleButtonModel\r
+                       pairNoteOnOffModel = new JToggleButton.ToggleButtonModel() {\r
+                               {\r
+                                       addItemListener(\r
+                                               new ItemListener() {\r
+                                                       public void itemStateChanged(ItemEvent e) {\r
+                                                               eventDialog.midiMessageForm.durationForm.setEnabled(isSelected());\r
+                                                       }\r
+                                               }\r
+                                       );\r
+                                       setSelected(true);\r
+                               }\r
+                       };\r
                /**\r
-                * 対象シーケンス\r
+                * tick位置入力モデル\r
                 */\r
-               private SequenceTrackListTableModel sequenceTableModel;\r
+               private TickPositionModel tickPositionModel = new TickPositionModel();\r
                /**\r
                 * 選択されたイベント\r
                 */\r
@@ -607,55 +763,28 @@ class MidiEditor extends JDialog implements DropTargetListener {
                 * 選択されたイベントのtick位置\r
                 */\r
                private long currentTick = 0;\r
-               /**\r
-                * tick位置入力モデル\r
-                */\r
-               private TickPositionModel tickPositionModel = new TickPositionModel();\r
-               /**\r
-                * Pair noteON/OFF トグルボタンモデル\r
-                */\r
-               private JToggleButton.ToggleButtonModel pairNoteOnOffModel =\r
-                       new JToggleButton.ToggleButtonModel() {{\r
-                               addItemListener(new ItemListener() {\r
-                                       public void itemStateChanged(ItemEvent e) {\r
-                                               eventDialog.midiMessageForm.durationForm.setEnabled(isSelected());\r
-                                       }\r
-                               });\r
-                               setSelected(true);\r
-                       }};\r
-\r
                private void setSelectedEvent() {\r
-                       sequenceTableModel = sequenceListTableModel.getSelectedSequenceModel();\r
+                       SequenceTrackListTableModel sequenceTableModel = getModel().parent;\r
                        eventDialog.midiMessageForm.durationForm.setPPQ(sequenceTableModel.getSequence().getResolution());\r
                        tickPositionModel.setSequenceIndex(sequenceTableModel.getSequenceTickIndex());\r
                        selectedIndex = -1;\r
                        currentTick = 0;\r
                        selectedMidiEvent = null;\r
-                       midiTrackTableModel = (TrackEventListTableModel)trackEventListTableView.getModel();\r
-                       if( ! eventSelectionModel.isSelectionEmpty() ) {\r
-                               selectedIndex = eventSelectionModel.getMinSelectionIndex();\r
-                               selectedMidiEvent = midiTrackTableModel.getMidiEvent(selectedIndex);\r
+                       if( ! getSelectionModel().isSelectionEmpty() ) {\r
+                               selectedIndex = getSelectionModel().getMinSelectionIndex();\r
+                               selectedMidiEvent = getModel().getMidiEvent(selectedIndex);\r
                                currentTick = selectedMidiEvent.getTick();\r
                                tickPositionModel.setTickPosition(currentTick);\r
                        }\r
                }\r
                /**\r
-                * イベント入力をキャンセルするアクション\r
-                */\r
-               Action cancelAction = new AbstractAction() {\r
-                       {\r
-                               putValue(NAME,"Cancel");\r
-                               eventDialog.cancelButton.setAction(this);\r
-                       }\r
-                       public void actionPerformed(ActionEvent e) {\r
-                               fireEditingCanceled();\r
-                               eventDialog.setVisible(false);\r
-                       }\r
-               };\r
-               /**\r
                 * 指定のTick位置へジャンプするアクション\r
                 */\r
                Action queryJumpEventAction = new AbstractAction() {\r
+                       {\r
+                               putValue(NAME,"Jump to ...");\r
+                               setEnabled(false);\r
+                       }\r
                        private Action jumpEventAction = new AbstractAction() {\r
                                { putValue(NAME,"Jump"); }\r
                                public void actionPerformed(ActionEvent e) {\r
@@ -663,7 +792,6 @@ class MidiEditor extends JDialog implements DropTargetListener {
                                        eventDialog.setVisible(false);\r
                                }\r
                        };\r
-                       { putValue(NAME,"Jump to ..."); }\r
                        public void actionPerformed(ActionEvent e) {\r
                                setSelectedEvent();\r
                                eventDialog.setTitle("Jump selection to");\r
@@ -675,17 +803,18 @@ class MidiEditor extends JDialog implements DropTargetListener {
                 * 指定のTick位置へ貼り付けるアクション\r
                 */\r
                Action queryPasteEventAction = new AbstractAction() {\r
-                       { putValue(NAME,"Paste to ..."); }\r
+                       {\r
+                               putValue(NAME,"Paste to ...");\r
+                               setEnabled(false);\r
+                       }\r
                        private Action pasteEventAction = new AbstractAction() {\r
                                { putValue(NAME,"Paste"); }\r
                                public void actionPerformed(ActionEvent e) {\r
                                        long tick = tickPositionModel.getTickPosition();\r
-                                       ((TrackEventListTableModel)trackEventListTableView.getModel()).addMidiEvents(\r
-                                               copiedEventsToPaste, tick, copiedEventsPPQ\r
-                                       );\r
+                                       getModel().addMidiEvents(copiedEventsToPaste, tick, copiedEventsPPQ);\r
                                        scrollToEventAt(tick);\r
                                        // プレイリストの曲の長さ表示を更新\r
-                                       sequenceListTableModel.fireSelectedSequenceChanged();\r
+                                       getModel().parent.parent.fireSelectedSequenceChanged();\r
                                        eventDialog.setVisible(false);\r
                                }\r
                        };\r
@@ -700,13 +829,16 @@ class MidiEditor extends JDialog implements DropTargetListener {
                 * 新しいイベントの追加を行うアクション\r
                 */\r
                Action queryAddEventAction = new AbstractAction() {\r
-                       { putValue(NAME,"New"); }\r
+                       {\r
+                               putValue(NAME,"New");\r
+                               setEnabled(false);\r
+                       }\r
                        public void actionPerformed(ActionEvent e) {\r
                                setSelectedEvent();\r
-                               midiEventsToBeRemoved = null;\r
+                               midiEventsToBeOverwritten = null; // 追加なので、上書きされるイベントなし\r
                                eventDialog.setTitle("Add a new MIDI event");\r
-                               eventDialog.okButton.setAction(addEventAction);\r
-                               int ch = midiTrackTableModel.getChannel();\r
+                               eventDialog.okButton.setAction(eventCellEditor.addEventAction);\r
+                               int ch = getModel().getChannel();\r
                                if( ch >= 0 ) {\r
                                        eventDialog.midiMessageForm.channelText.setSelectedChannel(ch);\r
                                }\r
@@ -714,48 +846,75 @@ class MidiEditor extends JDialog implements DropTargetListener {
                        }\r
                };\r
                /**\r
-                * イベントの追加(または変更)を行うアクション\r
+                * 上書きして削除対象にする変更前イベント(null可)\r
+                */\r
+               private MidiEvent[] midiEventsToBeOverwritten;\r
+               /**\r
+                * ペースト用にコピーされたMIDIイベントの配列\r
                 */\r
-               private Action addEventAction = new AbstractAction() {\r
-                       { putValue(NAME,"OK"); }\r
+               private MidiEvent copiedEventsToPaste[];\r
+               /**\r
+                * ペースト用にコピーされたMIDIイベントのタイミング解像度\r
+                */\r
+               private int copiedEventsPPQ = 0;\r
+               /**\r
+                * イベントカットアクション\r
+                */\r
+               public Action cutEventAction = new AbstractAction("Cut") {\r
+                       {\r
+                               setEnabled(false);\r
+                       }\r
+                       @Override\r
                        public void actionPerformed(ActionEvent e) {\r
-                               long tick = tickPositionModel.getTickPosition();\r
-                               MidiMessage msg = eventDialog.midiMessageForm.getMessage();\r
-                               MidiEvent newMidiEvent = new MidiEvent(msg,tick);\r
-                               if( midiEventsToBeRemoved != null ) {\r
-                                       midiTrackTableModel.removeMidiEvents(midiEventsToBeRemoved);\r
-                               }\r
-                               if( ! midiTrackTableModel.addMidiEvent(newMidiEvent) ) {\r
-                                       System.out.println("addMidiEvent failure");\r
+                               if( ! confirm("Do you want to cut selected event ?\n選択したMIDIイベントを切り取りますか?"))\r
                                        return;\r
-                               }\r
-                               if(pairNoteOnOffModel.isSelected() && eventDialog.midiMessageForm.isNote()) {\r
-                                       ShortMessage sm = eventDialog.midiMessageForm.getPartnerMessage();\r
-                                       if( sm == null ) scrollToEventAt( tick );\r
-                                       else {\r
-                                               int duration = eventDialog.midiMessageForm.durationForm.getDuration();\r
-                                               if( eventDialog.midiMessageForm.isNote(false) ) { // Note Off\r
-                                                       duration = -duration;\r
-                                               }\r
-                                               long partnerTick = tick + (long)duration;\r
-                                               if( partnerTick < 0L ) partnerTick = 0L;\r
-                                               MidiEvent partner_midi_event =\r
-                                                               new MidiEvent( (MidiMessage)sm, partnerTick );\r
-                                               if( ! midiTrackTableModel.addMidiEvent(partner_midi_event) ) {\r
-                                                       System.out.println("addMidiEvent failure (note on/off partner message)");\r
-                                               }\r
-                                               scrollToEventAt( partnerTick > tick ? partnerTick : tick );\r
-                                       }\r
-                               }\r
-                               sequenceListTableModel.fireSequenceModified(sequenceTableModel);\r
-                               eventDialog.setVisible(false);\r
-                               fireEditingStopped();\r
+                               copiedEventsToPaste = getModel().getSelectedMidiEvents();\r
+                               copiedEventsPPQ = getModel().parent.parent.getSelectedSequenceModel().getSequence().getResolution();\r
+                               getModel().removeMidiEvents(copiedEventsToPaste);\r
+                               queryPasteEventAction.setEnabled(\r
+                                       copiedEventsToPaste != null &&\r
+                                       copiedEventsToPaste.length > 0\r
+                               );\r
+                       }\r
+               };\r
+               /**\r
+                * イベントコピーアクション\r
+                */\r
+               public Action copyEventAction = new AbstractAction("Copy") {\r
+                       {\r
+                               setEnabled(false);\r
+                       }\r
+                       @Override\r
+                       public void actionPerformed(ActionEvent e) {\r
+                               copiedEventsToPaste = getModel().getSelectedMidiEvents();\r
+                               copiedEventsPPQ = getModel().parent.parent.getSelectedSequenceModel().getSequence().getResolution();\r
+                               queryPasteEventAction.setEnabled(\r
+                                       copiedEventsToPaste != null &&\r
+                                       copiedEventsToPaste.length > 0\r
+                               );\r
+                       }\r
+               };\r
+               /**\r
+                * イベント削除アクション\r
+                */\r
+               public Action deleteEventAction = new AbstractAction("Delete", deleteIcon) {\r
+                       {\r
+                               setEnabled(false);\r
+                       }\r
+                       @Override\r
+                       public void actionPerformed(ActionEvent e) {\r
+                               if( ! confirm("Do you want to delete selected event ?\n選択したMIDIイベントを削除しますか?"))\r
+                                       return;\r
+                               getModel().removeSelectedMidiEvents();\r
                        }\r
                };\r
                /**\r
                 * イベント編集アクション\r
                 */\r
                private Action editEventAction = new AbstractAction() {\r
+                       {\r
+                               setEnabled(false);\r
+                       }\r
                        public void actionPerformed(ActionEvent e) {\r
                                setSelectedEvent();\r
                                if( selectedMidiEvent == null )\r
@@ -763,12 +922,12 @@ class MidiEditor extends JDialog implements DropTargetListener {
                                MidiEvent partnerEvent = null;\r
                                eventDialog.midiMessageForm.setMessage(selectedMidiEvent.getMessage());\r
                                if( eventDialog.midiMessageForm.isNote() ) {\r
-                                       int partnerIndex = midiTrackTableModel.getIndexOfPartnerFor(selectedIndex);\r
+                                       int partnerIndex = getModel().getIndexOfPartnerFor(selectedIndex);\r
                                        if( partnerIndex < 0 ) {\r
                                                eventDialog.midiMessageForm.durationForm.setDuration(0);\r
                                        }\r
                                        else {\r
-                                               partnerEvent = midiTrackTableModel.getMidiEvent(partnerIndex);\r
+                                               partnerEvent = getModel().getMidiEvent(partnerIndex);\r
                                                long partnerTick = partnerEvent.getTick();\r
                                                long duration = currentTick > partnerTick ?\r
                                                        currentTick - partnerTick : partnerTick - currentTick ;\r
@@ -785,103 +944,139 @@ class MidiEditor extends JDialog implements DropTargetListener {
                                        events[0] = selectedMidiEvent;\r
                                        events[1] = partnerEvent;\r
                                }\r
-                               midiEventsToBeRemoved = events;\r
+                               midiEventsToBeOverwritten = events;\r
                                eventDialog.setTitle("Change MIDI event");\r
-                               eventDialog.okButton.setAction(addEventAction);\r
+                               eventDialog.okButton.setAction(eventCellEditor.addEventAction);\r
                                eventDialog.openEventForm();\r
                        }\r
-               };\r
-               public MidiEventCellEditor() {\r
-                       eventDialog.midiMessageForm.setOutputMidiChannels(virtualMidiDevice.getChannels());\r
-                       eventDialog.tickPositionInputForm.setModel(tickPositionModel);\r
-               }\r
-               @Override\r
-               public boolean isCellEditable(EventObject e) {\r
-                       // ダブルクリックで編集\r
-                       return e instanceof MouseEvent && ((MouseEvent)e).getClickCount() == 2;\r
+               };\r
+               /**\r
+                * MIDIイベント表のセルエディタ\r
+                */\r
+               private MidiEventCellEditor eventCellEditor = new MidiEventCellEditor();\r
+               /**\r
+                * MIDIイベント表のセルエディタ\r
+                */\r
+               class MidiEventCellEditor extends AbstractCellEditor implements TableCellEditor {\r
+                       /**\r
+                        * イベント入力をキャンセルするアクション\r
+                        */\r
+                       Action cancelAction = new AbstractAction() {\r
+                               {\r
+                                       putValue(NAME,"Cancel");\r
+                                       eventDialog.cancelButton.setAction(this);\r
+                               }\r
+                               public void actionPerformed(ActionEvent e) {\r
+                                       fireEditingCanceled();\r
+                                       eventDialog.setVisible(false);\r
+                               }\r
+                       };\r
+                       /**\r
+                        * イベントの追加(または変更)を行うアクション\r
+                        */\r
+                       private Action addEventAction = new AbstractAction() {\r
+                               { putValue(NAME,"OK"); }\r
+                               public void actionPerformed(ActionEvent e) {\r
+                                       long tick = tickPositionModel.getTickPosition();\r
+                                       MidiMessage msg = eventDialog.midiMessageForm.getMessage();\r
+                                       MidiEvent newMidiEvent = new MidiEvent(msg,tick);\r
+                                       if( midiEventsToBeOverwritten != null ) {\r
+                                               // 上書き消去するための選択済イベントがあった場合\r
+                                               getModel().removeMidiEvents(midiEventsToBeOverwritten);\r
+                                       }\r
+                                       if( ! getModel().addMidiEvent(newMidiEvent) ) {\r
+                                               System.out.println("addMidiEvent failure");\r
+                                               return;\r
+                                       }\r
+                                       if(pairNoteOnOffModel.isSelected() && eventDialog.midiMessageForm.isNote()) {\r
+                                               ShortMessage sm = eventDialog.midiMessageForm.getPartnerMessage();\r
+                                               if( sm == null ) scrollToEventAt( tick );\r
+                                               else {\r
+                                                       int duration = eventDialog.midiMessageForm.durationForm.getDuration();\r
+                                                       if( eventDialog.midiMessageForm.isNote(false) ) { // Note Off\r
+                                                               duration = -duration;\r
+                                                       }\r
+                                                       long partnerTick = tick + (long)duration;\r
+                                                       if( partnerTick < 0L ) partnerTick = 0L;\r
+                                                       MidiEvent partner_midi_event =\r
+                                                                       new MidiEvent( (MidiMessage)sm, partnerTick );\r
+                                                       if( ! getModel().addMidiEvent(partner_midi_event) ) {\r
+                                                               System.out.println("addMidiEvent failure (note on/off partner message)");\r
+                                                       }\r
+                                                       scrollToEventAt( partnerTick > tick ? partnerTick : tick );\r
+                                               }\r
+                                       }\r
+                                       getModel().parent.parent.fireSequenceModified(getModel().parent);\r
+                                       eventDialog.setVisible(false);\r
+                                       fireEditingStopped();\r
+                               }\r
+                       };\r
+                       public MidiEventCellEditor() {\r
+                               eventDialog.midiMessageForm.setOutputMidiChannels(virtualMidiDevice.getChannels());\r
+                               eventDialog.tickPositionInputForm.setModel(tickPositionModel);\r
+                       }\r
+                       @Override\r
+                       public boolean isCellEditable(EventObject e) {\r
+                               // ダブルクリックで編集\r
+                               return e instanceof MouseEvent && ((MouseEvent)e).getClickCount() == 2;\r
+                       }\r
+                       @Override\r
+                       public Object getCellEditorValue() { return ""; }\r
+                       private JButton editEventButton = new JButton(editEventAction){\r
+                               {\r
+                                       setHorizontalAlignment(JButton.LEFT);\r
+                               }\r
+                       };\r
+                       @Override\r
+                       public Component getTableCellEditorComponent(\r
+                               JTable table, Object value, boolean isSelected, int row, int column\r
+                       ) {\r
+                               editEventButton.setText((String)value);\r
+                               return editEventButton;\r
+                       }\r
                }\r
-               @Override\r
-               public Object getCellEditorValue() { return ""; }\r
-               private JButton editEventButton = new JButton(editEventAction){\r
-                       {\r
-                               setHorizontalAlignment(JButton.LEFT);\r
-                       }\r
-               };\r
-               @Override\r
-               public Component getTableCellEditorComponent(\r
-                       JTable table, Object value, boolean isSelected, int row, int column\r
-               ) {\r
-                       editEventButton.setText((String)value);\r
-                       return editEventButton;\r
+               /**\r
+                * スクロール可能なMIDIイベントテーブルビュー\r
+                */\r
+               private JScrollPane scrollPane = new JScrollPane(this);\r
+               /**\r
+                * 指定の MIDI tick のイベントへスクロールします。\r
+                * @param tick MIDI tick\r
+                */\r
+               public void scrollToEventAt(long tick) {\r
+                       int index = getModel().tickToIndex(tick);\r
+                       scrollPane.getVerticalScrollBar().setValue(index * getRowHeight());\r
+                       getSelectionModel().setSelectionInterval(index, index);\r
                }\r
        }\r
-\r
-       /**\r
-        * ペースト用にコピーされたMIDIイベントの配列\r
-        */\r
-       private MidiEvent copiedEventsToPaste[];\r
        /**\r
-        * ペースト用にコピーされたMIDIイベントのタイミング解像度\r
-        */\r
-       private int copiedEventsPPQ = 0;\r
-       /**\r
-        * イベントカットアクション\r
-        */\r
-       public Action cutEventAction = new AbstractAction("Cut") {\r
-               @Override\r
-               public void actionPerformed(ActionEvent e) {\r
-                       if( ! confirm("Do you want to cut selected event ?\n選択したMIDIイベントを切り取りますか?"))\r
-                               return;\r
-                       TrackEventListTableModel trackTableModel = (TrackEventListTableModel)trackEventListTableView.getModel();\r
-                       copiedEventsToPaste = trackTableModel.getMidiEvents(eventSelectionModel);\r
-                       copiedEventsPPQ = sequenceListTableModel.getSelectedSequenceModel().getSequence().getResolution();\r
-                       trackTableModel.removeMidiEvents(copiedEventsToPaste);\r
-                       // プレイリストの曲の長さ表示を更新\r
-                       sequenceListTableModel.fireSelectedSequenceChanged();\r
-               }\r
-       };\r
-       /**\r
-        * イベントコピーアクション\r
-        */\r
-       public Action copyEventAction = new AbstractAction("Copy") {\r
-               @Override\r
-               public void actionPerformed(ActionEvent e) {\r
-                       TrackEventListTableModel trackTableModel = (TrackEventListTableModel)trackEventListTableView.getModel();\r
-                       copiedEventsToPaste = trackTableModel.getMidiEvents(eventSelectionModel);\r
-                       copiedEventsPPQ = sequenceListTableModel.getSelectedSequenceModel().getSequence().getResolution();\r
-                       updateButtonStatus();\r
-               }\r
-       };\r
-       /**\r
-        * イベント削除アクション\r
+        * MIDIイベントリストテーブルビュー\r
         */\r
-       public Action deleteEventAction = new AbstractAction("Delete", deleteIcon) {\r
-               @Override\r
-               public void actionPerformed(ActionEvent e) {\r
-                       if( ! confirm("Do you want to delete selected event ?\n選択したMIDIイベントを削除しますか?"))\r
-                               return;\r
-                       ((TrackEventListTableModel)trackEventListTableView.getModel()).removeMidiEvents(eventSelectionModel);\r
-                       // プレイリストの曲の長さ表示を更新\r
-                       sequenceListTableModel.fireSelectedSequenceChanged();\r
-               }\r
-       };\r
+       EventListTable eventListTableView;\r
 \r
        /**\r
         * 新しい {@link MidiEditor} を構築します。\r
         * @param deviceModelList MIDIデバイスモデルリスト\r
         */\r
        public MidiEditor(MidiSequencerModel sequencerModel) {\r
-               sequenceListTableModel = new SequenceListTableModel(sequencerModel) ;\r
-               sequenceTrackListTableModel = new SequenceTrackListTableModel(sequenceListTableModel);\r
+               // テーブルモデルとテーブルビューの生成\r
+               sequenceListTableView = new SequenceListTable(\r
+                       sequenceListTableModel = new SequenceListTableModel(sequencerModel)\r
+               );\r
+               trackListTableView = new TrackListTable(\r
+                       sequenceListTableModel.emptySequenceModel\r
+               );\r
+               eventListTableView = new EventListTable(\r
+                       sequenceListTableModel.emptySequenceModel.emptyTrackModel\r
+               );\r
+               // レイアウト\r
                setTitle("MIDI Editor/Playlist - MIDI Chord Helper");\r
                setBounds( 150, 200, 850, 500 );\r
                setLayout(new FlowLayout());\r
                new DropTarget(this, DnDConstants.ACTION_COPY_OR_MOVE, this, true);\r
                JPanel playlistPanel = new JPanel() {{\r
                        setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));\r
-                       add(new JScrollPane(\r
-                               sequenceListTableView = new SequenceListTable(sequenceListTableModel)\r
-                       ));\r
+                       add(new JScrollPane(sequenceListTableView));\r
                        add(Box.createRigidArea(new Dimension(0, 10)));\r
                        add(new JPanel() {{\r
                                setLayout(new BoxLayout(this, BoxLayout.LINE_AXIS));\r
@@ -936,110 +1131,45 @@ class MidiEditor extends JDialog implements DropTargetListener {
                }};\r
                JPanel trackListPanel = new JPanel() {{\r
                        setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));\r
-                       add(new JLabel() {\r
-                               private static final String TITLE = "Tracks";\r
-                               {\r
-                                       sequenceListTableModel.selectionModel.addListSelectionListener(\r
-                                               new ListSelectionListener() {\r
-                                                       @Override\r
-                                                       public void valueChanged(ListSelectionEvent e) {\r
-                                                               if( e.getValueIsAdjusting() )\r
-                                                                       return;\r
-                                                               int index = sequenceListTableModel.selectionModel.getMinSelectionIndex();\r
-                                                               String text = TITLE;\r
-                                                               if( index >= 0 )\r
-                                                                       text = String.format(text+" - MIDI file No.%d", index);\r
-                                                               setText(text);\r
-                                                       }\r
-                                               }\r
-                                       );\r
-                                       setText(TITLE);\r
-                               }\r
-                       });\r
+                       add(trackListTableView.titleLabel);\r
                        add(Box.createRigidArea(new Dimension(0, 5)));\r
-                       add(new JScrollPane(trackListTableView = new JTable(\r
-                               sequenceTrackListTableModel, null,\r
-                               trackSelectionModel = new DefaultListSelectionModel() {{\r
-                                       setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);\r
-                                       addListSelectionListener(new ListSelectionListener() {\r
-                                               @Override\r
-                                               public void valueChanged(ListSelectionEvent e) {\r
-                                                       if( e.getValueIsAdjusting() )\r
-                                                               return;\r
-                                                       SequenceTrackListTableModel sequenceModel =\r
-                                                               sequenceListTableModel.getSelectedSequenceModel();\r
-                                                       if( sequenceModel == null || isSelectionEmpty() ) {\r
-                                                               trackEventListTableView.setModel(new TrackEventListTableModel());\r
-                                                       }\r
-                                                       else {\r
-                                                               TrackEventListTableModel trackModel = sequenceModel.getTrackModel(getMinSelectionIndex());\r
-                                                               if( trackModel == null ) {\r
-                                                                       trackEventListTableView.setModel(new TrackEventListTableModel());\r
-                                                               }\r
-                                                               else {\r
-                                                                       trackEventListTableView.setModel(trackModel);\r
-                                                                       TableColumnModel tcm = trackEventListTableView.getColumnModel();\r
-                                                                       trackModel.sizeColumnWidthToFit(tcm);\r
-                                                                       TableColumn midiMessageColumn = tcm.getColumn(TrackEventListTableModel.Column.MESSAGE.ordinal());\r
-                                                                       midiMessageColumn.setCellEditor(eventCellEditor);\r
-                                                               }\r
-                                                       }\r
-                                                       updateButtonStatus();\r
-                                               }\r
-                                       });\r
-                               }}\r
-                       ) {{\r
-                               // 録音対象のMIDIチャンネルをコンボボックスで選択できるよう、\r
-                               // セルエディタを差し替える。\r
-                               getColumnModel().getColumn(\r
-                                       SequenceTrackListTableModel.Column.RECORD_CHANNEL.ordinal()\r
-                               ).setCellEditor(\r
-                                       new DefaultCellEditor(new JComboBox<String>() {{\r
-                                               addItem("OFF");\r
-                                               for(int i=1; i <= MIDISpec.MAX_CHANNELS; i++)\r
-                                                       addItem(String.format("%d", i));\r
-                                               addItem("ALL");\r
-                                       }} )\r
-                               );\r
-                               // デフォルトでは、データモデルが差し替えられると列が再作成される。\r
-                               // しかしこれでは、シーケンスモデルを差し替えたとたん、\r
-                               // せっかく差し替えたセルエディタがデフォルトに戻ってしまう。\r
-                               //\r
-                               // そこで、一度列データモデルが自動作成されたら、\r
-                               // 以後は自動作成しないようにする。\r
-                               setAutoCreateColumnsFromModel(false);\r
-                       }}));\r
+                       add(new JScrollPane(trackListTableView));\r
                        add(Box.createRigidArea(new Dimension(0, 5)));\r
                        add(new JPanel() {{\r
-                               add(new JButton(addTrackAction) {{ setMargin(ZERO_INSETS); }});\r
-                               add(new JButton(deleteTrackAction) {{ setMargin(ZERO_INSETS); }});\r
+                               add(new JButton(trackListTableView.addTrackAction) {{\r
+                                       setMargin(ZERO_INSETS);\r
+                               }});\r
+                               add(new JButton(trackListTableView.deleteTrackAction) {{\r
+                                       setMargin(ZERO_INSETS);\r
+                               }});\r
                        }});\r
                }};\r
                JPanel eventListPanel = new JPanel() {{\r
-                       add(new MidiEventsLabel());\r
-                       add(scrollableEventTableView = new JScrollPane(\r
-                               trackEventListTableView = new JTable(\r
-                                       new TrackEventListTableModel(),\r
-                                       null,\r
-                                       eventSelectionModel\r
-                               ) {{\r
-                                       setAutoCreateColumnsFromModel(false);\r
-                               }}\r
-                       ));\r
+                       add(eventListTableView.titleLabel);\r
+                       add(eventListTableView.scrollPane);\r
                        add(new JPanel() {{\r
-                               add(pairNoteCheckbox);\r
-                               add(new JButton(eventCellEditor.queryJumpEventAction) {{\r
+                               add(new JCheckBox("Pair NoteON/OFF") {{\r
+                                       setModel(eventListTableView.pairNoteOnOffModel);\r
+                                       setToolTipText("NoteON/OFFをペアで同時選択する");\r
+                               }});\r
+                               add(new JButton(eventListTableView.queryJumpEventAction) {{\r
+                                       setMargin(ZERO_INSETS);\r
+                               }});\r
+                               add(new JButton(eventListTableView.queryAddEventAction) {{\r
                                        setMargin(ZERO_INSETS);\r
                                }});\r
-                               add(new JButton(eventCellEditor.queryAddEventAction) {{\r
+                               add(new JButton(eventListTableView.copyEventAction) {{\r
                                        setMargin(ZERO_INSETS);\r
                                }});\r
-                               add(new JButton(copyEventAction) {{ setMargin(ZERO_INSETS); }});\r
-                               add(new JButton(cutEventAction) {{ setMargin(ZERO_INSETS); }});\r
-                               add(new JButton(eventCellEditor.queryPasteEventAction) {{\r
+                               add(new JButton(eventListTableView.cutEventAction) {{\r
+                                       setMargin(ZERO_INSETS);\r
+                               }});\r
+                               add(new JButton(eventListTableView.queryPasteEventAction) {{\r
+                                       setMargin(ZERO_INSETS);\r
+                               }});\r
+                               add(new JButton(eventListTableView.deleteEventAction) {{\r
                                        setMargin(ZERO_INSETS);\r
                                }});\r
-                               add(new JButton(deleteEventAction) {{ setMargin(ZERO_INSETS); }});\r
                        }});\r
                        setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));\r
                }};\r
@@ -1055,106 +1185,6 @@ class MidiEditor extends JDialog implements DropTargetListener {
                                setDividerLocation(160);\r
                        }}\r
                );\r
-               updateButtonStatus();\r
-       }\r
-\r
-       /**\r
-        * ボタン状態の更新\r
-        */\r
-       public void updateButtonStatus() {\r
-               SequenceTrackListTableModel\r
-                       sequenceModel = sequenceListTableModel.getSelectedSequenceModel();\r
-               if(sequenceModel == null)\r
-                       trackListTableView.setModel(sequenceListTableModel.emptySequenceModel);\r
-               else\r
-                       trackListTableView.setModel(sequenceModel);\r
-               addTrackAction.setEnabled(sequenceModel != null);\r
-               boolean isTrackSelected = (\r
-                       ! trackSelectionModel.isSelectionEmpty()\r
-                       &&\r
-                       sequenceModel != null && sequenceModel.getRowCount() > 0\r
-               );\r
-               deleteTrackAction.setEnabled(isTrackSelected);\r
-               //\r
-               TableModel tm = trackEventListTableView.getModel();\r
-               if( ! (tm instanceof TrackEventListTableModel) )\r
-                       return;\r
-               TrackEventListTableModel trackTableModel = (TrackEventListTableModel)tm;\r
-               boolean isEventSelected = (\r
-                       isTrackSelected &&\r
-                       ! eventSelectionModel.isSelectionEmpty() &&\r
-                       trackTableModel != null && trackTableModel.getRowCount() > 0\r
-               );\r
-               copyEventAction.setEnabled(isEventSelected);\r
-               deleteEventAction.setEnabled(isEventSelected);\r
-               cutEventAction.setEnabled(isEventSelected);\r
-               eventCellEditor.queryJumpEventAction.setEnabled(\r
-                       trackTableModel != null && isTrackSelected\r
-               );\r
-               eventCellEditor.queryAddEventAction.setEnabled(\r
-                       trackTableModel != null && isTrackSelected\r
-               );\r
-               eventCellEditor.queryPasteEventAction.setEnabled(\r
-                       trackTableModel != null && isTrackSelected &&\r
-                       copiedEventsToPaste != null && copiedEventsToPaste.length > 0\r
-               );\r
-       }\r
-       /**\r
-        * MIDIシーケンスを追加します。\r
-        * シーケンサーが停止中の場合、追加したシーケンスから再生を開始します。\r
-        * @param sequence MIDIシーケンス\r
-        * @return 追加先インデックス(先頭が 0)\r
-        */\r
-       public int addSequenceAndPlay(Sequence sequence) {\r
-               int lastIndex = sequenceListTableModel.addSequence(sequence,"");\r
-               if( ! sequenceListTableModel.sequencerModel.getSequencer().isRunning() ) {\r
-                       sequenceListTableModel.loadToSequencer(lastIndex);\r
-                       sequenceListTableModel.sequencerModel.start();\r
-               }\r
-               return lastIndex;\r
-       }\r
-       /**\r
-        * バイト列とファイル名からMIDIシーケンスを追加します。\r
-        * バイト列が null の場合、空のMIDIシーケンスを追加します。\r
-        * @param data バイト列\r
-        * @param filename ファイル名\r
-        * @return 追加先インデックス(先頭が 0、失敗した場合は -1)\r
-        */\r
-       public int addSequence(byte[] data, String filename) {\r
-               if( data == null ) {\r
-                       return sequenceListTableModel.addDefaultSequence();\r
-               }\r
-               int lastIndex;\r
-               try (InputStream in = new ByteArrayInputStream(data)) {\r
-                       Sequence seq = MidiSystem.getSequence(in);\r
-                       lastIndex = sequenceListTableModel.addSequence(seq, filename);\r
-               } catch( IOException|InvalidMidiDataException e ) {\r
-                       showWarning(e.getMessage());\r
-                       return -1;\r
-               }\r
-               return lastIndex;\r
-       }\r
-       /**\r
-        * URLから読み込んだMIDIシーケンスを追加します。\r
-        * @param midiFileUrl MIDIファイルのURL\r
-        * @return 追加先インデックス(先頭が 0、失敗した場合は -1)\r
-        */\r
-       public int addSequenceFromURL(String midiFileUrl) {\r
-               Sequence seq = null;\r
-               String filename = null;\r
-               try {\r
-                       URI uri = new URI(midiFileUrl);\r
-                       URL url = uri.toURL();\r
-                       seq = MidiSystem.getSequence(url);\r
-                       filename = url.getFile().replaceFirst("^.*/","");\r
-               } catch( URISyntaxException|IOException|InvalidMidiDataException e ) {\r
-                       showWarning(e.getMessage());\r
-               } catch( AccessControlException e ) {\r
-                       showError(e.getMessage());\r
-                       e.printStackTrace();\r
-               }\r
-               if( seq == null ) return -1;\r
-               return sequenceListTableModel.addSequence(seq, filename);\r
        }\r
 \r
        /**\r
@@ -1181,16 +1211,6 @@ class MidiEditor extends JDialog implements DropTargetListener {
                        sequenceListTableModel.sequencerModel.start();\r
                }\r
        }\r
-       /**\r
-        * 選択されているシーケンスが、\r
-        * ユーザ操作により録音可能な設定になったかどうか調べます。\r
-        * @return 選択されているシーケンスが録音可能な設定ならtrue\r
-        */\r
-       public boolean isRecordable() {\r
-               SequenceTrackListTableModel sequenceTableModel =\r
-                       sequenceListTableModel.getSelectedSequenceModel();\r
-               return sequenceTableModel == null ? false : sequenceTableModel.isRecordable();\r
-       }\r
 }\r
 \r
 /**\r
@@ -1245,17 +1265,77 @@ class SequencerSpeedSlider extends JPanel {
 /**\r
  * プレイリスト(MIDIシーケンスリスト)のテーブルデータモデル\r
  */\r
-class SequenceListTableModel extends AbstractTableModel\r
-       implements MetaEventListener\r
-{\r
+class SequenceListTableModel extends AbstractTableModel {\r
        /**\r
         * 新しいプレイリストのテーブルモデルを構築します。\r
         * @param sequencerModel MIDIシーケンサーモデル\r
         */\r
        public SequenceListTableModel(MidiSequencerModel sequencerModel) {\r
                this.sequencerModel = sequencerModel;\r
-               sequencerModel.addChangeListener(secondPosition);\r
-               sequencerModel.getSequencer().addMetaEventListener(this);\r
+               //\r
+               // 秒位置を監視\r
+               sequencerModel.addChangeListener(secondPosition = new SecondPosition());\r
+               //\r
+               // メタイベントを監視\r
+               sequencerModel.getSequencer().addMetaEventListener(\r
+                       new MetaEventListener() {\r
+                               /**\r
+                                * {@inheritDoc}\r
+                                *\r
+                                * <p>EOT (End Of Track、type==0x2F) を受信したときの処理です。\r
+                                * </p>\r
+                                * <p>これは MetaEventListener のための実装なので、多くの場合\r
+                                * Swing EDT ではなく MIDI シーケンサの EDT から起動されます。\r
+                                * Swing EDT とは違うスレッドで動いていた場合は Swing EDT に振り直されます。\r
+                                * </p>\r
+                                */\r
+                               @Override\r
+                               public void meta(MetaMessage msg) {\r
+                                       if( msg.getType() == 0x2F ) {\r
+                                               if( ! SwingUtilities.isEventDispatchThread() ) {\r
+                                                       SwingUtilities.invokeLater(\r
+                                                               new Runnable() {\r
+                                                                       @Override\r
+                                                                       public void run() { goNext(); }\r
+                                                               }\r
+                                                       );\r
+                                                       return;\r
+                                               }\r
+                                               goNext();\r
+                                       }\r
+                               }\r
+                       }\r
+               );\r
+       }\r
+       /**\r
+        * 次の曲へ進みます。\r
+        *\r
+        * <p>リピートモードの場合は同じ曲をもう一度再生、\r
+        * そうでない場合は次の曲へ進んで再生します。\r
+        * 次の曲がなければ、そこで停止します。\r
+        * いずれの場合も局の先頭へ戻ります。\r
+        * </p>\r
+        */\r
+       private void goNext() {\r
+               // とりあえず曲の先頭へ戻る\r
+               sequencerModel.getSequencer().setMicrosecondPosition(0);\r
+               if( (Boolean)toggleRepeatAction.getValue(Action.SELECTED_KEY) || loadNext(1)) {\r
+                       // リピートモードのときはもう一度同じ曲を、\r
+                       // そうでない場合は次の曲を再生開始\r
+                       sequencerModel.start();\r
+               }\r
+               else {\r
+                       // 最後の曲が終わったので、停止状態にする\r
+                       sequencerModel.stop();\r
+                       // ここでボタンが停止状態に変わったはずなので、\r
+                       // 通常であれば再生ボタンが自力で再描画するところだが、\r
+                       //\r
+                       // セルのレンダラーが描く再生ボタンには効かないようなので、\r
+                       // セルを突っついて再表示させる。\r
+                       int rowIndex = indexOfSequenceOnSequencer();\r
+                       int colIndex = Column.SEQ_PLAY.ordinal();\r
+                       fireTableCellUpdated(rowIndex, colIndex);\r
+               }\r
        }\r
        /**\r
         * MIDIシーケンサモデル\r
@@ -1266,7 +1346,7 @@ class SequenceListTableModel extends AbstractTableModel
         */\r
        List<SequenceTrackListTableModel> sequenceList = new Vector<>();\r
        /**\r
-        * 行の選択状態\r
+        * 選択されているシーケンスのインデックス\r
         */\r
        ListSelectionModel selectionModel = new DefaultListSelectionModel() {{\r
                setSelectionMode(ListSelectionModel.SINGLE_SELECTION);\r
@@ -1328,68 +1408,23 @@ class SequenceListTableModel extends AbstractTableModel
                public void actionPerformed(ActionEvent event) { }\r
        };\r
        /**\r
-        * {@inheritDoc}\r
-        *\r
-        * <p>EOT (End Of Track、type==0x2F) を受信したときの処理です。\r
-        * 通常はシーケンサの EDT から起動されるため、\r
-        * Swing の EDT へ振りなおします。\r
-        * </p>\r
-        */\r
-       @Override\r
-       public void meta(MetaMessage msg) {\r
-               if( msg.getType() == 0x2F ) {\r
-                       if( ! SwingUtilities.isEventDispatchThread() ) {\r
-                               SwingUtilities.invokeLater(new Runnable() {\r
-                                       @Override\r
-                                       public void run() { goNext(); }\r
-                               });\r
-                               return;\r
-                       }\r
-                       goNext();\r
-               }\r
-       }\r
-       /**\r
-        * 次の曲へ進みます。\r
-        *\r
-        * <p>リピートモードの場合は同じ曲をもう一度再生、\r
-        * そうでない場合は次の曲へ進んで再生します。\r
-        * 次の曲がなければ、そこで停止します。\r
-        * いずれの場合も局の先頭へ戻ります。\r
-        * </p>\r
-        */\r
-       private void goNext() {\r
-               sequencerModel.getSequencer().setMicrosecondPosition(0);\r
-               if( (Boolean)toggleRepeatAction.getValue(Action.SELECTED_KEY) || loadNext(1) ) {\r
-                       sequencerModel.start();\r
-               }\r
-               else {\r
-                       sequencerModel.stop();\r
-               }\r
-       }\r
-       /**\r
-        * 再生中のシーケンサーの秒位置\r
-        */\r
-       private SecondPosition secondPosition = new SecondPosition();\r
-       /**\r
         * 再生中のシーケンサーの秒位置\r
         */\r
        private class SecondPosition implements ChangeListener {\r
                private int value = 0;\r
                /**\r
                 * 再生中のシーケンサーの秒位置が変わったときに表示を更新します。\r
+                * @param event 変更イベント\r
                 */\r
                @Override\r
-               public void stateChanged(ChangeEvent e) {\r
-                       if( e.getSource() instanceof MidiSequencerModel ) {\r
-                               MidiSequencerModel m = (MidiSequencerModel)e.getSource();\r
-                               int newValue = m.getValue() / 1000;\r
-                               if(value == newValue)\r
-                                       return;\r
-                               // 秒が変わったときだけ更新(小数点以下は無視)\r
+               public void stateChanged(ChangeEvent event) {\r
+                       Object src = event.getSource();\r
+                       if( src instanceof MidiSequencerModel ) {\r
+                               int newValue = ((MidiSequencerModel)src).getValue() / 1000;\r
+                               if(value == newValue) return;\r
                                value = newValue;\r
-                               fireTableCellUpdated(\r
-                                       indexOfSequenceOnSequencer(), Column.SEQ_POSITION.ordinal()\r
-                               );\r
+                               int rowIndex = indexOfSequenceOnSequencer();\r
+                               fireTableCellUpdated(rowIndex, Column.SEQ_POSITION.ordinal());\r
                        }\r
                }\r
                @Override\r
@@ -1489,6 +1524,8 @@ class SequenceListTableModel extends AbstractTableModel
        public boolean isCellEditable(int row, int column) {\r
                return Column.values()[column].isCellEditable();\r
        }\r
+       /** 再生中のシーケンサーの秒位置 */\r
+       private SecondPosition secondPosition;\r
        @Override\r
        public Object getValueAt(int row, int column) {\r
                Column c = Column.values()[column];\r
@@ -1507,7 +1544,8 @@ class SequenceListTableModel extends AbstractTableModel
                }\r
                case RESOLUTION: return sequenceModel.getSequence().getResolution();\r
                case TRACKS: return sequenceModel.getSequence().getTracks().length;\r
-               case SEQ_POSITION: return sequenceModel.isOnSequencer() ? secondPosition : "";\r
+               case SEQ_POSITION:\r
+                       return sequenceModel.isOnSequencer() ? secondPosition : "";\r
                case SEQ_LENGTH: {\r
                        long usec = sequenceModel.getSequence().getMicrosecondLength();\r
                        int sec = (int)( (usec < 0 ? usec += 0x100000000L : usec) / 1000L / 1000L );\r
@@ -1583,8 +1621,7 @@ class SequenceListTableModel extends AbstractTableModel
        }\r
        /**\r
         * 選択されたMIDIシーケンスのテーブルモデルを返します。\r
-        * @param selectionModel 選択状態\r
-        * @return 選択されたMIDIシーケンスのテーブルモデル\r
+        * @return 選択されたMIDIシーケンスのテーブルモデル(非選択時はnull)\r
         */\r
        public SequenceTrackListTableModel getSelectedSequenceModel() {\r
                if( selectionModel.isSelectionEmpty() )\r
@@ -1620,13 +1657,51 @@ class SequenceListTableModel extends AbstractTableModel
                fireTableRowsUpdated(minIndex, maxIndex);\r
        }\r
        /**\r
+        * バイト列とファイル名からMIDIシーケンスを追加します。\r
+        * バイト列が null の場合、空のMIDIシーケンスを追加します。\r
+        * @param data バイト列\r
+        * @param filename ファイル名\r
+        * @return 追加先インデックス(先頭が 0)\r
+        * @throws IOException ファイル読み込みに失敗した場合\r
+        * @throws InvalidMidiDataException MIDIデータが正しくない場合\r
+        */\r
+       public int addSequence(byte[] data, String filename)\r
+               throws IOException, InvalidMidiDataException\r
+       {\r
+               if( data == null ) return addDefaultSequence();\r
+               int lastIndex;\r
+               try (InputStream in = new ByteArrayInputStream(data)) {\r
+                       Sequence seq = MidiSystem.getSequence(in);\r
+                       lastIndex = addSequence(seq, filename);\r
+               } catch( IOException|InvalidMidiDataException e ) {\r
+                       throw e;\r
+               }\r
+               return lastIndex;\r
+       }\r
+       /**\r
+        * MIDIシーケンスを追加します。\r
+        * シーケンサーが停止中の場合、追加したシーケンスから再生を開始します。\r
+        * @param sequence MIDIシーケンス\r
+        * @return 追加先インデックス(先頭が 0)\r
+        */\r
+       public int addSequenceAndPlay(Sequence sequence) {\r
+               int lastIndex = addSequence(sequence,"");\r
+               if( ! sequencerModel.getSequencer().isRunning() ) {\r
+                       loadToSequencer(lastIndex);\r
+                       sequencerModel.start();\r
+               }\r
+               return lastIndex;\r
+       }\r
+       /**\r
         * MIDIシーケンスを追加します。\r
-        * @param seq MIDIシーケンス\r
+        * @param sequence MIDIシーケンス\r
         * @param filename ファイル名\r
         * @return 追加されたシーケンスのインデックス(先頭が 0)\r
         */\r
-       public int addSequence(Sequence seq, String filename) {\r
-               sequenceList.add(new SequenceTrackListTableModel(this, seq, filename));\r
+       public int addSequence(Sequence sequence, String filename) {\r
+               sequenceList.add(\r
+                       new SequenceTrackListTableModel(this, sequence, filename)\r
+               );\r
                int lastIndex = sequenceList.size() - 1;\r
                fireTableRowsInserted(lastIndex, lastIndex);\r
                return lastIndex;\r
@@ -1666,7 +1741,9 @@ class SequenceListTableModel extends AbstractTableModel
         * @throws InvalidMidiDataException ファイル内のMIDIデータが正しくない場合\r
         * @throws IOException ファイル入出力に失敗した場合\r
         */\r
-       public int addSequences(List<File> fileList) throws InvalidMidiDataException, IOException {\r
+       public int addSequences(List<File> fileList)\r
+               throws InvalidMidiDataException, IOException\r
+       {\r
                int firstIndex = -1;\r
                for( File file : fileList ) {\r
                        int lastIndex = addSequence(file);\r
@@ -1676,6 +1753,24 @@ class SequenceListTableModel extends AbstractTableModel
                return firstIndex;\r
        }\r
        /**\r
+        * URLから読み込んだMIDIシーケンスを追加します。\r
+        * @param midiFileUrl MIDIファイルのURL\r
+        * @return 追加先インデックス(先頭が 0、失敗した場合は -1)\r
+        * @throws URISyntaxException URLの形式に誤りがある場合\r
+        * @throws IOException 入出力に失敗した場合\r
+        * @throws InvalidMidiDataException MIDIデータが正しくない場合\r
+        */\r
+       public int addSequenceFromURL(String midiFileUrl)\r
+               throws URISyntaxException, IOException, InvalidMidiDataException\r
+       {\r
+               URI uri = new URI(midiFileUrl);\r
+               URL url = uri.toURL();\r
+               Sequence seq = MidiSystem.getSequence(url);\r
+               String filename = url.getFile().replaceFirst("^.*/","");\r
+               return addSequence(seq, filename);\r
+       }\r
+\r
+       /**\r
         * 選択したシーケンスを除去します。\r
         * @param listSelectionModel 選択状態\r
         */\r
@@ -1774,7 +1869,7 @@ class SequenceTrackListTableModel extends AbstractTableModel {
        /**\r
         * 親のプレイリスト\r
         */\r
-       SequenceListTableModel sequenceListTableModel;\r
+       SequenceListTableModel parent;\r
        /**\r
         * ラップされたMIDIシーケンス\r
         */\r
@@ -1792,11 +1887,21 @@ class SequenceTrackListTableModel extends AbstractTableModel {
         */\r
        private List<TrackEventListTableModel> trackModelList = new ArrayList<>();\r
        /**\r
+        * 空のトラックモデル\r
+        */\r
+       TrackEventListTableModel emptyTrackModel = new TrackEventListTableModel(this);\r
+       /**\r
+        * 選択されているトラックのインデックス\r
+        */\r
+       ListSelectionModel selectionModel = new DefaultListSelectionModel() {{\r
+               setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);\r
+       }};\r
+       /**\r
         * 空の {@link SequenceTrackListTableModel} を構築します。\r
         * @param sequenceListTableModel 親のプレイリスト\r
         */\r
        public SequenceTrackListTableModel(SequenceListTableModel sequenceListTableModel) {\r
-               this.sequenceListTableModel = sequenceListTableModel;\r
+               this.parent = sequenceListTableModel;\r
        }\r
        /**\r
         * MIDIシーケンスとファイル名から {@link SequenceTrackListTableModel} を構築します。\r
@@ -1850,9 +1955,9 @@ class SequenceTrackListTableModel extends AbstractTableModel {
                case TRACK_NUMBER: return row;\r
                case EVENTS: return sequence.getTracks()[row].size();\r
                case MUTE:\r
-                       return isOnSequencer() ? getSequencer().getTrackMute(row) : "";\r
+                       return isOnSequencer() ? parent.sequencerModel.getSequencer().getTrackMute(row) : "";\r
                case SOLO:\r
-                       return isOnSequencer() ? getSequencer().getTrackSolo(row) : "";\r
+                       return isOnSequencer() ? parent.sequencerModel.getSequencer().getTrackSolo(row) : "";\r
                case RECORD_CHANNEL:\r
                        return isOnSequencer() ? trackModelList.get(row).getRecordingChannel() : "";\r
                case CHANNEL: {\r
@@ -1886,10 +1991,10 @@ class SequenceTrackListTableModel extends AbstractTableModel {
                Column c = Column.values()[column];\r
                switch(c) {\r
                case MUTE:\r
-                       getSequencer().setTrackMute(row, ((Boolean)val).booleanValue());\r
+                       parent.sequencerModel.getSequencer().setTrackMute(row, ((Boolean)val).booleanValue());\r
                        break;\r
                case SOLO:\r
-                       getSequencer().setTrackSolo(row, ((Boolean)val).booleanValue());\r
+                       parent.sequencerModel.getSequencer().setTrackSolo(row, ((Boolean)val).booleanValue());\r
                        break;\r
                case RECORD_CHANNEL:\r
                        trackModelList.get(row).setRecordingChannel((String)val);\r
@@ -1937,7 +2042,7 @@ class SequenceTrackListTableModel extends AbstractTableModel {
         * @param sequence MIDIシーケンス\r
         */\r
        private void setSequence(Sequence sequence) {\r
-               getSequencer().recordDisable(null); // The "null" means all tracks\r
+               parent.sequencerModel.getSequencer().recordDisable(null); // The "null" means all tracks\r
                this.sequence = sequence;\r
                int oldSize = trackModelList.size();\r
                if( oldSize > 0 ) {\r
@@ -2021,14 +2126,17 @@ class SequenceTrackListTableModel extends AbstractTableModel {
                int row = indexOf(track);\r
                if( row < 0 ) return;\r
                fireTableRowsUpdated(row, row);\r
-               sequenceListTableModel.fireSequenceModified(this);\r
+               parent.fireSequenceModified(this);\r
        }\r
        /**\r
-        * 指定のインデックスのトラックモデルを返します。\r
+        * 選択されているトラックモデルを返します。\r
         * @param index トラックのインデックス\r
         * @return トラックモデル(見つからない場合null)\r
         */\r
-       public TrackEventListTableModel getTrackModel(int index) {\r
+       public TrackEventListTableModel getSelectedTrackModel() {\r
+               if( selectionModel.isSelectionEmpty() )\r
+                       return null;\r
+               int index = selectionModel.getMinSelectionIndex();\r
                Track tracks[] = sequence.getTracks();\r
                if( tracks.length != 0 ) {\r
                        Track track = tracks[index];\r
@@ -2058,15 +2166,18 @@ class SequenceTrackListTableModel extends AbstractTableModel {
                trackModelList.add(new TrackEventListTableModel(sequence.createTrack(), this));\r
                int lastRow = sequence.getTracks().length - 1;\r
                fireTableRowsInserted(lastRow, lastRow);\r
+               //\r
                // 親のプレイリストのトラック数表示を更新\r
-               sequenceListTableModel.fireSelectedSequenceChanged();\r
+               parent.fireSelectedSequenceChanged();\r
+               //\r
+               // 追加したトラックを選択\r
+               selectionModel.setSelectionInterval(lastRow, lastRow);\r
                return lastRow;\r
        }\r
        /**\r
         * 選択されているトラックを削除します。\r
-        * @param selectionModel 選択状態\r
         */\r
-       public void deleteTracks(ListSelectionModel selectionModel) {\r
+       public void deleteSelectedTracks() {\r
                if( selectionModel.isSelectionEmpty() )\r
                        return;\r
                int minIndex = selectionModel.getMinSelectionIndex();\r
@@ -2080,37 +2191,24 @@ class SequenceTrackListTableModel extends AbstractTableModel {
                }\r
                fireTableRowsDeleted(minIndex, maxIndex);\r
                // 親のプレイリストのトラック数表示を更新\r
-               sequenceListTableModel.fireSelectedSequenceChanged();\r
-       }\r
-       /**\r
-        * MIDIシーケンサを返します。\r
-        * @return MIDIシーケンサ\r
-        */\r
-       public Sequencer getSequencer() {\r
-               return sequenceListTableModel.sequencerModel.getSequencer();\r
+               parent.fireSelectedSequenceChanged();\r
        }\r
        /**\r
         * このシーケンスモデルのシーケンスをシーケンサーが操作しているか調べます。\r
         * @return シーケンサーが操作していたらtrue\r
         */\r
        public boolean isOnSequencer() {\r
-               return sequence == getSequencer().getSequence();\r
+               return sequence == parent.sequencerModel.getSequencer().getSequence();\r
        }\r
        /**\r
-        * 録音可能かどうかを返します。\r
-        *\r
-        * <p>シーケンサーにロード済みで、\r
-        * かつ録音しようとしているチャンネルの設定されたトラックが一つでもあれば、\r
-        * 録音可能です。\r
-        * </p>\r
-        * @return 録音可能であればtrue\r
+        * 録音しようとしているチャンネルの設定されたトラックがあるか調べます。\r
+        * @return 該当トラックがあればtrue\r
         */\r
-       public boolean isRecordable() {\r
-               if( isOnSequencer() ) {\r
-                       int rowCount = getRowCount();\r
-                       int col = Column.RECORD_CHANNEL.ordinal();\r
-                       for( int row=0; row < rowCount; row++ )\r
-                               if( ! "OFF".equals(getValueAt(row, col)) ) return true;\r
+       public boolean hasRecordChannel() {\r
+               int rowCount = getRowCount();\r
+               for( int row=0; row < rowCount; row++ ) {\r
+                       Object value = getValueAt(row, Column.RECORD_CHANNEL.ordinal());\r
+                       if( ! "OFF".equals(value) ) return true;\r
                }\r
                return false;\r
        }\r
@@ -2125,19 +2223,18 @@ class TrackEventListTableModel extends AbstractTableModel {
         */\r
        public enum Column {\r
                /** MIDIイベント番号 */\r
-               EVENT_NUMBER("No.", 30, Integer.class),\r
+               EVENT_NUMBER("No.", Integer.class),\r
                /** tick位置 */\r
-               TICK_POSITION("TickPos.", 40, Long.class),\r
+               TICK_POSITION("TickPos.", Long.class),\r
                /** tick位置に対応する小節 */\r
-               MEASURE_POSITION("Measure", 20, Integer.class),\r
+               MEASURE_POSITION("Measure", Integer.class),\r
                /** tick位置に対応する拍 */\r
-               BEAT_POSITION("Beat", 20, Integer.class),\r
+               BEAT_POSITION("Beat", Integer.class),\r
                /** tick位置に対応する余剰tick(拍に収まらずに余ったtick数) */\r
-               EXTRA_TICK_POSITION("ExTick", 20, Integer.class),\r
+               EXTRA_TICK_POSITION("ExTick", Integer.class),\r
                /** MIDIメッセージ */\r
-               MESSAGE("MIDI Message", 280, String.class);\r
+               MESSAGE("MIDI Message", String.class);\r
                private String title;\r
-               private int widthRatio;\r
                private Class<?> columnClass;\r
                /**\r
                 * 列の識別子を構築します。\r
@@ -2145,20 +2242,10 @@ class TrackEventListTableModel extends AbstractTableModel {
                 * @param widthRatio 幅の割合\r
                 * @param columnClass 列のクラス\r
                 */\r
-               private Column(String title, int widthRatio, Class<?> columnClass) {\r
+               private Column(String title, Class<?> columnClass) {\r
                        this.title = title;\r
-                       this.widthRatio = widthRatio;\r
                        this.columnClass = columnClass;\r
                }\r
-               /**\r
-                * 幅の割合の合計を返します。\r
-                * @return 幅の割合の合計\r
-                */\r
-               public static int totalWidthRatio() {\r
-                       int total = 0;\r
-                       for( Column c : values() ) total += c.widthRatio;\r
-                       return total;\r
-               }\r
        }\r
        /**\r
         * ラップされているMIDIトラック\r
@@ -2167,7 +2254,15 @@ class TrackEventListTableModel extends AbstractTableModel {
        /**\r
         * 親のシーケンスモデル\r
         */\r
-       private SequenceTrackListTableModel parent;\r
+       SequenceTrackListTableModel parent;\r
+       /**\r
+        * 選択されているイベントのインデックス\r
+        */\r
+       ListSelectionModel selectionModel = new DefaultListSelectionModel() {\r
+               {\r
+                       setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);\r
+               }\r
+       };\r
        /**\r
         * 空のMIDIトラックモデルを構築します。\r
         */\r
@@ -2285,19 +2380,7 @@ class TrackEventListTableModel extends AbstractTableModel {
                fireTableDataChanged();\r
                if( MIDISpec.isEOT(msg) ) {\r
                        // EOTの場所が変わると曲の長さが変わるので、親モデルへ通知する。\r
-                       parent.sequenceListTableModel.fireSequenceModified(parent);\r
-               }\r
-       }\r
-       /**\r
-        * 列に合わせて幅を調整します。\r
-        * @param columnModel テーブル列モデル\r
-        */\r
-       public void sizeColumnWidthToFit(TableColumnModel columnModel) {\r
-               int totalWidth = columnModel.getTotalColumnWidth();\r
-               int totalWidthRatio = Column.totalWidthRatio();\r
-               for( Column c : Column.values() ) {\r
-                       int w = totalWidth * c.widthRatio / totalWidthRatio;\r
-                       columnModel.getColumn(c.ordinal()).setPreferredWidth(w);\r
+                       parent.parent.fireSequenceModified(parent);\r
                }\r
        }\r
        /**\r
@@ -2319,7 +2402,7 @@ class TrackEventListTableModel extends AbstractTableModel {
                if(name.equals(toString()) || ! MIDISpec.setNameOf(track, name))\r
                        return false;\r
                parent.setModified(true);\r
-               parent.sequenceListTableModel.fireSequenceModified(parent);\r
+               parent.parent.fireSequenceModified(parent);\r
                fireTableDataChanged();\r
                return true;\r
        }\r
@@ -2334,7 +2417,7 @@ class TrackEventListTableModel extends AbstractTableModel {
         * @param recordingChannel 録音中のMIDIチャンネル\r
         */\r
        public void setRecordingChannel(String recordingChannel) {\r
-               Sequencer sequencer = parent.getSequencer();\r
+               Sequencer sequencer = parent.parent.sequencerModel.getSequencer();\r
                if( recordingChannel.equals("OFF") ) {\r
                        sequencer.recordDisable( track );\r
                }\r
@@ -2513,7 +2596,6 @@ class TrackEventListTableModel extends AbstractTableModel {
                int cmd = ((ShortMessage)msg).getCommand();\r
                return cmd == ShortMessage.NOTE_ON || cmd == ShortMessage.NOTE_OFF ;\r
        }\r
-       public boolean hasTrack() { return track != null; }\r
        /**\r
         * 指定の行インデックスのMIDIイベントを返します。\r
         * @param index 行インデックス\r
@@ -2522,10 +2604,9 @@ class TrackEventListTableModel extends AbstractTableModel {
        public MidiEvent getMidiEvent(int index) { return track.get(index); }\r
        /**\r
         * 選択されているMIDIイベントを返します。\r
-        * @param selectionModel 選択状態モデル\r
         * @return 選択されているMIDIイベント\r
         */\r
-       public MidiEvent[] getMidiEvents(ListSelectionModel selectionModel) {\r
+       public MidiEvent[] getSelectedMidiEvents() {\r
                Vector<MidiEvent> events = new Vector<MidiEvent>();\r
                if( ! selectionModel.isSelectionEmpty() ) {\r
                        int i = selectionModel.getMinSelectionIndex();\r
@@ -2588,6 +2669,7 @@ class TrackEventListTableModel extends AbstractTableModel {
        }\r
        /**\r
         * MIDIイベントを除去します。\r
+        * 曲の長さが変わることがあるので、プレイリストにも通知します。\r
         * @param midiEvents 除去するMIDIイベント\r
         */\r
        public void removeMidiEvents(MidiEvent midiEvents[]) {\r
@@ -2597,19 +2679,22 @@ class TrackEventListTableModel extends AbstractTableModel {
                                hadTimeSignature = true;\r
                        track.remove(e);\r
                }\r
-               if( hadTimeSignature ) parent.fireTimeSignatureChanged();\r
+               if( hadTimeSignature ) {\r
+                       parent.fireTimeSignatureChanged();\r
+               }\r
                parent.fireTrackChanged(track);\r
                int lastIndex = track.size() - 1;\r
                int oldLastIndex = lastIndex + midiEvents.length;\r
                if(lastIndex < 0) lastIndex = 0;\r
                fireTableRowsDeleted(oldLastIndex, lastIndex);\r
+               parent.parent.fireSelectedSequenceChanged();\r
        }\r
        /**\r
         * 引数の選択内容が示すMIDIイベントを除去します。\r
         * @param selectionModel 選択内容\r
         */\r
-       public void removeMidiEvents(ListSelectionModel selectionModel) {\r
-               removeMidiEvents(getMidiEvents(selectionModel));\r
+       public void removeSelectedMidiEvents() {\r
+               removeMidiEvents(getSelectedMidiEvents());\r
        }\r
        private boolean isRhythmPart(int ch) { return (ch == 9); }\r
        /**\r
index b41b298..3a57120 100644 (file)
@@ -176,7 +176,7 @@ class MidiSequencerModel extends MidiConnecterListModel
         * MIDIシーケンサモデルを構築します。\r
         * @param deviceModelList 親のMIDIデバイスモデルリスト\r
         * @param sequencer シーケンサーMIDIデバイス\r
-        * @param modelList MIDIコネクタリストモデルのリスト\r
+        * @param modelList MIDIコネクタリストモデルのリスト(タイムスタンプリセット対象)\r
         */\r
        public MidiSequencerModel(\r
                MidiDeviceModelList deviceModelList,\r
@@ -187,10 +187,6 @@ class MidiSequencerModel extends MidiConnecterListModel
                this.deviceModelList = deviceModelList;\r
        }\r
        /**\r
-        * MIDIデバイスモデルリスト\r
-        */\r
-       private MidiDeviceModelList deviceModelList;\r
-       /**\r
         * このシーケンサーの再生スピード調整モデル\r
         */\r
        BoundedRangeModel speedSliderModel = new DefaultBoundedRangeModel(0, 0, -7, 7) {{\r
@@ -264,7 +260,16 @@ class MidiSequencerModel extends MidiConnecterListModel
                }\r
        );\r
        /**\r
+        * MIDIデバイスモデルリスト(タイムスタンプリセット対象)\r
+        */\r
+       private MidiDeviceModelList deviceModelList;\r
+       /**\r
         * このモデルのMIDIシーケンサを開始します。\r
+        *\r
+        * <p>録音するMIDIチャンネルがMIDIエディタで指定されている場合、\r
+        * 録音スタート時のタイムスタンプが正しく0になるよう、\r
+        * 各MIDIデバイスのタイムスタンプをすべてリセットします。\r
+        * </p>\r
         */\r
        public void start() {\r
                Sequencer sequencer = getSequencer();\r
@@ -275,7 +280,17 @@ class MidiSequencerModel extends MidiConnecterListModel
                }\r
                startStopAction.setRunning(true);\r
                timeRangeUpdater.start();\r
-               deviceModelList.startSequencerWithResetTimestamps();\r
+               SequenceTrackListTableModel sequenceTableModel = getSequenceTableModel();\r
+               if( sequenceTableModel != null && sequenceTableModel.hasRecordChannel() ) {\r
+                       for(MidiConnecterListModel m : deviceModelList)\r
+                               m.resetMicrosecondPosition();\r
+                       System.gc();\r
+                       sequencer.startRecording();\r
+               }\r
+               else {\r
+                       System.gc();\r
+                       sequencer.start();\r
+               }\r
                fireStateChanged();\r
        }\r
        /**\r
@@ -390,8 +405,9 @@ class MidiSequencerModel extends MidiConnecterListModel
         * @param sequenceTableModel MIDIトラックリストテーブルモデル\r
         * @return 成功したらtrue\r
         */\r
-       public boolean setSequenceTrackListTableModel(SequenceTrackListTableModel sequenceTableModel) {\r
-               //\r
+       public boolean setSequenceTrackListTableModel(\r
+               SequenceTrackListTableModel sequenceTableModel\r
+       ) {\r
                // javax.sound.midi:Sequencer.setSequence() のドキュメントにある\r
                // 「このメソッドは、Sequencer が閉じている場合でも呼び出すことができます。 」\r
                // という記述は、null をセットする場合には当てはまらない。\r
index 07a0cb3..bd701dc 100644 (file)
@@ -92,7 +92,7 @@ class NewSequenceDialog extends JDialog {
        ) {\r
                @Override\r
                public void actionPerformed(ActionEvent e) {\r
-                       midiEditor.addSequenceAndPlay(getMidiSequence());\r
+                       midiEditor.sequenceListTableModel.addSequenceAndPlay(getMidiSequence());\r
                        NewSequenceDialog.this.setVisible(false);\r
                }\r
        };\r