OSDN Git Service

リファクタリング(プレイリストの選択モデルをデータモデルに包含)
authorAkiyoshi Kamide <kamide@yk.rim.or.jp>
Tue, 26 Nov 2013 17:46:23 +0000 (17:46 +0000)
committerAkiyoshi Kamide <kamide@yk.rim.or.jp>
Tue, 26 Nov 2013 17:46:23 +0000 (17:46 +0000)
git-svn-id: https://svn.sourceforge.jp/svnroot/midichordhelper/MIDIChordHelper@17 302f1594-2db2-43b1-aaa4-6307b5a2a2de

src/Base64Dialog.java
src/ChordHelperApplet.java
src/MIDIEditor.java

index 5709fcb..f9c85e2 100644 (file)
@@ -42,7 +42,7 @@ public class Base64Dialog extends JDialog {
                                base64TextArea.requestFocusInWindow();\r
                                lastIndex = midiEditor.sequenceListTableModel.getRowCount() - 1;\r
                        }\r
-                       midiEditor.sequenceListSelectionModel.setSelectionInterval(lastIndex, lastIndex);\r
+                       midiEditor.sequenceListTableModel.selectionModel.setSelectionInterval(lastIndex, lastIndex);\r
                        setVisible(false);\r
                }\r
        };\r
