OSDN Git Service

MIDIデバイス接続、バージョン情報を開く処理をボタン用アクションへ移行
[midichordhelper/MIDIChordHelper.git] / src / camidion / chordhelper / midieditor / MidiSequenceEditor.java
1 package camidion.chordhelper.midieditor;
2
3 import java.awt.Component;
4 import java.awt.Container;
5 import java.awt.Dimension;
6 import java.awt.FlowLayout;
7 import java.awt.Insets;
8 import java.awt.datatransfer.DataFlavor;
9 import java.awt.datatransfer.Transferable;
10 import java.awt.dnd.DnDConstants;
11 import java.awt.dnd.DropTarget;
12 import java.awt.dnd.DropTargetAdapter;
13 import java.awt.dnd.DropTargetDragEvent;
14 import java.awt.dnd.DropTargetDropEvent;
15 import java.awt.dnd.DropTargetListener;
16 import java.awt.event.ActionEvent;
17 import java.awt.event.ComponentAdapter;
18 import java.awt.event.ComponentEvent;
19 import java.awt.event.ComponentListener;
20 import java.awt.event.ItemEvent;
21 import java.awt.event.ItemListener;
22 import java.awt.event.MouseEvent;
23 import java.io.File;
24 import java.io.FileOutputStream;
25 import java.io.IOException;
26 import java.nio.charset.Charset;
27 import java.security.AccessControlException;
28 import java.util.Arrays;
29 import java.util.EventObject;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.Set;
33
34 import javax.sound.midi.InvalidMidiDataException;
35 import javax.sound.midi.MidiChannel;
36 import javax.sound.midi.MidiEvent;
37 import javax.sound.midi.MidiMessage;
38 import javax.sound.midi.Sequencer;
39 import javax.sound.midi.ShortMessage;
40 import javax.swing.AbstractAction;
41 import javax.swing.AbstractCellEditor;
42 import javax.swing.Action;
43 import javax.swing.Box;
44 import javax.swing.BoxLayout;
45 import javax.swing.DefaultCellEditor;
46 import javax.swing.Icon;
47 import javax.swing.JButton;
48 import javax.swing.JCheckBox;
49 import javax.swing.JComboBox;
50 import javax.swing.JDialog;
51 import javax.swing.JFileChooser;
52 import javax.swing.JLabel;
53 import javax.swing.JOptionPane;
54 import javax.swing.JPanel;
55 import javax.swing.JScrollPane;
56 import javax.swing.JSplitPane;
57 import javax.swing.JTable;
58 import javax.swing.JToggleButton;
59 import javax.swing.ListSelectionModel;
60 import javax.swing.event.ListSelectionEvent;
61 import javax.swing.event.ListSelectionListener;
62 import javax.swing.event.TableModelEvent;
63 import javax.swing.filechooser.FileNameExtensionFilter;
64 import javax.swing.table.JTableHeader;
65 import javax.swing.table.TableCellEditor;
66 import javax.swing.table.TableCellRenderer;
67 import javax.swing.table.TableColumn;
68 import javax.swing.table.TableColumnModel;
69 import javax.swing.table.TableModel;
70
71 import camidion.chordhelper.ButtonIcon;
72 import camidion.chordhelper.ChordHelperApplet;
73 import camidion.chordhelper.mididevice.AbstractVirtualMidiDevice;
74 import camidion.chordhelper.mididevice.MidiSequencerModel;
75 import camidion.chordhelper.mididevice.VirtualMidiDevice;
76 import camidion.chordhelper.music.MIDISpec;
77
78 /**
79  * MIDIエディタ(MIDI Editor/Playlist for MIDI Chord Helper)
80  *
81  * @author
82  *      Copyright (C) 2006-2016 Akiyoshi Kamide
83  *      http://www.yk.rim.or.jp/~kamide/music/chordhelper/
84  */
85 public class MidiSequenceEditor extends JDialog {
86         /**
87          * このダイアログを表示するアクション
88          */
89         public Action openAction = new AbstractAction("Edit/Playlist/Speed", new ButtonIcon(ButtonIcon.EDIT_ICON)) {
90                 {
91                         String tooltip = "MIDIシーケンスの編集/プレイリスト/再生速度調整";
92                         putValue(Action.SHORT_DESCRIPTION, tooltip);
93                 }
94                 @Override
95                 public void actionPerformed(ActionEvent e) {
96                         if( isVisible() ) toFront(); else setVisible(true);
97                 }
98         };
99
100         /**
101          * このMIDIエディタにおける操作音の出力先となる仮想MIDIデバイスを返します。
102          */
103         public VirtualMidiDevice getVirtualMidiDevice() { return virtualMidiDevice; }
104         private static VirtualMidiDevice virtualMidiDevice = new AbstractVirtualMidiDevice() {
105                 {
106                         info = new MyInfo();
107                         setMaxReceivers(0); // 送信専用とする(MIDI IN はサポートしない)
108                 }
109                 /**
110                  * MIDIデバイス情報
111                  */
112                 protected MyInfo info;
113                 @Override
114                 public Info getDeviceInfo() { return info; }
115                 class MyInfo extends Info {
116                         protected MyInfo() { super("MIDI Editor","Unknown vendor","MIDI sequence editor",""); }
117                 }
118         };
119
120         /**
121          * エラーメッセージダイアログを表示します。
122          * @param message エラーメッセージ
123          */
124         public void showError(String message) { showMessage(message, JOptionPane.ERROR_MESSAGE); }
125         /**
126          * 警告メッセージダイアログを表示します。
127          * @param message 警告メッセージ
128          */
129         public void showWarning(String message) { showMessage(message, JOptionPane.WARNING_MESSAGE); }
130         private void showMessage(String message, int messageType) {
131                 JOptionPane.showMessageDialog(this, message, ChordHelperApplet.VersionInfo.NAME, messageType);
132         }
133         /**
134          * 確認ダイアログを表示します。
135          * @param message 確認メッセージ
136          * @return 確認OKのときtrue
137          */
138         boolean confirm(String message) {
139                 return JOptionPane.showConfirmDialog(this, message, ChordHelperApplet.VersionInfo.NAME,
140                                 JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE) == JOptionPane.YES_OPTION ;
141         }
142
143         /**
144          * ドロップされた複数のMIDIファイルを読み込むリスナー
145          */
146         public final DropTargetListener dropTargetListener = new DropTargetAdapter() {
147                 @Override
148                 public void dragEnter(DropTargetDragEvent event) {
149                         if( event.isDataFlavorSupported(DataFlavor.javaFileListFlavor) ) {
150                                 event.acceptDrag(DnDConstants.ACTION_COPY_OR_MOVE);
151                         }
152                 }
153                 @Override
154                 @SuppressWarnings("unchecked")
155                 public void drop(DropTargetDropEvent event) {
156                         event.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
157                         try {
158                                 int action = event.getDropAction();
159                                 if ( (action & DnDConstants.ACTION_COPY_OR_MOVE) != 0 ) {
160                                         Transferable t = event.getTransferable();
161                                         if( t.isDataFlavorSupported(DataFlavor.javaFileListFlavor) ) {
162                                                 loadAndPlay((List<File>)t.getTransferData(DataFlavor.javaFileListFlavor));
163                                                 event.dropComplete(true);
164                                                 return;
165                                         }
166                                 }
167                         }
168                         catch (Exception ex) {
169                                 ex.printStackTrace();
170                         }
171                         event.dropComplete(false);
172                 }
173         };
174
175         /**
176          * 複数のMIDIファイルを読み込み、再生されていなかったら再生します。
177          * すでに再生されていた場合、このエディタダイアログを表示します。
178          *
179          * @param fileList 読み込むMIDIファイルのリスト
180          * @see #loadAndPlay(File)
181          */
182         public void loadAndPlay(List<File> fileList) {
183                 int indexOfAddedTop = -1;
184                 PlaylistTableModel playlist = sequenceListTable.getModel();
185                 try {
186                         indexOfAddedTop = playlist.addSequences(fileList);
187                 } catch(IOException|InvalidMidiDataException e) {
188                         showWarning(e.getMessage());
189                 } catch(AccessControlException e) {
190                         showError(e.getMessage());
191                         e.printStackTrace();
192                 }
193                 MidiSequencerModel sequencerModel = playlist.sequencerModel;
194                 if( sequencerModel.getSequencer().isRunning() ) {
195                         String command = (String)openAction.getValue(Action.NAME);
196                         openAction.actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, command));
197                         return;
198                 }
199                 if( indexOfAddedTop >= 0 ) {
200                         playlist.loadToSequencer(indexOfAddedTop);
201                         sequencerModel.start();
202                 }
203         }
204         /**
205          * 1件のMIDIファイルを読み込み、再生されていなかったら再生します。
206          * すでに再生されていた場合、このエディタダイアログを表示します。
207          *
208          * @param file 読み込むMIDIファイル
209          * @see #loadAndPlay(List) loadAndPlay(List&lt;File&gt;)
210          */
211         public void loadAndPlay(File file) { loadAndPlay(Arrays.asList(file)); }
212
213         private static final Insets ZERO_INSETS = new Insets(0,0,0,0);
214         private static final Icon deleteIcon = new ButtonIcon(ButtonIcon.X_ICON);
215         /**
216          * 新しいMIDIシーケンスを生成するダイアログ
217          */
218         public NewSequenceDialog newSequenceDialog = new NewSequenceDialog(this);
219         /**
220          * BASE64テキスト入力ダイアログ
221          */
222         public Base64Dialog base64Dialog = new Base64Dialog(this);
223         /**
224          * プレイリストビュー(シーケンスリスト)
225          */
226         public SequenceListTable sequenceListTable;
227         /**
228          * MIDIトラックリストテーブルビュー(選択中のシーケンスの中身)
229          */
230         private TrackListTable trackListTable;
231         /**
232          * MIDIイベントリストテーブルビュー(選択中のトラックの中身)
233          */
234         private EventListTable eventListTable;
235         /**
236          * MIDIイベント入力ダイアログ(イベント入力とイベント送出で共用)
237          */
238         public MidiEventDialog eventDialog = new MidiEventDialog();
239         /**
240          * プレイリストビュー(シーケンスリスト)
241          */
242         public class SequenceListTable extends JTable {
243                 /**
244                  * ファイル選択ダイアログ(アプレットの場合は使用不可なのでnull)
245                  */
246                 private MidiFileChooser midiFileChooser;
247                 /**
248                  * BASE64エンコードアクション(ライブラリが見えている場合のみ有効)
249                  */
250                 private Action base64EncodeAction;
251                 /**
252                  * プレイリストビューを構築します。
253                  * @param model プレイリストデータモデル
254                  */
255                 public SequenceListTable(PlaylistTableModel model) {
256                         super(model, null, model.sequenceListSelectionModel);
257                         try {
258                                 midiFileChooser = new MidiFileChooser();
259                         }
260                         catch( ExceptionInInitializerError|NoClassDefFoundError|AccessControlException e ) {
261                                 // アプレットの場合、Webクライアントマシンのローカルファイルには
262                                 // アクセスできないので、ファイル選択ダイアログは使用不可。
263                                 midiFileChooser = null;
264                         }
265                         // 再生ボタンを埋め込む
266                         new PlayButtonCellEditor();
267                         new PositionCellEditor();
268                         //
269                         // 文字コード選択をプルダウンにする
270                         int column = PlaylistTableModel.Column.CHARSET.ordinal();
271                         TableCellEditor ce = new DefaultCellEditor(new JComboBox<Charset>() {{
272                                 Set<Map.Entry<String,Charset>> entrySet = Charset.availableCharsets().entrySet();
273                                 for( Map.Entry<String,Charset> entry : entrySet ) addItem(entry.getValue());
274                         }});
275                         getColumnModel().getColumn(column).setCellEditor(ce);
276                         setAutoCreateColumnsFromModel(false);
277                         //
278                         // Base64エンコードアクションの生成
279                         if( base64Dialog.isBase64Available() ) {
280                                 base64EncodeAction = new AbstractAction("Base64") {
281                                         {
282                                                 String tooltip = "Base64 text conversion - Base64テキスト変換";
283                                                 putValue(Action.SHORT_DESCRIPTION, tooltip);
284                                         }
285                                         @Override
286                                         public void actionPerformed(ActionEvent e) {
287                                                 SequenceTrackListTableModel mstm = getModel().getSelectedSequenceModel();
288                                                 byte[] data = null;
289                                                 String filename = null;
290                                                 if( mstm != null ) {
291                                                         data = mstm.getMIDIdata();
292                                                         filename = mstm.getFilename();
293                                                 }
294                                                 base64Dialog.setMIDIData(data, filename);
295                                                 base64Dialog.setVisible(true);
296                                         }
297                                 };
298                         }
299                         TableColumnModel colModel = getColumnModel();
300                         for( PlaylistTableModel.Column c : PlaylistTableModel.Column.values() ) {
301                                 TableColumn tc = colModel.getColumn(c.ordinal());
302                                 tc.setPreferredWidth(c.preferredWidth);
303                                 if( c == PlaylistTableModel.Column.LENGTH ) lengthColumn = tc;
304                         }
305                 }
306                 private TableColumn lengthColumn;
307                 @Override
308                 public void tableChanged(TableModelEvent event) {
309                         super.tableChanged(event);
310                         //
311                         // タイトルに合計シーケンス長を表示
312                         if( lengthColumn != null ) {
313                                 int sec = getModel().getTotalSeconds();
314                                 String title = PlaylistTableModel.Column.LENGTH.title;
315                                 title = String.format(title+" [%02d:%02d]", sec/60, sec%60);
316                                 lengthColumn.setHeaderValue(title);
317                         }
318                         //
319                         // シーケンス削除時など、合計シーケンス長が変わっても
320                         // 列モデルからではヘッダタイトルが再描画されないことがある。
321                         // そこで、ヘッダビューから repaint() で突っついて再描画させる。
322                         JTableHeader th = getTableHeader();
323                         if( th != null ) th.repaint();
324                 }
325                 /**
326                  * 時間位置表示セルエディタ(ダブルクリック専用)
327                  */
328                 private class PositionCellEditor extends AbstractCellEditor
329                         implements TableCellEditor
330                 {
331                         public PositionCellEditor() {
332                                 int column = PlaylistTableModel.Column.POSITION.ordinal();
333                                 TableColumn tc = getColumnModel().getColumn(column);
334                                 tc.setCellEditor(this);
335                         }
336                         /**
337                          * セルをダブルクリックしたときだけ編集モードに入るようにします。
338                          * @param e イベント(マウスイベント)
339                          * @return 編集可能になったらtrue
340                          */
341                         @Override
342                         public boolean isCellEditable(EventObject e) {
343                                 // マウスイベント以外のイベントでは編集不可
344                                 if( ! (e instanceof MouseEvent) ) return false;
345                                 return ((MouseEvent)e).getClickCount() == 2;
346                         }
347                         @Override
348                         public Object getCellEditorValue() { return null; }
349                         /**
350                          * 編集モード時のコンポーネントを返すタイミングで
351                          * そのシーケンスをシーケンサーにロードしたあと、
352                          * すぐに編集モードを解除します。
353                          * @return 常にnull
354                          */
355                         @Override
356                         public Component getTableCellEditorComponent(
357                                 JTable table, Object value, boolean isSelected,
358                                 int row, int column
359                         ) {
360                                 getModel().loadToSequencer(row);
361                                 fireEditingStopped();
362                                 return null;
363                         }
364                 }
365                 /**
366                  * プレイボタンを埋め込んだセルエディタ
367                  */
368                 private class PlayButtonCellEditor extends AbstractCellEditor
369                         implements TableCellEditor, TableCellRenderer
370                 {
371                         private JToggleButton playButton = new JToggleButton(
372                                 getModel().sequencerModel.startStopAction
373                         ) {
374                                 { setMargin(ZERO_INSETS); }
375                         };
376                         public PlayButtonCellEditor() {
377                                 int column = PlaylistTableModel.Column.PLAY.ordinal();
378                                 TableColumn tc = getColumnModel().getColumn(column);
379                                 tc.setCellRenderer(this);
380                                 tc.setCellEditor(this);
381                         }
382                         /**
383                          * {@inheritDoc}
384                          *
385                          * <p>この実装では、クリックしたセルのシーケンスが
386                          * シーケンサーにロードされている場合に
387                          * trueを返してプレイボタンを押せるようにします。
388                          * そうでない場合はプレイボタンのないセルなので、
389                          * ダブルクリックされたときだけtrueを返します。
390                          * </p>
391                          */
392                         @Override
393                         public boolean isCellEditable(EventObject e) {
394                                 // マウスイベント以外はデフォルトメソッドにお任せ
395                                 if( ! (e instanceof MouseEvent) ) return super.isCellEditable(e);
396                                 fireEditingStopped();
397                                 MouseEvent me = (MouseEvent)e;
398                                 //
399                                 // クリックされたセルの行を特定
400                                 int row = rowAtPoint(me.getPoint());
401                                 if( row < 0 ) return false;
402                                 PlaylistTableModel model = getModel();
403                                 if( row >= model.getRowCount() ) return false;
404                                 //
405                                 // セル内にプレイボタンがあれば、シングルクリックを受け付ける。
406                                 // プレイボタンのないセルは、ダブルクリックのみ受け付ける。
407                                 return model.sequenceList.get(row).isOnSequencer() || me.getClickCount() == 2;
408                         }
409                         @Override
410                         public Object getCellEditorValue() { return null; }
411                         /**
412                          * {@inheritDoc}
413                          *
414                          * <p>この実装では、行の表すシーケンスが
415                          * シーケンサーにロードされている場合にプレイボタンを返します。
416                          * そうでない場合は、
417                          * そのシーケンスをシーケンサーにロードしてnullを返します。
418                          * </p>
419                          */
420                         @Override
421                         public Component getTableCellEditorComponent(
422                                 JTable table, Object value, boolean isSelected, int row, int column
423                         ) {
424                                 fireEditingStopped();
425                                 PlaylistTableModel model = getModel();
426                                 if( model.sequenceList.get(row).isOnSequencer() ) return playButton;
427                                 model.loadToSequencer(row);
428                                 return null;
429                         }
430                         @Override
431                         public Component getTableCellRendererComponent(
432                                 JTable table, Object value, boolean isSelected,
433                                 boolean hasFocus, int row, int column
434                         ) {
435                                 PlaylistTableModel model = getModel();
436                                 if(model.sequenceList.get(row).isOnSequencer()) return playButton;
437                                 Class<?> cc = model.getColumnClass(column);
438                                 TableCellRenderer defaultRenderer = table.getDefaultRenderer(cc);
439                                 return defaultRenderer.getTableCellRendererComponent(
440                                         table, value, isSelected, hasFocus, row, column
441                                 );
442                         }
443                 }
444                 /**
445                  * このプレイリスト(シーケンスリスト)が表示するデータを提供する
446                  * プレイリストモデルを返します。
447                  * @return プレイリストモデル
448                  */
449                 @Override
450                 public PlaylistTableModel getModel() { return (PlaylistTableModel)super.getModel(); }
451                 /**
452                  * シーケンスを削除するアクション
453                  */
454                 Action deleteSequenceAction = getModel().new SelectedSequenceAction(
455                         "Delete", MidiSequenceEditor.deleteIcon,
456                         "Delete selected MIDI sequence - 選択した曲をプレイリストから削除"
457                 ) {
458                         @Override
459                         public void actionPerformed(ActionEvent e) {
460                                 PlaylistTableModel model = getModel();
461                                 if( midiFileChooser != null ) {
462                                         if( model.getSelectedSequenceModel().isModified() ) {
463                                                 String message =
464                                                         "Selected MIDI sequence not saved - delete it ?\n" +
465                                                         "選択したMIDIシーケンスはまだ保存されていません。削除しますか?";
466                                                 if( ! confirm(message) ) return;
467                                         }
468                                 }
469                                 model.removeSelectedSequence();
470                         }
471                 };
472                 /**
473                  * ファイル選択ダイアログ(アプレットでは使用不可)
474                  */
475                 private class MidiFileChooser extends JFileChooser {
476                         {
477                                 setFileFilter(new FileNameExtensionFilter("MIDI sequence (*.mid)", "mid"));
478                         }
479                         /**
480                          * ファイル保存アクション
481                          */
482                         public Action saveMidiFileAction = getModel().new SelectedSequenceAction(
483                                 "Save",
484                                 "Save selected MIDI sequence to file - 選択したMIDIシーケンスをファイルに保存"
485                         ) {
486                                 @Override
487                                 public void actionPerformed(ActionEvent event) {
488                                         SequenceTrackListTableModel sequenceModel = getModel().getSelectedSequenceModel();
489                                         String fn = sequenceModel.getFilename();
490                                         if( fn != null && ! fn.isEmpty() ) setSelectedFile(new File(fn));
491                                         if( showSaveDialog((Component)event.getSource()) != JFileChooser.APPROVE_OPTION ) return;
492                                         File f = getSelectedFile();
493                                         if( f.exists() ) {
494                                                 fn = f.getName();
495                                                 if( ! confirm("Overwrite " + fn + " ?\n" + fn + " を上書きしてよろしいですか?") ) return;
496                                         }
497                                         try ( FileOutputStream out = new FileOutputStream(f) ) {
498                                                 out.write(sequenceModel.getMIDIdata());
499                                                 sequenceModel.setModified(false);
500                                         }
501                                         catch( IOException ex ) {
502                                                 showError( ex.getMessage() );
503                                                 ex.printStackTrace();
504                                         }
505                                 }
506                         };
507                         /**
508                          * ファイルを開くアクション
509                          */
510                         public Action openMidiFileAction = new AbstractAction("Open") {
511                                 { putValue(Action.SHORT_DESCRIPTION, "Open MIDI file - MIDIファイルを開く"); }
512                                 @Override
513                                 public void actionPerformed(ActionEvent event) {
514                                         if( showOpenDialog((Component)event.getSource()) != JFileChooser.APPROVE_OPTION ) return;
515                                         loadAndPlay(getSelectedFile());
516                                 }
517                         };
518                 };
519         }
520
521         /**
522          * シーケンス(トラックリスト)テーブルビュー
523          */
524         public class TrackListTable extends JTable {
525                 /**
526                  * トラックリストテーブルビューを構築します。
527                  * @param model シーケンス(トラックリスト)データモデル
528                  */
529                 public TrackListTable(SequenceTrackListTableModel model) {
530                         super(model, null, model.trackListSelectionModel);
531                         //
532                         // 録音対象のMIDIチャンネルをコンボボックスで選択できるようにする
533                         int colIndex = SequenceTrackListTableModel.Column.RECORD_CHANNEL.ordinal();
534                         TableColumn tc = getColumnModel().getColumn(colIndex);
535                         tc.setCellEditor(new DefaultCellEditor(new JComboBox<String>(){{
536                                 addItem("OFF");
537                                 for(int i=1; i <= MIDISpec.MAX_CHANNELS; i++) addItem(String.format("%d", i));
538                                 addItem("ALL");
539                         }}));
540                         setAutoCreateColumnsFromModel(false);
541                         //
542                         trackSelectionListener = new TrackSelectionListener();
543                         titleLabel = new TitleLabel();
544                         model.sequenceListTableModel.sequenceListSelectionModel.addListSelectionListener(titleLabel);
545                         TableColumnModel colModel = getColumnModel();
546                         for( SequenceTrackListTableModel.Column c : SequenceTrackListTableModel.Column.values() )
547                                 colModel.getColumn(c.ordinal()).setPreferredWidth(c.preferredWidth);
548                 }
549                 /**
550                  * このテーブルビューが表示するデータを提供する
551                  * シーケンス(トラックリスト)データモデルを返します。
552                  * @return シーケンス(トラックリスト)データモデル
553                  */
554                 @Override
555                 public SequenceTrackListTableModel getModel() {
556                         return (SequenceTrackListTableModel) super.getModel();
557                 }
558                 /**
559                  * タイトルラベル
560                  */
561                 TitleLabel titleLabel;
562                 /**
563                  * 親テーブルの選択シーケンスの変更に反応する
564                  * 曲番号表示付きタイトルラベル
565                  */
566                 private class TitleLabel extends JLabel implements ListSelectionListener {
567                         private static final String TITLE = "Tracks";
568                         public TitleLabel() { setText(TITLE); }
569                         @Override
570                         public void valueChanged(ListSelectionEvent event) {
571                                 if( event.getValueIsAdjusting() ) return;
572                                 SequenceTrackListTableModel oldModel = getModel();
573                                 SequenceTrackListTableModel newModel = oldModel.sequenceListTableModel.getSelectedSequenceModel();
574                                 if( oldModel == newModel ) return;
575                                 //
576                                 // MIDIチャンネル選択中のときはキャンセルする
577                                 cancelCellEditing();
578                                 //
579                                 int index = oldModel.sequenceListTableModel.sequenceListSelectionModel.getMinSelectionIndex();
580                                 String text = TITLE;
581                                 if( index >= 0 ) text = String.format(text+" - MIDI file No.%d", index);
582                                 setText(text);
583                                 if( newModel == null ) {
584                                         newModel = oldModel.sequenceListTableModel.emptyTrackListTableModel;
585                                         addTrackAction.setEnabled(false);
586                                 }
587                                 else {
588                                         addTrackAction.setEnabled(true);
589                                 }
590                                 oldModel.trackListSelectionModel.removeListSelectionListener(trackSelectionListener);
591                                 setModel(newModel);
592                                 setSelectionModel(newModel.trackListSelectionModel);
593                                 newModel.trackListSelectionModel.addListSelectionListener(trackSelectionListener);
594                                 trackSelectionListener.valueChanged(null);
595                         }
596                 }
597                 /**
598                  * トラック選択リスナー
599                  */
600                 TrackSelectionListener trackSelectionListener;
601                 /**
602                  * 選択トラックの変更に反応するリスナー
603                  */
604                 private class TrackSelectionListener implements ListSelectionListener {
605                         @Override
606                         public void valueChanged(ListSelectionEvent e) {
607                                 if( e != null && e.getValueIsAdjusting() ) return;
608                                 ListSelectionModel tlsm = getModel().trackListSelectionModel;
609                                 deleteTrackAction.setEnabled(! tlsm.isSelectionEmpty());
610                                 eventListTable.titleLabel.update(tlsm, getModel());
611                         }
612                 }
613                 /**
614                  * {@inheritDoc}
615                  *
616                  * <p>このトラックリストテーブルのデータが変わったときに編集を解除します。
617                  * 例えば、イベントが編集された場合や、
618                  * シーケンサーからこのモデルが外された場合がこれに該当します。
619                  * </p>
620                  */
621                 @Override
622                 public void tableChanged(TableModelEvent e) {
623                         super.tableChanged(e);
624                         cancelCellEditing();
625                 }
626                 /**
627                  * このトラックリストテーブルが編集モードになっていたら解除します。
628                  */
629                 private void cancelCellEditing() {
630                         TableCellEditor currentCellEditor = getCellEditor();
631                         if( currentCellEditor != null ) currentCellEditor.cancelCellEditing();
632                 }
633                 /**
634                  * トラック追加アクション
635                  */
636                 Action addTrackAction = new AbstractAction("New") {
637                         {
638                                 String tooltip = "Append new track - 新しいトラックの追加";
639                                 putValue(Action.SHORT_DESCRIPTION, tooltip);
640                                 setEnabled(false);
641                         }
642                         @Override
643                         public void actionPerformed(ActionEvent e) { getModel().createTrack(); }
644                 };
645                 /**
646                  * トラック削除アクション
647                  */
648                 Action deleteTrackAction = new AbstractAction("Delete", deleteIcon) {
649                         {
650                                 String tooltip = "Delete selected track - 選択したトラックを削除";
651                                 putValue(Action.SHORT_DESCRIPTION, tooltip);
652                                 setEnabled(false);
653                         }
654                         @Override
655                         public void actionPerformed(ActionEvent e) {
656                                 String message = "Do you want to delete selected track ?\n選択したトラックを削除しますか?";
657                                 if( confirm(message) ) getModel().deleteSelectedTracks();
658                         }
659                 };
660         }
661
662         /**
663          * MIDIイベントリストテーブルビュー(選択中のトラックの中身)
664          */
665         public class EventListTable extends JTable {
666                 /**
667                  * 新しいイベントリストテーブルを構築します。
668                  * <p>データモデルとして一つのトラックのイベントリストを指定できます。
669                  * トラックを切り替えたいときは {@link #setModel(TableModel)}
670                  * でデータモデルを異なるトラックのものに切り替えます。
671                  * </p>
672                  *
673                  * @param model トラック(イベントリスト)データモデル
674                  */
675                 public EventListTable(TrackEventListTableModel model) {
676                         super(model, null, model.eventSelectionModel);
677                         //
678                         // 列モデルにセルエディタを設定
679                         eventCellEditor = new MidiEventCellEditor();
680                         setAutoCreateColumnsFromModel(false);
681                         //
682                         eventSelectionListener = new EventSelectionListener();
683                         titleLabel = new TitleLabel();
684                         //
685                         TableColumnModel colModel = getColumnModel();
686                         for( TrackEventListTableModel.Column c : TrackEventListTableModel.Column.values() )
687                                 colModel.getColumn(c.ordinal()).setPreferredWidth(c.preferredWidth);
688                 }
689                 /**
690                  * このテーブルビューが表示するデータを提供する
691                  * トラック(イベントリスト)データモデルを返します。
692                  * @return トラック(イベントリスト)データモデル
693                  */
694                 @Override
695                 public TrackEventListTableModel getModel() {
696                         return (TrackEventListTableModel) super.getModel();
697                 }
698                 /**
699                  * タイトルラベル
700                  */
701                 TitleLabel titleLabel;
702                 /**
703                  * 親テーブルの選択トラックの変更に反応する
704                  * トラック番号つきタイトルラベル
705                  */
706                 private class TitleLabel extends JLabel {
707                         private static final String TITLE = "MIDI Events";
708                         public TitleLabel() { super(TITLE); }
709                         public void update(ListSelectionModel tlsm, SequenceTrackListTableModel sequenceModel) {
710                                 String text = TITLE;
711                                 TrackEventListTableModel oldTrackModel = getModel();
712                                 int index = tlsm.getMinSelectionIndex();
713                                 if( index >= 0 ) {
714                                         text = String.format(TITLE+" - track No.%d", index);
715                                 }
716                                 setText(text);
717                                 TrackEventListTableModel newTrackModel = sequenceModel.getSelectedTrackModel();
718                                 if( oldTrackModel == newTrackModel )
719                                         return;
720                                 if( newTrackModel == null ) {
721                                         newTrackModel = getModel().sequenceTrackListTableModel.sequenceListTableModel.emptyEventListTableModel;
722                                         queryJumpEventAction.setEnabled(false);
723                                         queryAddEventAction.setEnabled(false);
724
725                                         queryPasteEventAction.setEnabled(false);
726                                         copyEventAction.setEnabled(false);
727                                         deleteEventAction.setEnabled(false);
728                                         cutEventAction.setEnabled(false);
729                                 }
730                                 else {
731                                         queryJumpEventAction.setEnabled(true);
732                                         queryAddEventAction.setEnabled(true);
733                                 }
734                                 oldTrackModel.eventSelectionModel.removeListSelectionListener(eventSelectionListener);
735                                 setModel(newTrackModel);
736                                 setSelectionModel(newTrackModel.eventSelectionModel);
737                                 newTrackModel.eventSelectionModel.addListSelectionListener(eventSelectionListener);
738                         }
739                 }
740                 /**
741                  * イベント選択リスナー
742                  */
743                 private EventSelectionListener eventSelectionListener;
744                 /**
745                  * 選択イベントの変更に反応するリスナー
746                  */
747                 private class EventSelectionListener implements ListSelectionListener {
748                         public EventSelectionListener() {
749                                 getModel().eventSelectionModel.addListSelectionListener(this);
750                         }
751                         @Override
752                         public void valueChanged(ListSelectionEvent e) {
753                                 if( e.getValueIsAdjusting() )
754                                         return;
755                                 if( getSelectionModel().isSelectionEmpty() ) {
756                                         queryPasteEventAction.setEnabled(false);
757                                         copyEventAction.setEnabled(false);
758                                         deleteEventAction.setEnabled(false);
759                                         cutEventAction.setEnabled(false);
760                                 }
761                                 else {
762                                         copyEventAction.setEnabled(true);
763                                         deleteEventAction.setEnabled(true);
764                                         cutEventAction.setEnabled(true);
765                                         TrackEventListTableModel trackModel = getModel();
766                                         int minIndex = getSelectionModel().getMinSelectionIndex();
767                                         MidiEvent midiEvent = trackModel.getMidiEvent(minIndex);
768                                         if( midiEvent != null ) {
769                                                 MidiMessage msg = midiEvent.getMessage();
770                                                 if( msg instanceof ShortMessage ) {
771                                                         ShortMessage sm = (ShortMessage)msg;
772                                                         int cmd = sm.getCommand();
773                                                         if( cmd == 0x80 || cmd == 0x90 || cmd == 0xA0 ) {
774                                                                 // ノート番号を持つ場合、音を鳴らす。
775                                                                 MidiChannel outMidiChannels[] = virtualMidiDevice.getChannels();
776                                                                 int ch = sm.getChannel();
777                                                                 int note = sm.getData1();
778                                                                 int vel = sm.getData2();
779                                                                 outMidiChannels[ch].noteOn(note, vel);
780                                                                 outMidiChannels[ch].noteOff(note, vel);
781                                                         }
782                                                 }
783                                         }
784                                         if( pairNoteOnOffModel.isSelected() ) {
785                                                 int maxIndex = getSelectionModel().getMaxSelectionIndex();
786                                                 int partnerIndex;
787                                                 for( int i=minIndex; i<=maxIndex; i++ ) {
788                                                         if( ! getSelectionModel().isSelectedIndex(i) ) continue;
789                                                         partnerIndex = trackModel.getIndexOfPartnerFor(i);
790                                                         if( partnerIndex >= 0 && ! getSelectionModel().isSelectedIndex(partnerIndex) )
791                                                                 getSelectionModel().addSelectionInterval(partnerIndex, partnerIndex);
792                                                 }
793                                         }
794                                 }
795                         }
796                 }
797                 /**
798                  * Pair noteON/OFF トグルボタンモデル
799                  */
800                 private JToggleButton.ToggleButtonModel
801                         pairNoteOnOffModel = new JToggleButton.ToggleButtonModel() {
802                                 {
803                                         addItemListener(new ItemListener() {
804                                                 public void itemStateChanged(ItemEvent e) {
805                                                         eventDialog.midiMessageForm.durationForm.setEnabled(isSelected());
806                                                 }
807                                         });
808                                         setSelected(true);
809                                 }
810                         };
811                 private class EventEditContext {
812                         /**
813                          * 編集対象トラック
814                          */
815                         private TrackEventListTableModel trackModel;
816                         /**
817                          * tick位置入力モデル
818                          */
819                         private TickPositionModel tickPositionModel = new TickPositionModel();
820                         /**
821                          * 選択されたイベント
822                          */
823                         private MidiEvent selectedMidiEvent = null;
824                         /**
825                          * 選択されたイベントの場所
826                          */
827                         private int selectedIndex = -1;
828                         /**
829                          * 選択されたイベントのtick位置
830                          */
831                         private long currentTick = 0;
832                         /**
833                          * 上書きして削除対象にする変更前イベント(null可)
834                          */
835                         private MidiEvent[] midiEventsToBeOverwritten;
836                         /**
837                          * 選択したイベントを入力ダイアログなどに反映します。
838                          * @param model 対象データモデル
839                          */
840                         private void setSelectedEvent(TrackEventListTableModel trackModel) {
841                                 this.trackModel = trackModel;
842                                 SequenceTrackListTableModel sequenceTableModel = trackModel.sequenceTrackListTableModel;
843                                 int ppq = sequenceTableModel.getSequence().getResolution();
844                                 eventDialog.midiMessageForm.durationForm.setPPQ(ppq);
845                                 tickPositionModel.setSequenceIndex(sequenceTableModel.getSequenceTickIndex());
846
847                                 selectedIndex = trackModel.eventSelectionModel.getMinSelectionIndex();
848                                 selectedMidiEvent = selectedIndex < 0 ? null : trackModel.getMidiEvent(selectedIndex);
849                                 currentTick = selectedMidiEvent == null ? 0 : selectedMidiEvent.getTick();
850                                 tickPositionModel.setTickPosition(currentTick);
851                         }
852                         public void setupForEdit(TrackEventListTableModel trackModel) {
853                                 MidiEvent partnerEvent = null;
854                                 eventDialog.midiMessageForm.setMessage(
855                                         selectedMidiEvent.getMessage(),
856                                         trackModel.sequenceTrackListTableModel.charset
857                                 );
858                                 if( eventDialog.midiMessageForm.isNote() ) {
859                                         int partnerIndex = trackModel.getIndexOfPartnerFor(selectedIndex);
860                                         if( partnerIndex < 0 ) {
861                                                 eventDialog.midiMessageForm.durationForm.setDuration(0);
862                                         }
863                                         else {
864                                                 partnerEvent = trackModel.getMidiEvent(partnerIndex);
865                                                 long partnerTick = partnerEvent.getTick();
866                                                 long duration = currentTick > partnerTick ?
867                                                         currentTick - partnerTick : partnerTick - currentTick ;
868                                                 eventDialog.midiMessageForm.durationForm.setDuration((int)duration);
869                                         }
870                                 }
871                                 if(partnerEvent == null)
872                                         midiEventsToBeOverwritten = new MidiEvent[] {selectedMidiEvent};
873                                 else
874                                         midiEventsToBeOverwritten = new MidiEvent[] {selectedMidiEvent, partnerEvent};
875                         }
876                         private Action jumpEventAction = new AbstractAction() {
877                                 { putValue(NAME,"Jump"); }
878                                 public void actionPerformed(ActionEvent e) {
879                                         long tick = tickPositionModel.getTickPosition();
880                                         scrollToEventAt(tick);
881                                         eventDialog.setVisible(false);
882                                         trackModel = null;
883                                 }
884                         };
885                         private Action pasteEventAction = new AbstractAction() {
886                                 { putValue(NAME,"Paste"); }
887                                 public void actionPerformed(ActionEvent e) {
888                                         long tick = tickPositionModel.getTickPosition();
889                                         clipBoard.paste(trackModel, tick);
890                                         scrollToEventAt(tick);
891                                         // ペーストで曲の長さが変わったことをプレイリストに通知
892                                         SequenceTrackListTableModel seqModel = trackModel.sequenceTrackListTableModel;
893                                         seqModel.sequenceListTableModel.fireSequenceModified(seqModel);
894                                         eventDialog.setVisible(false);
895                                         trackModel = null;
896                                 }
897                         };
898                         private boolean applyEvent() {
899                                 long tick = tickPositionModel.getTickPosition();
900                                 MidiMessageForm form = eventDialog.midiMessageForm;
901                                 SequenceTrackListTableModel seqModel = trackModel.sequenceTrackListTableModel;
902                                 MidiEvent newMidiEvent = new MidiEvent(form.getMessage(seqModel.charset), tick);
903                                 if( midiEventsToBeOverwritten != null ) {
904                                         // 上書き消去するための選択済イベントがあった場合
905                                         trackModel.removeMidiEvents(midiEventsToBeOverwritten);
906                                 }
907                                 if( ! trackModel.addMidiEvent(newMidiEvent) ) {
908                                         System.out.println("addMidiEvent failure");
909                                         return false;
910                                 }
911                                 if(pairNoteOnOffModel.isSelected() && form.isNote()) {
912                                         ShortMessage sm = form.createPartnerMessage();
913                                         if(sm == null)
914                                                 scrollToEventAt( tick );
915                                         else {
916                                                 int duration = form.durationForm.getDuration();
917                                                 if( form.isNote(false) ) {
918                                                         duration = -duration;
919                                                 }
920                                                 long partnerTick = tick + (long)duration;
921                                                 if( partnerTick < 0L ) partnerTick = 0L;
922                                                 MidiEvent partner = new MidiEvent((MidiMessage)sm, partnerTick);
923                                                 if( ! trackModel.addMidiEvent(partner) ) {
924                                                         System.out.println("addMidiEvent failure (note on/off partner message)");
925                                                 }
926                                                 scrollToEventAt(partnerTick > tick ? partnerTick : tick);
927                                         }
928                                 }
929                                 seqModel.sequenceListTableModel.fireSequenceModified(seqModel);
930                                 eventDialog.setVisible(false);
931                                 return true;
932                         }
933                 }
934                 private EventEditContext editContext = new EventEditContext();
935                 /**
936                  * 指定のTick位置へジャンプするアクション
937                  */
938                 Action queryJumpEventAction = new AbstractAction() {
939                         {
940                                 putValue(NAME,"Jump to ...");
941                                 setEnabled(false);
942                         }
943                         public void actionPerformed(ActionEvent e) {
944                                 editContext.setSelectedEvent(getModel());
945                                 eventDialog.openTickForm("Jump selection to", editContext.jumpEventAction);
946                         }
947                 };
948                 /**
949                  * 新しいイベントの追加を行うアクション
950                  */
951                 Action queryAddEventAction = new AbstractAction() {
952                         {
953                                 putValue(NAME,"New");
954                                 setEnabled(false);
955                         }
956                         public void actionPerformed(ActionEvent e) {
957                                 TrackEventListTableModel model = getModel();
958                                 editContext.setSelectedEvent(model);
959                                 editContext.midiEventsToBeOverwritten = null;
960                                 eventDialog.openEventForm(
961                                         "New MIDI event",
962                                         eventCellEditor.applyEventAction,
963                                         model.getChannel()
964                                 );
965                         }
966                 };
967                 /**
968                  * MIDIイベントのコピー&ペーストを行うためのクリップボード
969                  */
970                 private class LocalClipBoard {
971                         private MidiEvent copiedEventsToPaste[];
972                         private int copiedEventsPPQ = 0;
973                         public void copy(TrackEventListTableModel model, boolean withRemove) {
974                                 copiedEventsToPaste = model.getSelectedMidiEvents();
975                                 copiedEventsPPQ = model.sequenceTrackListTableModel.getSequence().getResolution();
976                                 if( withRemove ) model.removeMidiEvents(copiedEventsToPaste);
977                                 boolean en = (copiedEventsToPaste != null && copiedEventsToPaste.length > 0);
978                                 queryPasteEventAction.setEnabled(en);
979                         }
980                         public void cut(TrackEventListTableModel model) {copy(model,true);}
981                         public void copy(TrackEventListTableModel model){copy(model,false);}
982                         public void paste(TrackEventListTableModel model, long tick) {
983                                 model.addMidiEvents(copiedEventsToPaste, tick, copiedEventsPPQ);
984                         }
985                 }
986                 private LocalClipBoard clipBoard = new LocalClipBoard();
987                 /**
988                  * 指定のTick位置へ貼り付けるアクション
989                  */
990                 Action queryPasteEventAction = new AbstractAction() {
991                         {
992                                 putValue(NAME,"Paste to ...");
993                                 setEnabled(false);
994                         }
995                         public void actionPerformed(ActionEvent e) {
996                                 editContext.setSelectedEvent(getModel());
997                                 eventDialog.openTickForm("Paste to", editContext.pasteEventAction);
998                         }
999                 };
1000                 /**
1001                  * イベントカットアクション
1002                  */
1003                 public Action cutEventAction = new AbstractAction("Cut") {
1004                         {
1005                                 setEnabled(false);
1006                         }
1007                         @Override
1008                         public void actionPerformed(ActionEvent e) {
1009                                 TrackEventListTableModel model = getModel();
1010                                 if( ! confirm("Do you want to cut selected event ?\n選択したMIDIイベントを切り取りますか?"))
1011                                         return;
1012                                 clipBoard.cut(model);
1013                         }
1014                 };
1015                 /**
1016                  * イベントコピーアクション
1017                  */
1018                 public Action copyEventAction = new AbstractAction("Copy") {
1019                         {
1020                                 setEnabled(false);
1021                         }
1022                         @Override
1023                         public void actionPerformed(ActionEvent e) {
1024                                 clipBoard.copy(getModel());
1025                         }
1026                 };
1027                 /**
1028                  * イベント削除アクション
1029                  */
1030                 public Action deleteEventAction = new AbstractAction("Delete", deleteIcon) {
1031                         {
1032                                 setEnabled(false);
1033                         }
1034                         @Override
1035                         public void actionPerformed(ActionEvent e) {
1036                                 TrackEventListTableModel model = getModel();
1037                                 if( ! confirm("Do you want to delete selected event ?\n選択したMIDIイベントを削除しますか?"))
1038                                         return;
1039                                 model.removeSelectedMidiEvents();
1040                         }
1041                 };
1042                 /**
1043                  * MIDIイベント表のセルエディタ
1044                  */
1045                 private MidiEventCellEditor eventCellEditor;
1046                 /**
1047                  * MIDIイベント表のセルエディタ
1048                  */
1049                 class MidiEventCellEditor extends AbstractCellEditor implements TableCellEditor {
1050                         /**
1051                          * MIDIイベントセルエディタを構築します。
1052                          */
1053                         public MidiEventCellEditor() {
1054                                 eventDialog.midiMessageForm.setOutputMidiChannels(virtualMidiDevice.getChannels());
1055                                 eventDialog.tickPositionInputForm.setModel(editContext.tickPositionModel);
1056                                 int index = TrackEventListTableModel.Column.MESSAGE.ordinal();
1057                                 getColumnModel().getColumn(index).setCellEditor(this);
1058                         }
1059                         /**
1060                          * セルをダブルクリックしないと編集できないようにします。
1061                          * @param e イベント(マウスイベント)
1062                          * @return 編集可能になったらtrue
1063                          */
1064                         @Override
1065                         public boolean isCellEditable(EventObject e) {
1066                                 if( ! (e instanceof MouseEvent) ) return super.isCellEditable(e);
1067                                 return ((MouseEvent)e).getClickCount() == 2;
1068                         }
1069                         @Override
1070                         public Object getCellEditorValue() { return null; }
1071                         /**
1072                          * MIDIメッセージダイアログが閉じたときにセル編集を中止するリスナー
1073                          */
1074                         private ComponentListener dialogComponentListener = new ComponentAdapter() {
1075                                 @Override
1076                                 public void componentHidden(ComponentEvent e) {
1077                                         fireEditingCanceled();
1078                                         // 用が済んだら当リスナーを除去
1079                                         eventDialog.removeComponentListener(this);
1080                                 }
1081                         };
1082                         /**
1083                          * 既存イベントを編集するアクション
1084                          */
1085                         private Action editEventAction = new AbstractAction() {
1086                                 public void actionPerformed(ActionEvent e) {
1087                                         TrackEventListTableModel model = getModel();
1088                                         editContext.setSelectedEvent(model);
1089                                         if( editContext.selectedMidiEvent == null )
1090                                                 return;
1091                                         editContext.setupForEdit(model);
1092                                         eventDialog.addComponentListener(dialogComponentListener);
1093                                         eventDialog.openEventForm("Change MIDI event", applyEventAction);
1094                                 }
1095                         };
1096                         /**
1097                          * イベント編集ボタン
1098                          */
1099                         private JButton editEventButton = new JButton(editEventAction){{
1100                                 setHorizontalAlignment(JButton.LEFT);
1101                         }};
1102                         @Override
1103                         public Component getTableCellEditorComponent(
1104                                 JTable table, Object value, boolean isSelected, int row, int column
1105                         ) {
1106                                 editEventButton.setText(value.toString());
1107                                 return editEventButton;
1108                         }
1109                         /**
1110                          * 入力したイベントを反映するアクション
1111                          */
1112                         private Action applyEventAction = new AbstractAction() {
1113                                 {
1114                                         putValue(NAME,"OK");
1115                                 }
1116                                 public void actionPerformed(ActionEvent e) {
1117                                         if( editContext.applyEvent() ) fireEditingStopped();
1118                                 }
1119                         };
1120                 }
1121                 /**
1122                  * スクロール可能なMIDIイベントテーブルビュー
1123                  */
1124                 private JScrollPane scrollPane = new JScrollPane(this);
1125                 /**
1126                  * 指定の MIDI tick のイベントへスクロールします。
1127                  * @param tick MIDI tick
1128                  */
1129                 public void scrollToEventAt(long tick) {
1130                         int index = getModel().tickToIndex(tick);
1131                         scrollPane.getVerticalScrollBar().setValue(index * getRowHeight());
1132                         getSelectionModel().setSelectionInterval(index, index);
1133                 }
1134         }
1135
1136         /**
1137          * 新しい {@link MidiSequenceEditor} を構築します。
1138          * @param deviceModelList MIDIデバイスモデルリスト
1139          */
1140         public MidiSequenceEditor(MidiSequencerModel sequencerModel) {
1141                 sequenceListTable = new SequenceListTable(new PlaylistTableModel(sequencerModel));
1142                 trackListTable = new TrackListTable(
1143                         new SequenceTrackListTableModel(sequenceListTable.getModel(), null, null)
1144                 );
1145                 eventListTable = new EventListTable(new TrackEventListTableModel(trackListTable.getModel(), null));
1146                 setTitle("MIDI Editor/Playlist - MIDI Chord Helper");
1147                 setBounds( 150, 200, 900, 500 );
1148                 setLayout(new FlowLayout());
1149                 new DropTarget(this, DnDConstants.ACTION_COPY_OR_MOVE, dropTargetListener, true);
1150                 //
1151                 // パネルレイアウト
1152                 JPanel playlistPanel = new JPanel() {{
1153                         JPanel playlistOperationPanel = new JPanel() {{
1154                                 setLayout(new BoxLayout(this, BoxLayout.LINE_AXIS));
1155                                 add(Box.createRigidArea(new Dimension(10, 0)));
1156                                 add(new JButton(newSequenceDialog.openAction) {{ setMargin(ZERO_INSETS); }});
1157                                 if( sequenceListTable.midiFileChooser != null ) {
1158                                         add( Box.createRigidArea(new Dimension(5, 0)) );
1159                                         add(new JButton(sequenceListTable.midiFileChooser.openMidiFileAction) {{
1160                                                 setMargin(ZERO_INSETS);
1161                                         }});
1162                                 }
1163                                 if(sequenceListTable.base64EncodeAction != null) {
1164                                         add(Box.createRigidArea(new Dimension(5, 0)));
1165                                         add(new JButton(sequenceListTable.base64EncodeAction) {{ setMargin(ZERO_INSETS); }});
1166                                 }
1167                                 add(Box.createRigidArea(new Dimension(5, 0)));
1168                                 PlaylistTableModel playlistTableModel = sequenceListTable.getModel();
1169                                 add(new JButton(playlistTableModel.moveToTopAction) {{ setMargin(ZERO_INSETS); }});
1170                                 add(Box.createRigidArea(new Dimension(5, 0)));
1171                                 add(new JButton(playlistTableModel.moveToBottomAction) {{ setMargin(ZERO_INSETS); }});
1172                                 if( sequenceListTable.midiFileChooser != null ) {
1173                                         add(Box.createRigidArea(new Dimension(5, 0)));
1174                                         add(new JButton(sequenceListTable.midiFileChooser.saveMidiFileAction) {{
1175                                                 setMargin(ZERO_INSETS);
1176                                         }});
1177                                 }
1178                                 add( Box.createRigidArea(new Dimension(5, 0)) );
1179                                 add(new JButton(sequenceListTable.deleteSequenceAction) {{ setMargin(ZERO_INSETS); }});
1180                                 add( Box.createRigidArea(new Dimension(5, 0)) );
1181                                 add(new SequencerSpeedSlider(playlistTableModel.sequencerModel.speedSliderModel));
1182                                 add( Box.createRigidArea(new Dimension(5, 0)) );
1183                                 add(new JPanel() {{
1184                                         MidiSequencerModel sequencerModel = sequenceListTable.getModel().sequencerModel;
1185                                         add(new JLabel("SyncMode:"));
1186                                         add(new JLabel("Master"));
1187                                         add(new JComboBox<Sequencer.SyncMode>(sequencerModel.masterSyncModeModel));
1188                                         add(new JLabel("Slave"));
1189                                         add(new JComboBox<Sequencer.SyncMode>(sequencerModel.slaveSyncModeModel));
1190                                 }});
1191                         }};
1192                         setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
1193                         add(new JScrollPane(sequenceListTable));
1194                         add(Box.createRigidArea(new Dimension(0, 10)));
1195                         add(playlistOperationPanel);
1196                         add(Box.createRigidArea(new Dimension(0, 10)));
1197                 }};
1198                 JPanel trackListPanel = new JPanel() {{
1199                         JPanel trackListOperationPanel = new JPanel() {{
1200                                 add(new JButton(trackListTable.addTrackAction) {{ setMargin(ZERO_INSETS); }});
1201                                 add(new JButton(trackListTable.deleteTrackAction) {{ setMargin(ZERO_INSETS); }});
1202                         }};
1203                         setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
1204                         add(trackListTable.titleLabel);
1205                         add(Box.createRigidArea(new Dimension(0, 5)));
1206                         add(new JScrollPane(trackListTable));
1207                         add(Box.createRigidArea(new Dimension(0, 5)));
1208                         add(trackListOperationPanel);
1209                 }};
1210                 JPanel eventListPanel = new JPanel() {{
1211                         JPanel eventListOperationPanel = new JPanel() {{
1212                                 add(new JCheckBox("Pair NoteON/OFF") {{
1213                                         setModel(eventListTable.pairNoteOnOffModel);
1214                                         setToolTipText("NoteON/OFFをペアで同時選択する");
1215                                 }});
1216                                 add(new JButton(eventListTable.queryJumpEventAction) {{ setMargin(ZERO_INSETS); }});
1217                                 add(new JButton(eventListTable.queryAddEventAction) {{ setMargin(ZERO_INSETS); }});
1218                                 add(new JButton(eventListTable.copyEventAction) {{ setMargin(ZERO_INSETS); }});
1219                                 add(new JButton(eventListTable.cutEventAction) {{ setMargin(ZERO_INSETS); }});
1220                                 add(new JButton(eventListTable.queryPasteEventAction) {{ setMargin(ZERO_INSETS); }});
1221                                 add(new JButton(eventListTable.deleteEventAction) {{ setMargin(ZERO_INSETS); }});
1222                         }};
1223                         setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
1224                         add(eventListTable.titleLabel);
1225                         add(eventListTable.scrollPane);
1226                         add(eventListOperationPanel);
1227                 }};
1228                 Container cp = getContentPane();
1229                 cp.setLayout(new BoxLayout(cp, BoxLayout.Y_AXIS));
1230                 cp.add(Box.createVerticalStrut(2));
1231                 cp.add(
1232                         new JSplitPane(JSplitPane.VERTICAL_SPLIT, playlistPanel,
1233                                 new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, trackListPanel, eventListPanel) {{
1234                                         setDividerLocation(300);
1235                                 }}
1236                         ) {{
1237                                 setDividerLocation(160);
1238                         }}
1239                 );
1240         }
1241
1242 }