index 29041d8..f1d6d4f 100644 (file)
@@ -123,7 +123,7 @@ public class ChordHelperApplet extends JApplet {
         * シーケンサへロードして再生します。\r
         */\r
        public void play() {\r
-               play(editorDialog.sequenceListSelectionModel.getMinSelectionIndex());\r
+               play(editorDialog.sequenceListTableModel.selectionModel.getMinSelectionIndex());\r
        }\r
        /**\r
         * 指定されたインデックス値が示すプレイリスト上のMIDIシーケンスを、\r
@@ -251,7 +251,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.20131126.1";\r
+               public static final String      VERSION = "Ver.20131127.1";\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
@@ -494,7 +494,7 @@ public class ChordHelperApplet extends JApplet {
                        @Override\r
                        public void stateChanged(ChangeEvent e) {\r
                                SequenceTrackListTableModel sequenceTableModel = deviceModelList.sequencerModel.getSequenceTableModel();\r
-                               int loadedSequenceIndex = editorDialog.sequenceListTableModel.getLoadedIndex();\r
+                               int loadedSequenceIndex = editorDialog.sequenceListTableModel.indexOfSequenceOnSequencer();\r
                                songTitleLabel.setText(\r
                                        "<html>"+(\r
                                                loadedSequenceIndex < 0 ? "[No MIDI file loaded]" :\r
index f675806..5803b80 100644 (file)
@@ -92,7 +92,7 @@ import javax.swing.table.TableModel;
  */\r
 class MidiEditor extends JDialog implements DropTargetListener {\r
        public static final Insets ZERO_INSETS = new Insets(0,0,0,0);\r
-       private static final Icon deleteIcon = new ButtonIcon(ButtonIcon.X_ICON);\r
+       static final Icon deleteIcon = new ButtonIcon(ButtonIcon.X_ICON);\r
        /**\r
         * このMIDIエディタの仮想MIDIデバイス\r
         */\r
@@ -106,12 +106,6 @@ class MidiEditor extends JDialog implements DropTargetListener {
                { info = new MyInfo(); setMaxReceivers(0); }\r
        };\r
        /**\r
-        * このダイアログを開きます。すでに開かれていた場合は前面に移動します。\r
-        */\r
-       public void open() {\r
-               if( isVisible() ) toFront(); else setVisible(true);\r
-       }\r
-       /**\r
         * このダイアログを表示するアクション\r
         */\r
        public Action openAction = new AbstractAction(\r
@@ -125,57 +119,55 @@ class MidiEditor extends JDialog implements DropTargetListener {
                public void actionPerformed(ActionEvent e) { open(); }\r
        };\r
        /**\r
-        * ã\83\97ã\83¬ã\82¤ã\83ªã\82¹ã\83\88ã\81®ã\83\87ã\83¼ã\82¿ã\83¢ã\83\87ã\83«\r
+        * ã\81\93ã\81®ã\83\80ã\82¤ã\82¢ã\83­ã\82°ã\82\92é\96\8bã\81\8dã\81¾ã\81\99ã\80\82ã\81\99ã\81§ã\81«é\96\8bã\81\8bã\82\8cã\81¦ã\81\84ã\81\9få ´å\90\88ã\81¯å\89\8dé\9d¢ã\81«ç§»å\8b\95ã\81\97ã\81¾ã\81\99ã\80\82\r
         */\r
-       SequenceListTableModel sequenceListTableModel;\r
+       public void open() {\r
+               if( isVisible() ) toFront(); else setVisible(true);\r
+       }\r
        /**\r
-        * 新しいMIDIシーケンスを生成するダイアログ\r
+        * エラーメッセージダイアログを表示します。\r
+        * @param message エラーメッセージ\r
         */\r
-       NewSequenceDialog newSequenceDialog = new NewSequenceDialog(this);\r
+       private void showError(String message) {\r
+               JOptionPane.showMessageDialog(\r
+                       this, message,\r
+                       ChordHelperApplet.VersionInfo.NAME,\r
+                       JOptionPane.ERROR_MESSAGE\r
+               );\r
+       }\r
        /**\r
-        * 選択されたシーケンスへジャンプするアクション\r
+        * 警告メッセージダイアログを表示します。\r
+        * @param message 警告メッセージ\r
         */\r
-       public Action loadToSequencerAction = new AbstractAction("Load to sequencer") {\r
-               {\r
-                       String tooltip = "Load selected MIDI sequence to sequencer - 選択した曲をシーケンサへロード";\r
-                       putValue(Action.SHORT_DESCRIPTION, tooltip);\r
-               }\r
-               @Override\r
-               public void actionPerformed(ActionEvent e) {\r
-                       sequenceListTableModel.loadToSequencer(sequenceListSelectionModel.getMinSelectionIndex());\r
-               }\r
-       };\r
+       private void showWarning(String message) {\r
+               JOptionPane.showMessageDialog(\r
+                       this, message,\r
+                       ChordHelperApplet.VersionInfo.NAME,\r
+                       JOptionPane.WARNING_MESSAGE\r
+               );\r
+       }\r
        /**\r
-        * シーケンスを削除するアクション\r
+        * 確認ダイアログを表示します。\r
+        * @param message 確認メッセージ\r
+        * @return 確認OKのときtrue\r
         */\r
-       public Action deleteSequenceAction = new AbstractAction("Delete",deleteIcon) {\r
-               {\r
-                       String tooltip = "Delete selected MIDI sequence - 選択した曲をプレイリストから削除";\r
-                       putValue(Action.SHORT_DESCRIPTION, tooltip);\r
-               }\r
-               @Override\r
-               public void actionPerformed(ActionEvent e) {\r
-                       if( midiFileChooser != null ) {\r
-                               // ファイルに保存できる場合(Javaアプレットではなく、Javaアプリとして動作している場合)\r
-                               //\r
-                               SequenceTrackListTableModel seqModel =\r
-                                       sequenceListTableModel.getSequenceModel(sequenceListSelectionModel);\r
-                               if( seqModel.isModified() ) {\r
-                                       // ファイル未保存の変更がある場合\r
-                                       //\r
-                                       String message =\r
-                                               "Selected MIDI sequence not saved - delete it ?\n" +\r
-                                               "選択したMIDIシーケンスはまだ保存されていません。削除しますか?";\r
-                                       if( ! confirm(message) ) {\r
-                                               // 実は削除してほしくなかった場合\r
-                                               return;\r
-                                       }\r
-                               }\r
-                       }\r
-                       // 削除を実行\r
-                       sequenceListTableModel.removeSequence(sequenceListSelectionModel);\r
-               }\r
-       };\r
+       boolean confirm(String message) {\r
+               return JOptionPane.showConfirmDialog(\r
+                       this, message,\r
+                       ChordHelperApplet.VersionInfo.NAME,\r
+                       JOptionPane.YES_NO_OPTION,\r
+                       JOptionPane.WARNING_MESSAGE\r
+               ) == JOptionPane.YES_OPTION ;\r
+       }\r
+\r
+       /**\r
+        * プレイリストのデータモデル\r
+        */\r
+       SequenceListTableModel sequenceListTableModel;\r
+       /**\r
+        * 新しいMIDIシーケンスを生成するダイアログ\r
+        */\r
+       NewSequenceDialog newSequenceDialog = new NewSequenceDialog(this);\r
        /**\r
         * BASE64テキスト入力ダイアログ\r
         */\r
@@ -185,50 +177,14 @@ class MidiEditor extends JDialog implements DropTargetListener {
         */\r
        private Action base64EncodeAction;\r
        /**\r
-        * プレイリストのMIDIシーケンス選択状態\r
-        */\r
-       ListSelectionModel sequenceListSelectionModel = new DefaultListSelectionModel() {\r
-               {\r
-                       setSelectionMode(ListSelectionModel.SINGLE_SELECTION);\r
-                       addListSelectionListener(new ListSelectionListener() {\r
-                               @Override\r
-                               public void valueChanged(ListSelectionEvent e) {\r
-                                       if( e.getValueIsAdjusting() )\r
-                                               return;\r
-                                       updateButtonStatus();\r
-                                       updateEnabled();\r
-                               }\r
-                       });\r
-                       if( base64Dialog.isBase64Available() ) {\r
-                               base64EncodeAction = new AbstractAction("Base64 Encode") {\r
-                                       {\r
-                                               String tooltip = "Encode selected sequence to Base64 textdata - 選択した曲をBase64テキストにエンコード";\r
-                                               putValue(Action.SHORT_DESCRIPTION, tooltip);\r
-                                       }\r
-                                       @Override\r
-                                       public void actionPerformed(ActionEvent e) {\r
-                                               SequenceTrackListTableModel mstm = sequenceListTableModel.getSequenceModel(sequenceListSelectionModel);\r
-                                               base64Dialog.setMIDIData(mstm.getMIDIdata(), mstm.getFilename());\r
-                                               base64Dialog.setVisible(true);\r
-                                       }\r
-                               };\r
-                       }\r
-                       updateEnabled();\r
-               }\r
-               private void updateEnabled() {\r
-                       int selIndex = getMinSelectionIndex();\r
-                       boolean isSelected = (selIndex >= 0);\r
-                       if(base64EncodeAction != null)\r
-                               base64EncodeAction.setEnabled(isSelected);\r
-                       deleteSequenceAction.setEnabled(isSelected);\r
-                       loadToSequencerAction.setEnabled(isSelected);\r
-               }\r
-       };\r
-       /**\r
         * プレイリストビュー\r
         */\r
        JTable sequenceListTableView;\r
        /**\r
+        * シーケンスを削除するアクション\r
+        */\r
+       public Action deleteSequenceAction;\r
+       /**\r
         * ファイル選択ダイアログ(アプレットでは使用不可)\r
         */\r
        private MidiFileChooser midiFileChooser;\r
@@ -247,7 +203,7 @@ class MidiEditor extends JDialog implements DropTargetListener {
                        @Override\r
                        public void actionPerformed(ActionEvent e) {\r
                                SequenceTrackListTableModel sequenceTableModel =\r
-                                       sequenceListTableModel.getSequenceModel(sequenceListSelectionModel);\r
+                                       sequenceListTableModel.getSelectedSequenceModel();\r
                                String filename = sequenceTableModel.getFilename();\r
                                File midiFile;\r
                                if( filename != null && ! filename.isEmpty() ) {\r
@@ -284,7 +240,7 @@ class MidiEditor extends JDialog implements DropTargetListener {
                 * シーケンスの選択有無に応じて、保存ボタンのイネーブル状態を更新します。\r
                 */\r
                private void updateEnabled() {\r
-                       boolean en = (sequenceListSelectionModel.getMinSelectionIndex() >= 0);\r
+                       boolean en = (sequenceListTableModel.selectionModel.getMinSelectionIndex() >= 0);\r
                        saveMidiFileAction.setEnabled(en);\r
                }\r
                {\r
@@ -292,7 +248,7 @@ class MidiEditor extends JDialog implements DropTargetListener {
                        setFileFilter(new FileNameExtensionFilter("MIDI sequence (*.mid)", "mid"));\r
                        //\r
                        // 選択状態のリスニングを開始\r
-                       sequenceListSelectionModel.addListSelectionListener(this);\r
+                       sequenceListTableModel.selectionModel.addListSelectionListener(this);\r
                        updateEnabled();\r
                }\r
                @Override\r
@@ -310,12 +266,49 @@ class MidiEditor extends JDialog implements DropTargetListener {
                                putValue(Action.SHORT_DESCRIPTION, tooltip);\r
                        }\r
                        @Override\r
-                       public void actionPerformed(ActionEvent e) {\r
-                               if(showOpenDialog(MidiEditor.this) == JFileChooser.APPROVE_OPTION)\r
-                                       addSequence(getSelectedFile());\r
+                       public void actionPerformed(ActionEvent event) {\r
+                               if(showOpenDialog(MidiEditor.this) == JFileChooser.APPROVE_OPTION) {\r
+                                       try  {\r
+                                               sequenceListTableModel.addSequence(getSelectedFile());\r
+                                       } catch( IOException|InvalidMidiDataException e ) {\r
+                                               showWarning(e.getMessage());\r
+                                       } catch( AccessControlException e ) {\r
+                                               showError(e.getMessage());\r
+                                               e.printStackTrace();\r
+                                       }\r
+                               }\r
                        }\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
        /**\r
         * MIDIトラック選択状態\r
@@ -327,7 +320,8 @@ class MidiEditor extends JDialog implements DropTargetListener {
                        public void valueChanged(ListSelectionEvent e) {\r
                                if( e.getValueIsAdjusting() )\r
                                        return;\r
-                               SequenceTrackListTableModel sequenceModel = sequenceListTableModel.getSequenceModel(sequenceListSelectionModel);\r
+                               SequenceTrackListTableModel sequenceModel =\r
+                                       sequenceListTableModel.getSelectedSequenceModel();\r
                                if( sequenceModel == null || isSelectionEmpty() ) {\r
                                        trackEventListTableView.setModel(new TrackEventListTableModel());\r
                                }\r
@@ -359,9 +353,8 @@ class MidiEditor extends JDialog implements DropTargetListener {
                }\r
                @Override\r
                public void actionPerformed(ActionEvent e) {\r
-                       int index = sequenceListTableModel.getSequenceModel(sequenceListSelectionModel).createTrack();\r
+                       int index = sequenceListTableModel.getSelectedSequenceModel().createTrack();\r
                        trackSelectionModel.setSelectionInterval(index, index);\r
-                       sequenceListTableModel.fireSequenceChanged(sequenceListSelectionModel);\r
                }\r
        };\r
        /**\r
@@ -376,60 +369,13 @@ class MidiEditor extends JDialog implements DropTargetListener {
                public void actionPerformed(ActionEvent e) {\r
                        if( ! confirm("Do you want to delete selected track ?\n選択したトラックを削除しますか?"))\r
                                return;\r
-                       sequenceListTableModel.getSequenceModel(sequenceListSelectionModel).deleteTracks(trackSelectionModel);\r
-                       sequenceListTableModel.fireSequenceChanged(sequenceListSelectionModel);\r
+                       sequenceListTableModel.getSelectedSequenceModel().deleteTracks(trackSelectionModel);\r
                }\r
        };\r
        /**\r
         * MIDIトラックリストテーブルビュー(選択中のシーケンスの中身)\r
         */\r
-       private JTable trackListTableView = new JTable(\r
-               new SequenceTrackListTableModel(sequenceListTableModel),\r
-               null, trackSelectionModel\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
-       /**\r
-        * MIDIトラックリストのタイトルラベル\r
-        */\r
-       private JLabel trackListTitleLabel = new JLabel() {\r
-               private static final String TITLE = "Tracks";\r
-               {\r
-                       sequenceListSelectionModel.addListSelectionListener(\r
-                               new ListSelectionListener() {\r
-                                       @Override\r
-                                       public void valueChanged(ListSelectionEvent e) {\r
-                                               if( e.getValueIsAdjusting() )\r
-                                                       return;\r
-                                               int index = sequenceListSelectionModel.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
+       private JTable trackListTableView;\r
 \r
        /**\r
         * MIDIイベント選択状態\r
@@ -575,7 +521,7 @@ class MidiEditor extends JDialog implements DropTargetListener {
                        }};\r
 \r
                private void setSelectedEvent() {\r
-                       sequenceTableModel = sequenceListTableModel.getSequenceModel(sequenceListSelectionModel);\r
+                       sequenceTableModel = sequenceListTableModel.getSelectedSequenceModel();\r
                        eventDialog.midiMessageForm.durationForm.setPPQ(sequenceTableModel.getSequence().getResolution());\r
                        tickPositionModel.setSequenceIndex(sequenceTableModel.getSequenceTickIndex());\r
                        selectedIndex = -1;\r
@@ -634,7 +580,8 @@ class MidiEditor extends JDialog implements DropTargetListener {
                                                copiedEventsToPaste, tick, copiedEventsPPQ\r
                                        );\r
                                        scrollToEventAt(tick);\r
-                                       sequenceListTableModel.fireSequenceChanged(sequenceListSelectionModel);\r
+                                       // プレイリストの曲の長さ表示を更新\r
+                                       sequenceListTableModel.fireSelectedSequenceChanged();\r
                                        eventDialog.setVisible(false);\r
                                }\r
                        };\r
@@ -696,7 +643,7 @@ class MidiEditor extends JDialog implements DropTargetListener {
                                                scrollToEventAt( partnerTick > tick ? partnerTick : tick );\r
                                        }\r
                                }\r
-                               sequenceListTableModel.fireSequenceChanged(sequenceTableModel);\r
+                               sequenceListTableModel.fireSequenceModified(sequenceTableModel);\r
                                eventDialog.setVisible(false);\r
                                fireEditingStopped();\r
                        }\r
@@ -783,9 +730,10 @@ class MidiEditor extends JDialog implements DropTargetListener {
                                return;\r
                        TrackEventListTableModel trackTableModel = (TrackEventListTableModel)trackEventListTableView.getModel();\r
                        copiedEventsToPaste = trackTableModel.getMidiEvents(eventSelectionModel);\r
-                       copiedEventsPPQ = sequenceListTableModel.getSequenceModel(sequenceListSelectionModel).getSequence().getResolution();\r
+                       copiedEventsPPQ = sequenceListTableModel.getSelectedSequenceModel().getSequence().getResolution();\r
                        trackTableModel.removeMidiEvents(copiedEventsToPaste);\r
-                       sequenceListTableModel.fireSequenceChanged(sequenceListSelectionModel);\r
+                       // プレイリストの曲の長さ表示を更新\r
+                       sequenceListTableModel.fireSelectedSequenceChanged();\r
                }\r
        };\r
        /**\r
@@ -796,7 +744,7 @@ class MidiEditor extends JDialog implements DropTargetListener {
                public void actionPerformed(ActionEvent e) {\r
                        TrackEventListTableModel trackTableModel = (TrackEventListTableModel)trackEventListTableView.getModel();\r
                        copiedEventsToPaste = trackTableModel.getMidiEvents(eventSelectionModel);\r
-                       copiedEventsPPQ = sequenceListTableModel.getSequenceModel(sequenceListSelectionModel).getSequence().getResolution();\r
+                       copiedEventsPPQ = sequenceListTableModel.getSelectedSequenceModel().getSequence().getResolution();\r
                        updateButtonStatus();\r
                }\r
        };\r
@@ -809,9 +757,11 @@ class MidiEditor extends JDialog implements DropTargetListener {
                        if( ! confirm("Do you want to delete selected event ?\n選択したMIDIイベントを削除しますか?"))\r
                                return;\r
                        ((TrackEventListTableModel)trackEventListTableView.getModel()).removeMidiEvents(eventSelectionModel);\r
-                       sequenceListTableModel.fireSequenceChanged(sequenceListSelectionModel);\r
+                       // プレイリストの曲の長さ表示を更新\r
+                       sequenceListTableModel.fireSelectedSequenceChanged();\r
                }\r
        };\r
+\r
        /**\r
         * 新しい {@link MidiEditor} を構築します。\r
         * @param deviceModelList MIDIデバイスモデルリスト\r
@@ -830,8 +780,49 @@ class MidiEditor extends JDialog implements DropTargetListener {
                        // アクセスできないので、ファイル選択ダイアログは使用不可。\r
                        midiFileChooser = null;\r
                }\r
+               if( base64Dialog.isBase64Available() ) {\r
+                       base64EncodeAction = sequenceListTableModel.new SelectedSequenceAction(\r
+                               "Base64 Encode",\r
+                               "Encode selected sequence to Base64 textdata - 選択した曲をBase64テキストにエンコード"\r
+                       ) {\r
+                               @Override\r
+                               public void actionPerformed(ActionEvent e) {\r
+                                       SequenceTrackListTableModel mstm =\r
+                                               sequenceListTableModel.getSelectedSequenceModel();\r
+                                       base64Dialog.setMIDIData(mstm.getMIDIdata(), mstm.getFilename());\r
+                                       base64Dialog.setVisible(true);\r
+                               }\r
+                       };\r
+               }\r
+               deleteSequenceAction = sequenceListTableModel.new SelectedSequenceAction(\r
+                       "Delete", MidiEditor.deleteIcon,\r
+                       "Delete selected MIDI sequence - 選択した曲をプレイリストから削除"\r
+               ) {\r
+                       @Override\r
+                       public void actionPerformed(ActionEvent e) {\r
+                               if( midiFileChooser != null ) {\r
+                                       // ファイルに保存できる場合(Javaアプレットではなく、Javaアプリとして動作している場合)\r
+                                       //\r
+                                       SequenceTrackListTableModel seqModel =\r
+                                               sequenceListTableModel.getSelectedSequenceModel();\r
+                                       if( seqModel.isModified() ) {\r
+                                               // ファイル未保存の変更がある場合\r
+                                               //\r
+                                               String message =\r
+                                                       "Selected MIDI sequence not saved - delete it ?\n" +\r
+                                                       "選択したMIDIシーケンスはまだ保存されていません。削除しますか?";\r
+                                               if( ! confirm(message) ) {\r
+                                                       // 実は削除してほしくなかった場合\r
+                                                       return;\r
+                                               }\r
+                                       }\r
+                               }\r
+                               // 削除を実行\r
+                               sequenceListTableModel.removeSelectedSequence();\r
+                       }\r
+               };\r
                sequenceListTableView = new JTable(\r
-                       sequenceListTableModel, null, sequenceListSelectionModel\r
+                       sequenceListTableModel, null, sequenceListTableModel.selectionModel\r
                ) {\r
                        private JToggleButton playButton = new JToggleButton(\r
                                sequenceListTableModel.sequencerModel.startStopAction\r
@@ -839,6 +830,16 @@ class MidiEditor extends JDialog implements DropTargetListener {
                                setMargin(ZERO_INSETS);\r
                        }};\r
                        {\r
+                               sequenceListTableModel.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
                                sequenceListTableModel.addTableModelListener(new TableModelListener() {\r
                                        /**\r
                                         * 全シーケンスの合計時間長をヘッダータイトルに反映します。\r
@@ -918,7 +919,9 @@ class MidiEditor extends JDialog implements DropTargetListener {
                                        setMargin(ZERO_INSETS);\r
                                }});\r
                                add(Box.createRigidArea(new Dimension(5, 0)));\r
-                               add(new JButton(loadToSequencerAction){{ setMargin(ZERO_INSETS); }});\r
+                               add(new JButton(sequenceListTableModel.loadToSequencerAction){{\r
+                                       setMargin(ZERO_INSETS);\r
+                               }});\r
                                add(Box.createRigidArea(new Dimension(5, 0)));\r
                                add(new JButton(sequenceListTableModel.moveToBottomAction) {{\r
                                        setMargin(ZERO_INSETS);\r
@@ -948,9 +951,51 @@ class MidiEditor extends JDialog implements DropTargetListener {
                }};\r
                JPanel trackListPanel = new JPanel() {{\r
                        setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));\r
-                       add(trackListTitleLabel);\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(Box.createRigidArea(new Dimension(0, 5)));\r
-                       add(new JScrollPane(trackListTableView));\r
+                       add(new JScrollPane(trackListTableView = new JTable(\r
+                               new SequenceTrackListTableModel(sequenceListTableModel),\r
+                               null, trackSelectionModel\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(Box.createRigidArea(new Dimension(0, 5)));\r
                        add(new JPanel() {{\r
                                add(new JButton(addTrackAction) {{ setMargin(ZERO_INSETS); }});\r
@@ -992,64 +1037,12 @@ class MidiEditor extends JDialog implements DropTargetListener {
                updateButtonStatus();\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
-       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
-       // Short message dialogs\r
-       private void showError(String message) {\r
-               JOptionPane.showMessageDialog(\r
-                       this, message,\r
-                       ChordHelperApplet.VersionInfo.NAME,\r
-                       JOptionPane.ERROR_MESSAGE\r
-               );\r
-       }\r
-       private void showWarning(String message) {\r
-               JOptionPane.showMessageDialog(\r
-                       this, message,\r
-                       ChordHelperApplet.VersionInfo.NAME,\r
-                       JOptionPane.WARNING_MESSAGE\r
-               );\r
-       }\r
-       private boolean confirm(String message) {\r
-               return JOptionPane.showConfirmDialog(\r
-                       this, message,\r
-                       ChordHelperApplet.VersionInfo.NAME,\r
-                       JOptionPane.YES_NO_OPTION,\r
-                       JOptionPane.WARNING_MESSAGE\r
-               ) == JOptionPane.YES_OPTION ;\r
-       }\r
-\r
        /**\r
         * ボタン状態の更新\r
         */\r
        public void updateButtonStatus() {\r
                SequenceTrackListTableModel sequenceModel =\r
-                       sequenceListTableModel.getSequenceModel(sequenceListSelectionModel);\r
+                       sequenceListTableModel.getSelectedSequenceModel();\r
                boolean isSequenceSelected = (sequenceModel != null);\r
                if(isSequenceSelected) {\r
                        trackListTableView.setModel(sequenceModel);\r
@@ -1116,35 +1109,10 @@ class MidiEditor extends JDialog implements DropTargetListener {
                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
-        * MIDIファイルから読み込んだシーケンスを追加します。\r
-        * ファイルが null の場合、空のMIDIシーケンスを追加します。\r
-        * @param midiFile MIDIファイル\r
-        * @return 追加先インデックス(先頭が 0、失敗した場合は -1)\r
-        */\r
-       public int addSequence(File midiFile) {\r
-               if( midiFile == null ) {\r
-                       return sequenceListTableModel.addDefaultSequence();\r
-               }\r
-               int lastIndex;\r
-               try (FileInputStream in = new FileInputStream(midiFile)) {\r
-                       Sequence seq = MidiSystem.getSequence(in);\r
-                       String filename = midiFile.getName();\r
                        lastIndex = sequenceListTableModel.addSequence(seq, filename);\r
                } catch( IOException|InvalidMidiDataException e ) {\r
                        showWarning(e.getMessage());\r
                        return -1;\r
-               } catch( AccessControlException e ) {\r
-                       showError(e.getMessage());\r
-                       e.printStackTrace();\r
-                       return -1;\r
                }\r
                return lastIndex;\r
        }\r
@@ -1179,10 +1147,13 @@ class MidiEditor extends JDialog implements DropTargetListener {
         */\r
        public void loadAndPlay(List<File> fileList) {\r
                int firstIndex = -1;\r
-               for( File file : fileList ) {\r
-                       int lastIndex = addSequence(file);\r
-                       if( firstIndex == -1 )\r
-                               firstIndex = lastIndex;\r
+               try {\r
+                       firstIndex = sequenceListTableModel.addSequences(fileList);\r
+               } catch( IOException|InvalidMidiDataException e ) {\r
+                       showWarning(e.getMessage());\r
+               } catch( AccessControlException e ) {\r
+                       showError(e.getMessage());\r
+                       e.printStackTrace();\r
                }\r
                if(sequenceListTableModel.sequencerModel.getSequencer().isRunning()) {\r
                        open();\r
@@ -1199,7 +1170,7 @@ class MidiEditor extends JDialog implements DropTargetListener {
         */\r
        public boolean isRecordable() {\r
                SequenceTrackListTableModel sequenceTableModel =\r
-                       sequenceListTableModel.getSequenceModel(sequenceListSelectionModel);\r
+                       sequenceListTableModel.getSelectedSequenceModel();\r
                return sequenceTableModel == null ? false : sequenceTableModel.isRecordable();\r
        }\r
 }\r
@@ -1256,7 +1227,119 @@ class SequencerSpeedSlider extends JPanel {
 /**\r
  * プレイリスト(MIDIシーケンスリスト)のテーブルデータモデル\r
  */\r
-class SequenceListTableModel extends AbstractTableModel implements ChangeListener {\r
+class SequenceListTableModel extends AbstractTableModel {\r
+       /**\r
+        * 行の選択状態\r
+        */\r
+       ListSelectionModel selectionModel = new DefaultListSelectionModel() {{\r
+               setSelectionMode(ListSelectionModel.SINGLE_SELECTION);\r
+       }};\r
+       /**\r
+        * 行が選択されているときだけイネーブルになるアクション\r
+        */\r
+       public abstract class SelectedSequenceAction extends AbstractAction\r
+               implements ListSelectionListener\r
+       {\r
+               public SelectedSequenceAction(String name, Icon icon, String tooltip) {\r
+                       super(name,icon); init(tooltip);\r
+               }\r
+               public SelectedSequenceAction(String name, String tooltip) {\r
+                       super(name); init(tooltip);\r
+               }\r
+               @Override\r
+               public void valueChanged(ListSelectionEvent e) {\r
+                       if( e.getValueIsAdjusting() ) return;\r
+                       setEnebledBySelection();\r
+               }\r
+               protected void setEnebledBySelection() {\r
+                       int index = selectionModel.getMinSelectionIndex();\r
+                       setEnabled(index >= 0);\r
+               }\r
+               private void init(String tooltip) {\r
+                       putValue(Action.SHORT_DESCRIPTION, tooltip);\r
+                       selectionModel.addListSelectionListener(this);\r
+                       setEnebledBySelection();\r
+               }\r
+       }\r
+       /**\r
+        * 選択されたシーケンスへジャンプするアクション\r
+        */\r
+       public Action loadToSequencerAction = new SelectedSequenceAction(\r
+               "Load to sequencer",\r
+               "Load selected MIDI sequence to sequencer - 選択した曲をシーケンサへロード"\r
+       ) {\r
+               @Override\r
+               public void actionPerformed(ActionEvent e) {\r
+                       loadToSequencer(selectionModel.getMinSelectionIndex());\r
+               }\r
+       };\r
+\r
+       private class SecondPosition implements ChangeListener {\r
+               private int value = 0;\r
+               /**\r
+                * 再生中のシーケンサーの秒位置が変わったときに表示を更新します。\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
+                               value = newValue;\r
+                               fireTableCellUpdated(\r
+                                       indexOfSequenceOnSequencer(), Column.SEQ_POSITION.ordinal()\r
+                               );\r
+                       }\r
+               }\r
+               @Override\r
+               public String toString() {\r
+                       return String.format("%02d:%02d", value/60, value%60);\r
+               }\r
+       }\r
+       private SecondPosition secondPosition = new SecondPosition();\r
+       /**\r
+        * 新しいプレイリストのテーブルモデルを構築します。\r
+        * @param sequencerModel MIDIシーケンサーモデル\r
+        */\r
+       public SequenceListTableModel(MidiSequencerModel sequencerModel) {\r
+               this.sequencerModel = sequencerModel;\r
+               sequencerModel.addChangeListener(secondPosition);\r
+       }\r
+       /**\r
+        * MIDIシーケンサモデル\r
+        */\r
+       MidiSequencerModel sequencerModel;\r
+       /**\r
+        * 曲の先頭または前の曲へ戻るアクション\r
+        */\r
+       public Action moveToTopAction = new AbstractAction() {\r
+               {\r
+                       putValue(SHORT_DESCRIPTION,\r
+                               "Move to top or previous song - 曲の先頭または前の曲へ戻る"\r
+                       );\r
+                       putValue(LARGE_ICON_KEY, new ButtonIcon(ButtonIcon.TOP_ICON));\r
+               }\r
+               public void actionPerformed(ActionEvent event) {\r
+                       if( sequencerModel.getSequencer().getTickPosition() <= 40 )\r
+                               loadNext(-1);\r
+                       sequencerModel.setValue(0);\r
+               }\r
+       };\r
+       /**\r
+        * 次の曲へ進むアクション\r
+        */\r
+       public Action moveToBottomAction = new AbstractAction() {\r
+               {\r
+                       putValue(SHORT_DESCRIPTION, "Move to next song - 次の曲へ進む");\r
+                       putValue(LARGE_ICON_KEY, new ButtonIcon(ButtonIcon.BOTTOM_ICON));\r
+               }\r
+               public void actionPerformed(ActionEvent event) {\r
+                       if(loadNext(1)) sequencerModel.setValue(0);\r
+               }\r
+       };\r
+\r
        /**\r
         * 列の列挙型\r
         */\r
@@ -1303,68 +1386,11 @@ class SequenceListTableModel extends AbstractTableModel implements ChangeListene
                }\r
                public boolean isCellEditable() { return false; }\r
        }\r
-       /**\r
-        * MIDIシーケンサモデル\r
-        */\r
-       MidiSequencerModel sequencerModel;\r
-       /**\r
-        * 曲の先頭または前の曲へ戻るアクション\r
-        */\r
-       public Action moveToTopAction = new AbstractAction() {\r
-               {\r
-                       putValue(SHORT_DESCRIPTION,\r
-                               "Move to top or previous song - 曲の先頭または前の曲へ戻る"\r
-                       );\r
-                       putValue(LARGE_ICON_KEY, new ButtonIcon(ButtonIcon.TOP_ICON));\r
-               }\r
-               public void actionPerformed(ActionEvent event) {\r
-                       if( sequencerModel.getSequencer().getTickPosition() <= 40 )\r
-                               loadNext(-1);\r
-                       sequencerModel.setValue(0);\r
-               }\r
-       };\r
-       /**\r
-        * 次の曲へ進むアクション\r
-        */\r
-       public Action moveToBottomAction = new AbstractAction() {\r
-               {\r
-                       putValue(SHORT_DESCRIPTION, "Move to next song - 次の曲へ進む");\r
-                       putValue(LARGE_ICON_KEY, new ButtonIcon(ButtonIcon.BOTTOM_ICON));\r
-               }\r
-               public void actionPerformed(ActionEvent event) {\r
-                       if(loadNext(1)) sequencerModel.setValue(0);\r
-               }\r
-       };\r
-       /**\r
-        * 新しいプレイリストのテーブルモデルを構築します。\r
-        * @param sequencerModel MIDIシーケンサーモデル\r
-        */\r
-       public SequenceListTableModel(MidiSequencerModel sequencerModel) {\r
-               (this.sequencerModel = sequencerModel).addChangeListener(this);\r
-       }\r
-       /**\r
-        * シーケンサーの秒位置\r
-        */\r
-       private int secondPosition = 0;\r
-       /**\r
-        * 再生中のシーケンサーの秒位置が変わったときに表示を更新します。\r
-        */\r
-       @Override\r
-       public void stateChanged(ChangeEvent e) {\r
-               int sec = sequencerModel.getValue() / 1000;\r
-               if(secondPosition == sec)\r
-                       return;\r
-               // 秒が変わったときだけ更新(小数点以下は無視)\r
-               secondPosition = sec;\r
-               fireTableCellUpdated(getLoadedIndex(), Column.SEQ_POSITION.ordinal());\r
-       }\r
        List<SequenceTrackListTableModel> sequenceList = new Vector<>();\r
        @Override\r
        public int getRowCount() { return sequenceList.size(); }\r
        @Override\r
-       public int getColumnCount() {\r
-               return Column.values().length;\r
-       }\r
+       public int getColumnCount() { return Column.values().length; }\r
        @Override\r
        public String getColumnName(int column) {\r
                return Column.values()[column].title;\r
@@ -1379,12 +1405,13 @@ class SequenceListTableModel extends AbstractTableModel implements ChangeListene
        }\r
        @Override\r
        public Object getValueAt(int row, int column) {\r
-               switch(Column.values()[column]) {\r
-               case SEQ_NUMBER: return row;\r
-               case MODIFIED:\r
-                       return sequenceList.get(row).isModified();\r
+               Column c = Column.values()[column];\r
+               if( c == Column.SEQ_NUMBER ) return row;\r
+               SequenceTrackListTableModel sequenceModel = sequenceList.get(row);\r
+               switch(c) {\r
+               case MODIFIED: return sequenceModel.isModified();\r
                case DIVISION_TYPE: {\r
-                       float divType = sequenceList.get(row).getSequence().getDivisionType();\r
+                       float divType = sequenceModel.getSequence().getDivisionType();\r
                        if( divType == Sequence.PPQ ) return "PPQ";\r
                        else if( divType == Sequence.SMPTE_24 ) return "SMPTE_24";\r
                        else if( divType == Sequence.SMPTE_25 ) return "SMPTE_25";\r
@@ -1392,27 +1419,20 @@ class SequenceListTableModel extends AbstractTableModel implements ChangeListene
                        else if( divType == Sequence.SMPTE_30DROP ) return "SMPTE_30DROP";\r
                        else return "[Unknown]";\r
                }\r
-               case RESOLUTION:\r
-                       return sequenceList.get(row).getSequence().getResolution();\r
-               case TRACKS:\r
-                       return sequenceList.get(row).getSequence().getTracks().length;\r
-               case SEQ_POSITION: {\r
-                       if( getLoadedIndex() == row )\r
-                               return String.format("%02d:%02d", secondPosition/60, secondPosition%60);\r
-                       else\r
-                               return "";\r
-               }\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_LENGTH: {\r
-                       long usec = sequenceList.get(row).getSequence().getMicrosecondLength();\r
+                       long usec = sequenceModel.getSequence().getMicrosecondLength();\r
                        int sec = (int)( (usec < 0 ? usec += 0x100000000L : usec) / 1000L / 1000L );\r
                        return String.format( "%02d:%02d", sec/60, sec%60 );\r
                }\r
                case FILENAME: {\r
-                       String filename = sequenceList.get(row).getFilename();\r
+                       String filename = sequenceModel.getFilename();\r
                        return filename == null ? "" : filename;\r
                }\r
                case SEQ_NAME: {\r
-                       String name = sequenceList.get(row).toString();\r
+                       String name = sequenceModel.toString();\r
                        return name == null ? "" : name;\r
                }\r
                default: return "";\r
@@ -1431,6 +1451,7 @@ class SequenceListTableModel extends AbstractTableModel implements ChangeListene
                        // シーケンス名の設定または変更\r
                        if( sequenceList.get(row).setName((String)val) )\r
                                fireTableCellUpdated(row, Column.MODIFIED.ordinal());\r
+                       fireTableCellUpdated(row, column);\r
                        break;\r
                default:\r
                        break;\r
@@ -1464,11 +1485,11 @@ class SequenceListTableModel extends AbstractTableModel implements ChangeListene
         * @param selModel 選択状態\r
         * @param isModified 未保存の修正内容があるときtrue\r
         */\r
-       public void setModified(ListSelectionModel selModel, boolean isModified) {\r
-               int minIndex = selModel.getMinSelectionIndex();\r
-               int maxIndex = selModel.getMaxSelectionIndex();\r
+       public void setModified(boolean isModified) {\r
+               int minIndex = selectionModel.getMinSelectionIndex();\r
+               int maxIndex = selectionModel.getMaxSelectionIndex();\r
                for( int i = minIndex; i <= maxIndex; i++ ) {\r
-                       if( selModel.isSelectedIndex(i) ) {\r
+                       if( selectionModel.isSelectedIndex(i) ) {\r
                                sequenceList.get(i).setModified(isModified);\r
                                fireTableCellUpdated(i, Column.MODIFIED.ordinal());\r
                        }\r
@@ -1479,7 +1500,7 @@ class SequenceListTableModel extends AbstractTableModel implements ChangeListene
         * @param selectionModel 選択状態\r
         * @return 選択されたMIDIシーケンスのテーブルモデル\r
         */\r
-       public SequenceTrackListTableModel getSequenceModel(ListSelectionModel selectionModel) {\r
+       public SequenceTrackListTableModel getSelectedSequenceModel() {\r
                if( selectionModel.isSelectionEmpty() )\r
                        return null;\r
                int selectedIndex = selectionModel.getMinSelectionIndex();\r
@@ -1488,10 +1509,10 @@ class SequenceListTableModel extends AbstractTableModel implements ChangeListene
                return sequenceList.get(selectedIndex);\r
        }\r
        /**\r
-        * 指定されたシーケンスが変更されたことを通知します。\r
+        * 指定されたシーケンスが修正されたことを通知します。\r
         * @param sequenceTableModel MIDIシーケンスモデル\r
         */\r
-       public void fireSequenceChanged(SequenceTrackListTableModel sequenceTableModel) {\r
+       public void fireSequenceModified(SequenceTrackListTableModel sequenceTableModel) {\r
                int index = sequenceList.indexOf(sequenceTableModel);\r
                if( index < 0 )\r
                        return;\r
@@ -1502,7 +1523,7 @@ class SequenceListTableModel extends AbstractTableModel implements ChangeListene
         * 指定された選択範囲のシーケンスが変更されたことを通知します。\r
         * @param selectionModel 選択状態\r
         */\r
-       public void fireSequenceChanged(ListSelectionModel selectionModel) {\r
+       public void fireSelectedSequenceChanged() {\r
                if( selectionModel.isSelectionEmpty() )\r
                        return;\r
                int minIndex = selectionModel.getMinSelectionIndex();\r
@@ -1513,15 +1534,7 @@ class SequenceListTableModel extends AbstractTableModel implements ChangeListene
                fireTableRowsUpdated(minIndex, maxIndex);\r
        }\r
        /**\r
-        * デフォルトの内容でシーケンスを作成して追加します。\r
-        * @return 追加されたシーケンスのインデックス(先頭が 0)\r
-        */\r
-       public int addDefaultSequence() {\r
-               Sequence seq = (new Music.ChordProgression()).toMidiSequence();\r
-               return seq == null ? -1 : addSequence(seq,null);\r
-       }\r
-       /**\r
-        * 指定のシーケンスを追加します。\r
+        * MIDIシーケンスを追加します。\r
         * @param seq MIDIシーケンス\r
         * @param filename ファイル名\r
         * @return 追加されたシーケンスのインデックス(先頭が 0)\r
@@ -1533,13 +1546,57 @@ class SequenceListTableModel extends AbstractTableModel implements ChangeListene
                return lastIndex;\r
        }\r
        /**\r
+        * デフォルトの内容でMIDIシーケンスを作成して追加します。\r
+        * @return 追加されたMIDIシーケンスのインデックス(先頭が 0)\r
+        */\r
+       public int addDefaultSequence() {\r
+               Sequence seq = (new Music.ChordProgression()).toMidiSequence();\r
+               return seq == null ? -1 : addSequence(seq,null);\r
+       }\r
+       /**\r
+        * MIDIファイルを追加します。\r
+        * ファイルが null の場合、空のMIDIシーケンスを追加します。\r
+        * @param midiFile MIDIファイル\r
+        * @return 追加先インデックス(先頭が 0)\r
+        * @throws InvalidMidiDataException ファイル内のMIDIデータが正しくない場合\r
+        * @throws IOException ファイル入出力に失敗した場合\r
+        */\r
+       public int addSequence(File midiFile) throws InvalidMidiDataException, IOException {\r
+               if( midiFile == null ) return addDefaultSequence();\r
+               int lastIndex;\r
+               try (FileInputStream in = new FileInputStream(midiFile)) {\r
+                       Sequence seq = MidiSystem.getSequence(in);\r
+                       String filename = midiFile.getName();\r
+                       lastIndex = addSequence(seq, filename);\r
+               } catch( InvalidMidiDataException|IOException e ) {\r
+                       throw e;\r
+               }\r
+               return lastIndex;\r
+       }\r
+       /**\r
+        * 複数のMIDIファイルを追加します。\r
+        * @param fileList 追加するMIDIファイルのリスト\r
+        * @return 追加先の最初のインデックス(先頭が 0、追加されなかった場合は -1)\r
+        * @throws InvalidMidiDataException ファイル内のMIDIデータが正しくない場合\r
+        * @throws IOException ファイル入出力に失敗した場合\r
+        */\r
+       public int addSequences(List<File> fileList) throws InvalidMidiDataException, IOException {\r
+               int firstIndex = -1;\r
+               for( File file : fileList ) {\r
+                       int lastIndex = addSequence(file);\r
+                       if( firstIndex == -1 )\r
+                               firstIndex = lastIndex;\r
+               }\r
+               return firstIndex;\r
+       }\r
+       /**\r
         * 選択したシーケンスを除去します。\r
         * @param listSelectionModel 選択状態\r
         */\r
-       public void removeSequence(ListSelectionModel listSelectionModel) {\r
-               if( listSelectionModel.isSelectionEmpty() )\r
+       public void removeSelectedSequence() {\r
+               if( selectionModel.isSelectionEmpty() )\r
                        return;\r
-               int selectedIndex = listSelectionModel.getMinSelectionIndex();\r
+               int selectedIndex = selectionModel.getMinSelectionIndex();\r
                if( sequenceList.remove(selectedIndex).isOnSequencer() ) {\r
                        // 削除したシーケンスが\r
                        // シーケンサーにロード済みだった場合、アンロードする。\r
@@ -1549,30 +1606,33 @@ class SequenceListTableModel extends AbstractTableModel implements ChangeListene
        }\r
        /**\r
         * 指定したインデックス位置のシーケンスをシーケンサーにロードします。\r
-        * @param index シーケンスのインデックス位置\r
+        * @param index シーケンスのインデックス位置(-1 を指定するとアンロードされます)\r
         */\r
        public void loadToSequencer(int index) {\r
-               int oldIndex = getLoadedIndex();\r
-               if(index == oldIndex)\r
+               SequenceTrackListTableModel oldSeq = sequencerModel.getSequenceTableModel();\r
+               SequenceTrackListTableModel newSeq = (index < 0 ? null : sequenceList.get(index));\r
+               if(oldSeq == newSeq)\r
                        return;\r
-               sequencerModel.setSequenceTrackListTableModel(sequenceList.get(index));\r
+               sequencerModel.setSequenceTrackListTableModel(newSeq);\r
                int columnIndices[] = {\r
                        Column.SEQ_PLAY.ordinal(),\r
                        Column.SEQ_POSITION.ordinal(),\r
                };\r
-               for( int columnIndex : columnIndices ) {\r
-                       fireTableCellUpdated(oldIndex, columnIndex);\r
-               }\r
-               for( int columnIndex : columnIndices ) {\r
-                       fireTableCellUpdated(index, columnIndex);\r
+               if( oldSeq != null ) {\r
+                       int oldIndex = sequenceList.indexOf(oldSeq);\r
+                       for( int columnIndex : columnIndices )\r
+                               fireTableCellUpdated(oldIndex, columnIndex);\r
                }\r
+               if( newSeq != null )\r
+                       for( int columnIndex : columnIndices )\r
+                               fireTableCellUpdated(index, columnIndex);\r
        }\r
        /**\r
         * 現在シーケンサにロードされているシーケンスのインデックスを返します。\r
         * ロードされていない場合は -1 を返します。\r
         * @return 現在シーケンサにロードされているシーケンスのインデックス\r
         */\r
-       public int getLoadedIndex() {\r
+       public int indexOfSequenceOnSequencer() {\r
                return sequenceList.indexOf(sequencerModel.getSequenceTableModel());\r
        }\r
        /**\r
@@ -1581,7 +1641,7 @@ class SequenceListTableModel extends AbstractTableModel implements ChangeListene
         * @return 成功したらtrue\r
         */\r
        public boolean loadNext(int offset) {\r
-               int loadedIndex = getLoadedIndex();\r
+               int loadedIndex = indexOfSequenceOnSequencer();\r
                int index = (loadedIndex < 0 ? 0 : loadedIndex + offset);\r
                if( index < 0 || index >= sequenceList.size() )\r
                        return false;\r
@@ -1875,7 +1935,7 @@ class SequenceTrackListTableModel extends AbstractTableModel {
                int row = indexOf(track);\r
                if( row < 0 ) return;\r
                fireTableRowsUpdated(row, row);\r
-               sequenceListTableModel.fireSequenceChanged(this);\r
+               sequenceListTableModel.fireSequenceModified(this);\r
        }\r
        /**\r
         * 指定のインデックスのトラックモデルを返します。\r
@@ -1912,6 +1972,8 @@ class SequenceTrackListTableModel extends AbstractTableModel {
                trackModelList.add(new TrackEventListTableModel(sequence.createTrack(), this));\r
                int lastRow = sequence.getTracks().length - 1;\r
                fireTableRowsInserted(lastRow, lastRow);\r
+               // 親のプレイリストのトラック数表示を更新\r
+               sequenceListTableModel.fireSelectedSequenceChanged();\r
                return lastRow;\r
        }\r
        /**\r
@@ -1931,6 +1993,8 @@ class SequenceTrackListTableModel extends AbstractTableModel {
                        trackModelList.remove(i);\r
                }\r
                fireTableRowsDeleted(minIndex, maxIndex);\r
+               // 親のプレイリストのトラック数表示を更新\r
+               sequenceListTableModel.fireSelectedSequenceChanged();\r
        }\r
        /**\r
         * MIDIシーケンサを返します。\r
@@ -2135,7 +2199,7 @@ class TrackEventListTableModel extends AbstractTableModel {
                fireTableDataChanged();\r
                if( MIDISpec.isEOT(msg) ) {\r
                        // EOTの場所が変わると曲の長さが変わるので、親モデルへ通知する。\r
-                       parent.sequenceListTableModel.fireSequenceChanged(parent);\r
+                       parent.sequenceListTableModel.fireSequenceModified(parent);\r
                }\r
        }\r
        /**\r
@@ -2169,7 +2233,7 @@ class TrackEventListTableModel extends AbstractTableModel {
                if(name.equals(toString()) || ! MIDISpec.setNameOf(track, name))\r
                        return false;\r
                parent.setModified(true);\r
-               parent.sequenceListTableModel.fireSequenceChanged(parent);\r
+               parent.sequenceListTableModel.fireSequenceModified(parent);\r
                fireTableDataChanged();\r
                return true;\r
        }\r