1 package camidion.chordhelper.midieditor;
\r
3 import java.awt.Component;
\r
4 import java.awt.Container;
\r
5 import java.awt.Dimension;
\r
6 import java.awt.FlowLayout;
\r
7 import java.awt.Insets;
\r
8 import java.awt.datatransfer.DataFlavor;
\r
9 import java.awt.datatransfer.Transferable;
\r
10 import java.awt.dnd.DnDConstants;
\r
11 import java.awt.dnd.DropTarget;
\r
12 import java.awt.dnd.DropTargetDragEvent;
\r
13 import java.awt.dnd.DropTargetDropEvent;
\r
14 import java.awt.dnd.DropTargetEvent;
\r
15 import java.awt.dnd.DropTargetListener;
\r
16 import java.awt.event.ActionEvent;
\r
17 import java.awt.event.ComponentAdapter;
\r
18 import java.awt.event.ComponentEvent;
\r
19 import java.awt.event.ComponentListener;
\r
20 import java.awt.event.ItemEvent;
\r
21 import java.awt.event.ItemListener;
\r
22 import java.awt.event.MouseEvent;
\r
23 import java.io.File;
\r
24 import java.io.FileOutputStream;
\r
25 import java.io.IOException;
\r
26 import java.nio.charset.Charset;
\r
27 import java.security.AccessControlException;
\r
28 import java.util.EventObject;
\r
29 import java.util.List;
\r
30 import java.util.Map;
\r
31 import java.util.Set;
\r
33 import javax.sound.midi.InvalidMidiDataException;
\r
34 import javax.sound.midi.MidiChannel;
\r
35 import javax.sound.midi.MidiEvent;
\r
36 import javax.sound.midi.MidiMessage;
\r
37 import javax.sound.midi.Sequencer;
\r
38 import javax.sound.midi.ShortMessage;
\r
39 import javax.swing.AbstractAction;
\r
40 import javax.swing.AbstractCellEditor;
\r
41 import javax.swing.Action;
\r
42 import javax.swing.Box;
\r
43 import javax.swing.BoxLayout;
\r
44 import javax.swing.DefaultCellEditor;
\r
45 import javax.swing.Icon;
\r
46 import javax.swing.JButton;
\r
47 import javax.swing.JCheckBox;
\r
48 import javax.swing.JComboBox;
\r
49 import javax.swing.JDialog;
\r
50 import javax.swing.JFileChooser;
\r
51 import javax.swing.JLabel;
\r
52 import javax.swing.JOptionPane;
\r
53 import javax.swing.JPanel;
\r
54 import javax.swing.JScrollPane;
\r
55 import javax.swing.JSplitPane;
\r
56 import javax.swing.JTable;
\r
57 import javax.swing.JToggleButton;
\r
58 import javax.swing.ListSelectionModel;
\r
59 import javax.swing.event.ListSelectionEvent;
\r
60 import javax.swing.event.ListSelectionListener;
\r
61 import javax.swing.event.TableModelEvent;
\r
62 import javax.swing.filechooser.FileFilter;
\r
63 import javax.swing.filechooser.FileNameExtensionFilter;
\r
64 import javax.swing.table.JTableHeader;
\r
65 import javax.swing.table.TableCellEditor;
\r
66 import javax.swing.table.TableCellRenderer;
\r
67 import javax.swing.table.TableColumn;
\r
68 import javax.swing.table.TableColumnModel;
\r
69 import javax.swing.table.TableModel;
\r
71 import camidion.chordhelper.ButtonIcon;
\r
72 import camidion.chordhelper.ChordHelperApplet;
\r
73 import camidion.chordhelper.mididevice.AbstractVirtualMidiDevice;
\r
74 import camidion.chordhelper.mididevice.MidiSequencerModel;
\r
75 import camidion.chordhelper.mididevice.VirtualMidiDevice;
\r
76 import camidion.chordhelper.music.MIDISpec;
\r
79 * MIDIエディタ(MIDI Editor/Playlist for MIDI Chord Helper)
\r
82 * Copyright (C) 2006-2014 Akiyoshi Kamide
\r
83 * http://www.yk.rim.or.jp/~kamide/music/chordhelper/
\r
85 public class MidiEditor extends JDialog implements DropTargetListener {
\r
86 private static VirtualMidiDevice virtualMidiDevice = new AbstractVirtualMidiDevice() {
\r
88 info = new MyInfo();
\r
89 setMaxReceivers(0); // 送信専用とする(MIDI IN はサポートしない)
\r
94 protected MyInfo info;
\r
96 public Info getDeviceInfo() {
\r
99 class MyInfo extends Info {
\r
100 protected MyInfo() {
\r
101 super("MIDI Editor","Unknown vendor","MIDI sequence editor","");
\r
106 * このMIDIエディタの仮想MIDIデバイスを返します。
\r
108 public VirtualMidiDevice getVirtualMidiDevice() {
\r
109 return virtualMidiDevice;
\r
112 * このダイアログを表示するアクション
\r
114 public Action openAction = new AbstractAction(
\r
115 "Edit/Playlist/Speed", new ButtonIcon(ButtonIcon.EDIT_ICON)
\r
118 String tooltip = "MIDIシーケンスの編集/プレイリスト/再生速度調整";
\r
119 putValue(Action.SHORT_DESCRIPTION, tooltip);
\r
122 public void actionPerformed(ActionEvent e) { open(); }
\r
125 * このダイアログを開きます。すでに開かれていた場合は前面に移動します。
\r
127 public void open() {
\r
128 if( isVisible() ) toFront(); else setVisible(true);
\r
131 * エラーメッセージダイアログを表示します。
\r
132 * @param message エラーメッセージ
\r
134 public void showError(String message) {
\r
135 JOptionPane.showMessageDialog(
\r
137 ChordHelperApplet.VersionInfo.NAME,
\r
138 JOptionPane.ERROR_MESSAGE
\r
142 * 警告メッセージダイアログを表示します。
\r
143 * @param message 警告メッセージ
\r
145 public void showWarning(String message) {
\r
146 JOptionPane.showMessageDialog(
\r
148 ChordHelperApplet.VersionInfo.NAME,
\r
149 JOptionPane.WARNING_MESSAGE
\r
154 * @param message 確認メッセージ
\r
155 * @return 確認OKのときtrue
\r
157 boolean confirm(String message) {
\r
158 return JOptionPane.showConfirmDialog(
\r
160 ChordHelperApplet.VersionInfo.NAME,
\r
161 JOptionPane.YES_NO_OPTION,
\r
162 JOptionPane.WARNING_MESSAGE
\r
163 ) == JOptionPane.YES_OPTION ;
\r
166 public void dragEnter(DropTargetDragEvent event) {
\r
167 if( event.isDataFlavorSupported(DataFlavor.javaFileListFlavor) ) {
\r
168 event.acceptDrag(DnDConstants.ACTION_COPY_OR_MOVE);
\r
172 public void dragExit(DropTargetEvent event) {}
\r
174 public void dragOver(DropTargetDragEvent event) {}
\r
176 public void dropActionChanged(DropTargetDragEvent event) {}
\r
178 @SuppressWarnings("unchecked")
\r
179 public void drop(DropTargetDropEvent event) {
\r
180 event.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
\r
182 int action = event.getDropAction();
\r
183 if ( (action & DnDConstants.ACTION_COPY_OR_MOVE) != 0 ) {
\r
184 Transferable t = event.getTransferable();
\r
185 Object data = t.getTransferData(DataFlavor.javaFileListFlavor);
\r
186 loadAndPlay((List<File>)data);
\r
187 event.dropComplete(true);
\r
190 event.dropComplete(false);
\r
192 catch (Exception ex) {
\r
193 ex.printStackTrace();
\r
194 event.dropComplete(false);
\r
198 * 複数のMIDIファイルを読み込み、再生されていなかったら再生します。
\r
199 * すでに再生されていた場合、このエディタダイアログを表示します。
\r
201 * @param fileList 読み込むMIDIファイルのリスト
\r
203 public void loadAndPlay(List<File> fileList) {
\r
204 int firstIndex = -1;
\r
205 PlaylistTableModel playlist = sequenceListTable.getModel();
\r
207 firstIndex = playlist.addSequences(fileList);
\r
208 } catch(IOException|InvalidMidiDataException e) {
\r
209 showWarning(e.getMessage());
\r
210 } catch(AccessControlException e) {
\r
211 showError(e.getMessage());
\r
212 e.printStackTrace();
\r
214 if(playlist.sequencerModel.getSequencer().isRunning()) {
\r
217 else if( firstIndex >= 0 ) {
\r
218 playlist.loadToSequencer(firstIndex);
\r
219 playlist.sequencerModel.start();
\r
222 private static final Insets ZERO_INSETS = new Insets(0,0,0,0);
\r
223 private static final Icon deleteIcon = new ButtonIcon(ButtonIcon.X_ICON);
\r
225 * 新しいMIDIシーケンスを生成するダイアログ
\r
227 public NewSequenceDialog newSequenceDialog = new NewSequenceDialog(this);
\r
229 * BASE64テキスト入力ダイアログ
\r
231 public Base64Dialog base64Dialog = new Base64Dialog(this);
\r
233 * プレイリストビュー(シーケンスリスト)
\r
235 public SequenceListTable sequenceListTable;
\r
237 * MIDIトラックリストテーブルビュー(選択中のシーケンスの中身)
\r
239 private TrackListTable trackListTable;
\r
241 * MIDIイベントリストテーブルビュー(選択中のトラックの中身)
\r
243 private EventListTable eventListTable;
\r
245 * MIDIイベント入力ダイアログ(イベント入力とイベント送出で共用)
\r
247 public MidiEventDialog eventDialog = new MidiEventDialog();
\r
249 * プレイリストビュー(シーケンスリスト)
\r
251 public class SequenceListTable extends JTable {
\r
253 * ファイル選択ダイアログ(アプレットでは使用不可)
\r
255 private MidiFileChooser midiFileChooser;
\r
257 * BASE64エンコードアクション(ライブラリが見えている場合のみ有効)
\r
259 private Action base64EncodeAction;
\r
262 * @param model プレイリストデータモデル
\r
264 public SequenceListTable(PlaylistTableModel model) {
\r
265 super(model, null, model.sequenceListSelectionModel);
\r
267 midiFileChooser = new MidiFileChooser();
\r
269 catch( ExceptionInInitializerError|NoClassDefFoundError|AccessControlException e ) {
\r
270 // アプレットの場合、Webクライアントマシンのローカルファイルには
\r
271 // アクセスできないので、ファイル選択ダイアログは使用不可。
\r
272 midiFileChooser = null;
\r
275 new PlayButtonCellEditor();
\r
276 new PositionCellEditor();
\r
278 // 文字コード選択をプルダウンにする
\r
279 int column = PlaylistTableModel.Column.CHARSET.ordinal();
\r
280 TableCellEditor ce = new DefaultCellEditor(new JComboBox<Charset>() {{
\r
281 Set<Map.Entry<String,Charset>> entrySet = Charset.availableCharsets().entrySet();
\r
282 for( Map.Entry<String,Charset> entry : entrySet ) addItem(entry.getValue());
\r
284 getColumnModel().getColumn(column).setCellEditor(ce);
\r
285 setAutoCreateColumnsFromModel(false);
\r
287 // Base64エンコードアクションの生成
\r
288 if( base64Dialog.isBase64Available() ) {
\r
289 base64EncodeAction = new AbstractAction("Base64") {
\r
291 String tooltip = "Base64 text conversion - Base64テキスト変換";
\r
292 putValue(Action.SHORT_DESCRIPTION, tooltip);
\r
295 public void actionPerformed(ActionEvent e) {
\r
296 SequenceTrackListTableModel mstm = getModel().getSelectedSequenceModel();
\r
297 byte[] data = null;
\r
298 String filename = null;
\r
299 if( mstm != null ) {
\r
300 data = mstm.getMIDIdata();
\r
301 filename = mstm.getFilename();
\r
303 base64Dialog.setMIDIData(data, filename);
\r
304 base64Dialog.setVisible(true);
\r
308 TableColumnModel colModel = getColumnModel();
\r
309 for( PlaylistTableModel.Column c : PlaylistTableModel.Column.values() ) {
\r
310 TableColumn tc = colModel.getColumn(c.ordinal());
\r
311 tc.setPreferredWidth(c.preferredWidth);
\r
312 if( c == PlaylistTableModel.Column.LENGTH ) {
\r
317 private TableColumn lengthColumn;
\r
319 public void tableChanged(TableModelEvent event) {
\r
320 super.tableChanged(event);
\r
322 // タイトルに合計シーケンス長を表示
\r
323 if( lengthColumn != null ) {
\r
324 int sec = getModel().getTotalSeconds();
\r
325 String title = PlaylistTableModel.Column.LENGTH.title;
\r
326 title = String.format(title+" [%02d:%02d]", sec/60, sec%60);
\r
327 lengthColumn.setHeaderValue(title);
\r
330 // シーケンス削除時など、合計シーケンス長が変わっても
\r
331 // 列モデルからではヘッダタイトルが再描画されないことがある。
\r
332 // そこで、ヘッダビューから repaint() で突っついて再描画させる。
\r
333 JTableHeader th = getTableHeader();
\r
339 * 時間位置表示セルエディタ(ダブルクリック専用)
\r
341 private class PositionCellEditor extends AbstractCellEditor
\r
342 implements TableCellEditor
\r
344 public PositionCellEditor() {
\r
345 int column = PlaylistTableModel.Column.POSITION.ordinal();
\r
346 TableColumn tc = getColumnModel().getColumn(column);
\r
347 tc.setCellEditor(this);
\r
350 * セルをダブルクリックしたときだけ編集モードに入るようにします。
\r
351 * @param e イベント(マウスイベント)
\r
352 * @return 編集可能になったらtrue
\r
355 public boolean isCellEditable(EventObject e) {
\r
356 // マウスイベント以外のイベントでは編集不可
\r
357 if( ! (e instanceof MouseEvent) ) return false;
\r
358 return ((MouseEvent)e).getClickCount() == 2;
\r
361 public Object getCellEditorValue() { return null; }
\r
363 * 編集モード時のコンポーネントを返すタイミングで
\r
364 * そのシーケンスをシーケンサーにロードしたあと、
\r
369 public Component getTableCellEditorComponent(
\r
370 JTable table, Object value, boolean isSelected,
\r
371 int row, int column
\r
373 getModel().loadToSequencer(row);
\r
374 fireEditingStopped();
\r
379 * プレイボタンを埋め込んだセルエディタ
\r
381 private class PlayButtonCellEditor extends AbstractCellEditor
\r
382 implements TableCellEditor, TableCellRenderer
\r
384 private JToggleButton playButton = new JToggleButton(
\r
385 getModel().sequencerModel.startStopAction
\r
387 public PlayButtonCellEditor() {
\r
388 playButton.setMargin(ZERO_INSETS);
\r
389 int column = PlaylistTableModel.Column.PLAY.ordinal();
\r
390 TableColumn tc = getColumnModel().getColumn(column);
\r
391 tc.setCellRenderer(this);
\r
392 tc.setCellEditor(this);
\r
397 * <p>この実装では、クリックしたセルのシーケンスが
\r
398 * シーケンサーにロードされている場合に
\r
399 * trueを返してプレイボタンを押せるようにします。
\r
400 * そうでない場合はプレイボタンのないセルなので、
\r
401 * ダブルクリックされたときだけtrueを返します。
\r
405 public boolean isCellEditable(EventObject e) {
\r
406 if( ! (e instanceof MouseEvent) ) {
\r
407 // マウスイベント以外はデフォルトメソッドにお任せ
\r
408 return super.isCellEditable(e);
\r
410 fireEditingStopped();
\r
411 MouseEvent me = (MouseEvent)e;
\r
413 int row = rowAtPoint(me.getPoint());
\r
416 PlaylistTableModel model = getModel();
\r
417 if( row >= model.getRowCount() )
\r
419 if( model.sequenceList.get(row).isOnSequencer() ) {
\r
420 // プレイボタン表示中のセルはシングルクリックでもOK
\r
423 // プレイボタンのないセルはダブルクリックのみを受け付ける
\r
424 return me.getClickCount() == 2;
\r
427 public Object getCellEditorValue() { return null; }
\r
431 * <p>この実装では、行の表すシーケンスが
\r
432 * シーケンサーにロードされている場合にプレイボタンを返します。
\r
434 * そのシーケンスをシーケンサーにロードしてnullを返します。
\r
438 public Component getTableCellEditorComponent(
\r
439 JTable table, Object value, boolean isSelected, int row, int column
\r
441 fireEditingStopped();
\r
442 PlaylistTableModel model = getModel();
\r
443 if( model.sequenceList.get(row).isOnSequencer() ) {
\r
446 model.loadToSequencer(row);
\r
450 public Component getTableCellRendererComponent(
\r
451 JTable table, Object value, boolean isSelected,
\r
452 boolean hasFocus, int row, int column
\r
454 PlaylistTableModel model = getModel();
\r
455 if(model.sequenceList.get(row).isOnSequencer()) return playButton;
\r
456 Class<?> cc = model.getColumnClass(column);
\r
457 TableCellRenderer defaultRenderer = table.getDefaultRenderer(cc);
\r
458 return defaultRenderer.getTableCellRendererComponent(
\r
459 table, value, isSelected, hasFocus, row, column
\r
464 * このプレイリスト(シーケンスリスト)が表示するデータを提供する
\r
466 * @return プレイリストモデル
\r
469 public PlaylistTableModel getModel() {
\r
470 return (PlaylistTableModel) super.getModel();
\r
475 Action deleteSequenceAction = getModel().new SelectedSequenceAction(
\r
476 "Delete", MidiEditor.deleteIcon,
\r
477 "Delete selected MIDI sequence - 選択した曲をプレイリストから削除"
\r
480 public void actionPerformed(ActionEvent e) {
\r
481 PlaylistTableModel model = getModel();
\r
482 if( midiFileChooser != null ) {
\r
483 // ファイルに保存できる場合(Javaアプレットではなく、Javaアプリとして動作している場合)
\r
485 SequenceTrackListTableModel seqModel = model.getSelectedSequenceModel();
\r
486 if( seqModel.isModified() ) {
\r
490 "Selected MIDI sequence not saved - delete it ?\n" +
\r
491 "選択したMIDIシーケンスはまだ保存されていません。削除しますか?";
\r
492 if( ! confirm(message) ) {
\r
499 model.removeSelectedSequence();
\r
503 * ファイル選択ダイアログ(アプレットでは使用不可)
\r
505 private class MidiFileChooser extends JFileChooser {
\r
507 String description = "MIDI sequence (*.mid)";
\r
508 String extension = "mid";
\r
509 FileFilter filter = new FileNameExtensionFilter(description, extension);
\r
510 setFileFilter(filter);
\r
515 public Action saveMidiFileAction = getModel().new SelectedSequenceAction(
\r
517 "Save selected MIDI sequence to file - 選択したMIDIシーケンスをファイルに保存"
\r
520 public void actionPerformed(ActionEvent e) {
\r
521 PlaylistTableModel model = getModel();
\r
522 SequenceTrackListTableModel sequenceModel = model.getSelectedSequenceModel();
\r
523 String filename = sequenceModel.getFilename();
\r
525 if( filename != null && ! filename.isEmpty() ) {
\r
526 // プレイリスト上でファイル名が入っていたら、それを初期選択
\r
527 setSelectedFile(selectedFile = new File(filename));
\r
529 int saveOption = showSaveDialog(MidiEditor.this);
\r
530 if( saveOption != JFileChooser.APPROVE_OPTION ) {
\r
531 // 保存ダイアログでキャンセルされた場合
\r
534 if( (selectedFile = getSelectedFile()).exists() ) {
\r
535 // 指定されたファイルがすでにあった場合
\r
536 String fn = selectedFile.getName();
\r
537 String message = "Overwrite " + fn + " ?\n";
\r
538 message += fn + " を上書きしてよろしいですか?";
\r
539 if( ! confirm(message) ) {
\r
545 try ( FileOutputStream out = new FileOutputStream(selectedFile) ) {
\r
546 out.write(sequenceModel.getMIDIdata());
\r
547 sequenceModel.setModified(false);
\r
549 catch( IOException ex ) {
\r
550 showError( ex.getMessage() );
\r
551 ex.printStackTrace();
\r
558 public Action openMidiFileAction = new AbstractAction("Open") {
\r
560 String tooltip = "Open MIDI file - MIDIファイルを開く";
\r
561 putValue(Action.SHORT_DESCRIPTION, tooltip);
\r
564 public void actionPerformed(ActionEvent event) {
\r
565 int openOption = showOpenDialog(MidiEditor.this);
\r
566 if(openOption == JFileChooser.APPROVE_OPTION) {
\r
568 getModel().addSequence(getSelectedFile());
\r
569 } catch( IOException|InvalidMidiDataException e ) {
\r
570 showWarning(e.getMessage());
\r
571 } catch( AccessControlException e ) {
\r
572 showError(e.getMessage());
\r
573 e.printStackTrace();
\r
582 * シーケンス(トラックリスト)テーブルビュー
\r
584 public class TrackListTable extends JTable {
\r
586 * トラックリストテーブルビューを構築します。
\r
587 * @param model シーケンス(トラックリスト)データモデル
\r
589 public TrackListTable(SequenceTrackListTableModel model) {
\r
590 super(model, null, model.trackListSelectionModel);
\r
592 // 録音対象のMIDIチャンネルをコンボボックスで選択できるようにする
\r
593 int colIndex = SequenceTrackListTableModel.Column.RECORD_CHANNEL.ordinal();
\r
594 TableColumn tc = getColumnModel().getColumn(colIndex);
\r
595 tc.setCellEditor(new DefaultCellEditor(new JComboBox<String>(){{
\r
597 for(int i=1; i <= MIDISpec.MAX_CHANNELS; i++)
\r
598 addItem(String.format("%d", i));
\r
601 setAutoCreateColumnsFromModel(false);
\r
603 trackSelectionListener = new TrackSelectionListener();
\r
604 titleLabel = new TitleLabel();
\r
605 model.sequenceListTableModel.sequenceListSelectionModel.addListSelectionListener(titleLabel);
\r
606 TableColumnModel colModel = getColumnModel();
\r
607 for( SequenceTrackListTableModel.Column c : SequenceTrackListTableModel.Column.values() )
\r
608 colModel.getColumn(c.ordinal()).setPreferredWidth(c.preferredWidth);
\r
611 * このテーブルビューが表示するデータを提供する
\r
612 * シーケンス(トラックリスト)データモデルを返します。
\r
613 * @return シーケンス(トラックリスト)データモデル
\r
616 public SequenceTrackListTableModel getModel() {
\r
617 return (SequenceTrackListTableModel) super.getModel();
\r
622 TitleLabel titleLabel;
\r
624 * 親テーブルの選択シーケンスの変更に反応する
\r
627 private class TitleLabel extends JLabel implements ListSelectionListener {
\r
628 private static final String TITLE = "Tracks";
\r
629 public TitleLabel() { setText(TITLE); }
\r
631 public void valueChanged(ListSelectionEvent event) {
\r
632 if( event.getValueIsAdjusting() )
\r
634 SequenceTrackListTableModel oldModel = getModel();
\r
635 SequenceTrackListTableModel newModel = oldModel.sequenceListTableModel.getSelectedSequenceModel();
\r
636 if( oldModel == newModel )
\r
639 // MIDIチャンネル選択中のときはキャンセルする
\r
640 cancelCellEditing();
\r
642 int index = oldModel.sequenceListTableModel.sequenceListSelectionModel.getMinSelectionIndex();
\r
643 String text = TITLE;
\r
645 text = String.format(text+" - MIDI file No.%d", index);
\r
648 if( newModel == null ) {
\r
649 newModel = oldModel.sequenceListTableModel.emptyTrackListTableModel;
\r
650 addTrackAction.setEnabled(false);
\r
653 addTrackAction.setEnabled(true);
\r
655 oldModel.trackListSelectionModel.removeListSelectionListener(trackSelectionListener);
\r
656 setModel(newModel);
\r
657 setSelectionModel(newModel.trackListSelectionModel);
\r
658 newModel.trackListSelectionModel.addListSelectionListener(trackSelectionListener);
\r
659 trackSelectionListener.valueChanged(null);
\r
665 TrackSelectionListener trackSelectionListener;
\r
667 * 選択トラックの変更に反応するリスナー
\r
669 private class TrackSelectionListener implements ListSelectionListener {
\r
671 public void valueChanged(ListSelectionEvent e) {
\r
672 if( e != null && e.getValueIsAdjusting() )
\r
674 ListSelectionModel tlsm = getModel().trackListSelectionModel;
\r
675 deleteTrackAction.setEnabled(! tlsm.isSelectionEmpty());
\r
676 eventListTable.titleLabel.update(tlsm, getModel());
\r
682 * <p>このトラックリストテーブルのデータが変わったときに編集を解除します。
\r
683 * 例えば、イベントが編集された場合や、
\r
684 * シーケンサーからこのモデルが外された場合がこれに該当します。
\r
688 public void tableChanged(TableModelEvent e) {
\r
689 super.tableChanged(e);
\r
690 cancelCellEditing();
\r
693 * このトラックリストテーブルが編集モードになっていたら解除します。
\r
695 private void cancelCellEditing() {
\r
696 TableCellEditor currentCellEditor = getCellEditor();
\r
697 if( currentCellEditor != null )
\r
698 currentCellEditor.cancelCellEditing();
\r
703 Action addTrackAction = new AbstractAction("New") {
\r
705 String tooltip = "Append new track - 新しいトラックの追加";
\r
706 putValue(Action.SHORT_DESCRIPTION, tooltip);
\r
710 public void actionPerformed(ActionEvent e) {
\r
711 getModel().createTrack();
\r
717 Action deleteTrackAction = new AbstractAction("Delete", deleteIcon) {
\r
719 String tooltip = "Delete selected track - 選択したトラックを削除";
\r
720 putValue(Action.SHORT_DESCRIPTION, tooltip);
\r
724 public void actionPerformed(ActionEvent e) {
\r
725 String message = "Do you want to delete selected track ?\n"
\r
726 + "選択したトラックを削除しますか?";
\r
727 if( confirm(message) ) getModel().deleteSelectedTracks();
\r
733 * MIDIイベントリストテーブルビュー(選択中のトラックの中身)
\r
735 public class EventListTable extends JTable {
\r
737 * 新しいイベントリストテーブルを構築します。
\r
738 * <p>データモデルとして一つのトラックのイベントリストを指定できます。
\r
739 * トラックを切り替えたいときは {@link #setModel(TableModel)}
\r
740 * でデータモデルを異なるトラックのものに切り替えます。
\r
743 * @param model トラック(イベントリスト)データモデル
\r
745 public EventListTable(TrackEventListTableModel model) {
\r
746 super(model, null, model.eventSelectionModel);
\r
749 eventCellEditor = new MidiEventCellEditor();
\r
750 setAutoCreateColumnsFromModel(false);
\r
752 eventSelectionListener = new EventSelectionListener();
\r
753 titleLabel = new TitleLabel();
\r
755 TableColumnModel colModel = getColumnModel();
\r
756 for( TrackEventListTableModel.Column c : TrackEventListTableModel.Column.values() )
\r
757 colModel.getColumn(c.ordinal()).setPreferredWidth(c.preferredWidth);
\r
760 * このテーブルビューが表示するデータを提供する
\r
761 * トラック(イベントリスト)データモデルを返します。
\r
762 * @return トラック(イベントリスト)データモデル
\r
765 public TrackEventListTableModel getModel() {
\r
766 return (TrackEventListTableModel) super.getModel();
\r
771 TitleLabel titleLabel;
\r
773 * 親テーブルの選択トラックの変更に反応する
\r
776 private class TitleLabel extends JLabel {
\r
777 private static final String TITLE = "MIDI Events";
\r
778 public TitleLabel() { super(TITLE); }
\r
779 public void update(ListSelectionModel tlsm, SequenceTrackListTableModel sequenceModel) {
\r
780 String text = TITLE;
\r
781 TrackEventListTableModel oldTrackModel = getModel();
\r
782 int index = tlsm.getMinSelectionIndex();
\r
784 text = String.format(TITLE+" - track No.%d", index);
\r
787 TrackEventListTableModel newTrackModel = sequenceModel.getSelectedTrackModel();
\r
788 if( oldTrackModel == newTrackModel )
\r
790 if( newTrackModel == null ) {
\r
791 newTrackModel = getModel().sequenceTrackListTableModel.sequenceListTableModel.emptyEventListTableModel;
\r
792 queryJumpEventAction.setEnabled(false);
\r
793 queryAddEventAction.setEnabled(false);
\r
795 queryPasteEventAction.setEnabled(false);
\r
796 copyEventAction.setEnabled(false);
\r
797 deleteEventAction.setEnabled(false);
\r
798 cutEventAction.setEnabled(false);
\r
801 queryJumpEventAction.setEnabled(true);
\r
802 queryAddEventAction.setEnabled(true);
\r
804 oldTrackModel.eventSelectionModel.removeListSelectionListener(eventSelectionListener);
\r
805 setModel(newTrackModel);
\r
806 setSelectionModel(newTrackModel.eventSelectionModel);
\r
807 newTrackModel.eventSelectionModel.addListSelectionListener(eventSelectionListener);
\r
813 private EventSelectionListener eventSelectionListener;
\r
815 * 選択イベントの変更に反応するリスナー
\r
817 private class EventSelectionListener implements ListSelectionListener {
\r
818 public EventSelectionListener() {
\r
819 getModel().eventSelectionModel.addListSelectionListener(this);
\r
822 public void valueChanged(ListSelectionEvent e) {
\r
823 if( e.getValueIsAdjusting() )
\r
825 if( getSelectionModel().isSelectionEmpty() ) {
\r
826 queryPasteEventAction.setEnabled(false);
\r
827 copyEventAction.setEnabled(false);
\r
828 deleteEventAction.setEnabled(false);
\r
829 cutEventAction.setEnabled(false);
\r
832 copyEventAction.setEnabled(true);
\r
833 deleteEventAction.setEnabled(true);
\r
834 cutEventAction.setEnabled(true);
\r
835 TrackEventListTableModel trackModel = getModel();
\r
836 int minIndex = getSelectionModel().getMinSelectionIndex();
\r
837 MidiEvent midiEvent = trackModel.getMidiEvent(minIndex);
\r
838 if( midiEvent != null ) {
\r
839 MidiMessage msg = midiEvent.getMessage();
\r
840 if( msg instanceof ShortMessage ) {
\r
841 ShortMessage sm = (ShortMessage)msg;
\r
842 int cmd = sm.getCommand();
\r
843 if( cmd == 0x80 || cmd == 0x90 || cmd == 0xA0 ) {
\r
844 // ノート番号を持つ場合、音を鳴らす。
\r
845 MidiChannel outMidiChannels[] = virtualMidiDevice.getChannels();
\r
846 int ch = sm.getChannel();
\r
847 int note = sm.getData1();
\r
848 int vel = sm.getData2();
\r
849 outMidiChannels[ch].noteOn(note, vel);
\r
850 outMidiChannels[ch].noteOff(note, vel);
\r
854 if( pairNoteOnOffModel.isSelected() ) {
\r
855 int maxIndex = getSelectionModel().getMaxSelectionIndex();
\r
857 for( int i=minIndex; i<=maxIndex; i++ ) {
\r
858 if( ! getSelectionModel().isSelectedIndex(i) ) continue;
\r
859 partnerIndex = trackModel.getIndexOfPartnerFor(i);
\r
860 if( partnerIndex >= 0 && ! getSelectionModel().isSelectedIndex(partnerIndex) )
\r
861 getSelectionModel().addSelectionInterval(partnerIndex, partnerIndex);
\r
868 * Pair noteON/OFF トグルボタンモデル
\r
870 private JToggleButton.ToggleButtonModel
\r
871 pairNoteOnOffModel = new JToggleButton.ToggleButtonModel() {
\r
874 new ItemListener() {
\r
875 public void itemStateChanged(ItemEvent e) {
\r
876 eventDialog.midiMessageForm.durationForm.setEnabled(isSelected());
\r
883 private class EventEditContext {
\r
887 private TrackEventListTableModel trackModel;
\r
891 private TickPositionModel tickPositionModel = new TickPositionModel();
\r
895 private MidiEvent selectedMidiEvent = null;
\r
899 private int selectedIndex = -1;
\r
903 private long currentTick = 0;
\r
905 * 上書きして削除対象にする変更前イベント(null可)
\r
907 private MidiEvent[] midiEventsToBeOverwritten;
\r
909 * 選択したイベントを入力ダイアログなどに反映します。
\r
910 * @param model 対象データモデル
\r
912 private void setSelectedEvent(TrackEventListTableModel trackModel) {
\r
913 this.trackModel = trackModel;
\r
914 SequenceTrackListTableModel sequenceTableModel = trackModel.sequenceTrackListTableModel;
\r
915 int ppq = sequenceTableModel.getSequence().getResolution();
\r
916 eventDialog.midiMessageForm.durationForm.setPPQ(ppq);
\r
917 tickPositionModel.setSequenceIndex(sequenceTableModel.getSequenceTickIndex());
\r
919 selectedIndex = trackModel.eventSelectionModel.getMinSelectionIndex();
\r
920 selectedMidiEvent = selectedIndex < 0 ? null : trackModel.getMidiEvent(selectedIndex);
\r
921 currentTick = selectedMidiEvent == null ? 0 : selectedMidiEvent.getTick();
\r
922 tickPositionModel.setTickPosition(currentTick);
\r
924 public void setupForEdit(TrackEventListTableModel trackModel) {
\r
925 MidiEvent partnerEvent = null;
\r
926 eventDialog.midiMessageForm.setMessage(
\r
927 selectedMidiEvent.getMessage(),
\r
928 trackModel.sequenceTrackListTableModel.charset
\r
930 if( eventDialog.midiMessageForm.isNote() ) {
\r
931 int partnerIndex = trackModel.getIndexOfPartnerFor(selectedIndex);
\r
932 if( partnerIndex < 0 ) {
\r
933 eventDialog.midiMessageForm.durationForm.setDuration(0);
\r
936 partnerEvent = trackModel.getMidiEvent(partnerIndex);
\r
937 long partnerTick = partnerEvent.getTick();
\r
938 long duration = currentTick > partnerTick ?
\r
939 currentTick - partnerTick : partnerTick - currentTick ;
\r
940 eventDialog.midiMessageForm.durationForm.setDuration((int)duration);
\r
943 if(partnerEvent == null)
\r
944 midiEventsToBeOverwritten = new MidiEvent[] {selectedMidiEvent};
\r
946 midiEventsToBeOverwritten = new MidiEvent[] {selectedMidiEvent, partnerEvent};
\r
948 private Action jumpEventAction = new AbstractAction() {
\r
949 { putValue(NAME,"Jump"); }
\r
950 public void actionPerformed(ActionEvent e) {
\r
951 long tick = tickPositionModel.getTickPosition();
\r
952 scrollToEventAt(tick);
\r
953 eventDialog.setVisible(false);
\r
957 private Action pasteEventAction = new AbstractAction() {
\r
958 { putValue(NAME,"Paste"); }
\r
959 public void actionPerformed(ActionEvent e) {
\r
960 long tick = tickPositionModel.getTickPosition();
\r
961 clipBoard.paste(trackModel, tick);
\r
962 scrollToEventAt(tick);
\r
963 // ペーストで曲の長さが変わったことをプレイリストに通知
\r
964 SequenceTrackListTableModel seqModel = trackModel.sequenceTrackListTableModel;
\r
965 seqModel.sequenceListTableModel.fireSequenceModified(seqModel);
\r
966 eventDialog.setVisible(false);
\r
970 private boolean applyEvent() {
\r
971 long tick = tickPositionModel.getTickPosition();
\r
972 MidiMessageForm form = eventDialog.midiMessageForm;
\r
973 SequenceTrackListTableModel seqModel = trackModel.sequenceTrackListTableModel;
\r
974 MidiEvent newMidiEvent = new MidiEvent(form.getMessage(seqModel.charset), tick);
\r
975 if( midiEventsToBeOverwritten != null ) {
\r
976 // 上書き消去するための選択済イベントがあった場合
\r
977 trackModel.removeMidiEvents(midiEventsToBeOverwritten);
\r
979 if( ! trackModel.addMidiEvent(newMidiEvent) ) {
\r
980 System.out.println("addMidiEvent failure");
\r
983 if(pairNoteOnOffModel.isSelected() && form.isNote()) {
\r
984 ShortMessage sm = form.createPartnerMessage();
\r
986 scrollToEventAt( tick );
\r
988 int duration = form.durationForm.getDuration();
\r
989 if( form.isNote(false) ) {
\r
990 duration = -duration;
\r
992 long partnerTick = tick + (long)duration;
\r
993 if( partnerTick < 0L ) partnerTick = 0L;
\r
994 MidiEvent partner = new MidiEvent((MidiMessage)sm, partnerTick);
\r
995 if( ! trackModel.addMidiEvent(partner) ) {
\r
996 System.out.println("addMidiEvent failure (note on/off partner message)");
\r
998 scrollToEventAt(partnerTick > tick ? partnerTick : tick);
\r
1001 seqModel.sequenceListTableModel.fireSequenceModified(seqModel);
\r
1002 eventDialog.setVisible(false);
\r
1006 private EventEditContext editContext = new EventEditContext();
\r
1008 * 指定のTick位置へジャンプするアクション
\r
1010 Action queryJumpEventAction = new AbstractAction() {
\r
1012 putValue(NAME,"Jump to ...");
\r
1013 setEnabled(false);
\r
1015 public void actionPerformed(ActionEvent e) {
\r
1016 editContext.setSelectedEvent(getModel());
\r
1017 eventDialog.openTickForm("Jump selection to", editContext.jumpEventAction);
\r
1021 * 新しいイベントの追加を行うアクション
\r
1023 Action queryAddEventAction = new AbstractAction() {
\r
1025 putValue(NAME,"New");
\r
1026 setEnabled(false);
\r
1028 public void actionPerformed(ActionEvent e) {
\r
1029 TrackEventListTableModel model = getModel();
\r
1030 editContext.setSelectedEvent(model);
\r
1031 editContext.midiEventsToBeOverwritten = null;
\r
1032 eventDialog.openEventForm(
\r
1034 eventCellEditor.applyEventAction,
\r
1035 model.getChannel()
\r
1040 * MIDIイベントのコピー&ペーストを行うためのクリップボード
\r
1042 private class LocalClipBoard {
\r
1043 private MidiEvent copiedEventsToPaste[];
\r
1044 private int copiedEventsPPQ = 0;
\r
1045 public void copy(TrackEventListTableModel model, boolean withRemove) {
\r
1046 copiedEventsToPaste = model.getSelectedMidiEvents();
\r
1047 copiedEventsPPQ = model.sequenceTrackListTableModel.getSequence().getResolution();
\r
1048 if( withRemove ) model.removeMidiEvents(copiedEventsToPaste);
\r
1049 boolean en = (copiedEventsToPaste != null && copiedEventsToPaste.length > 0);
\r
1050 queryPasteEventAction.setEnabled(en);
\r
1052 public void cut(TrackEventListTableModel model) {copy(model,true);}
\r
1053 public void copy(TrackEventListTableModel model){copy(model,false);}
\r
1054 public void paste(TrackEventListTableModel model, long tick) {
\r
1055 model.addMidiEvents(copiedEventsToPaste, tick, copiedEventsPPQ);
\r
1058 private LocalClipBoard clipBoard = new LocalClipBoard();
\r
1060 * 指定のTick位置へ貼り付けるアクション
\r
1062 Action queryPasteEventAction = new AbstractAction() {
\r
1064 putValue(NAME,"Paste to ...");
\r
1065 setEnabled(false);
\r
1067 public void actionPerformed(ActionEvent e) {
\r
1068 editContext.setSelectedEvent(getModel());
\r
1069 eventDialog.openTickForm("Paste to", editContext.pasteEventAction);
\r
1075 public Action cutEventAction = new AbstractAction("Cut") {
\r
1077 setEnabled(false);
\r
1080 public void actionPerformed(ActionEvent e) {
\r
1081 TrackEventListTableModel model = getModel();
\r
1082 if( ! confirm("Do you want to cut selected event ?\n選択したMIDIイベントを切り取りますか?"))
\r
1084 clipBoard.cut(model);
\r
1090 public Action copyEventAction = new AbstractAction("Copy") {
\r
1092 setEnabled(false);
\r
1095 public void actionPerformed(ActionEvent e) {
\r
1096 clipBoard.copy(getModel());
\r
1102 public Action deleteEventAction = new AbstractAction("Delete", deleteIcon) {
\r
1104 setEnabled(false);
\r
1107 public void actionPerformed(ActionEvent e) {
\r
1108 TrackEventListTableModel model = getModel();
\r
1109 if( ! confirm("Do you want to delete selected event ?\n選択したMIDIイベントを削除しますか?"))
\r
1111 model.removeSelectedMidiEvents();
\r
1115 * MIDIイベント表のセルエディタ
\r
1117 private MidiEventCellEditor eventCellEditor;
\r
1119 * MIDIイベント表のセルエディタ
\r
1121 class MidiEventCellEditor extends AbstractCellEditor implements TableCellEditor {
\r
1123 * MIDIイベントセルエディタを構築します。
\r
1125 public MidiEventCellEditor() {
\r
1126 eventDialog.midiMessageForm.setOutputMidiChannels(virtualMidiDevice.getChannels());
\r
1127 eventDialog.tickPositionInputForm.setModel(editContext.tickPositionModel);
\r
1128 int index = TrackEventListTableModel.Column.MESSAGE.ordinal();
\r
1129 getColumnModel().getColumn(index).setCellEditor(this);
\r
1132 * セルをダブルクリックしないと編集できないようにします。
\r
1133 * @param e イベント(マウスイベント)
\r
1134 * @return 編集可能になったらtrue
\r
1137 public boolean isCellEditable(EventObject e) {
\r
1138 if( ! (e instanceof MouseEvent) )
\r
1139 return super.isCellEditable(e);
\r
1140 return ((MouseEvent)e).getClickCount() == 2;
\r
1143 public Object getCellEditorValue() { return null; }
\r
1145 * MIDIメッセージダイアログが閉じたときにセル編集を中止するリスナー
\r
1147 private ComponentListener dialogComponentListener = new ComponentAdapter() {
\r
1149 public void componentHidden(ComponentEvent e) {
\r
1150 fireEditingCanceled();
\r
1152 eventDialog.removeComponentListener(this);
\r
1156 * 既存イベントを編集するアクション
\r
1158 private Action editEventAction = new AbstractAction() {
\r
1159 public void actionPerformed(ActionEvent e) {
\r
1160 TrackEventListTableModel model = getModel();
\r
1161 editContext.setSelectedEvent(model);
\r
1162 if( editContext.selectedMidiEvent == null )
\r
1164 editContext.setupForEdit(model);
\r
1165 eventDialog.addComponentListener(dialogComponentListener);
\r
1166 eventDialog.openEventForm("Change MIDI event", applyEventAction);
\r
1172 private JButton editEventButton = new JButton(editEventAction){{
\r
1173 setHorizontalAlignment(JButton.LEFT);
\r
1176 public Component getTableCellEditorComponent(
\r
1177 JTable table, Object value, boolean isSelected, int row, int column
\r
1179 editEventButton.setText(value.toString());
\r
1180 return editEventButton;
\r
1183 * 入力したイベントを反映するアクション
\r
1185 private Action applyEventAction = new AbstractAction() {
\r
1187 putValue(NAME,"OK");
\r
1189 public void actionPerformed(ActionEvent e) {
\r
1190 if( editContext.applyEvent() ) fireEditingStopped();
\r
1195 * スクロール可能なMIDIイベントテーブルビュー
\r
1197 private JScrollPane scrollPane = new JScrollPane(this);
\r
1199 * 指定の MIDI tick のイベントへスクロールします。
\r
1200 * @param tick MIDI tick
\r
1202 public void scrollToEventAt(long tick) {
\r
1203 int index = getModel().tickToIndex(tick);
\r
1204 scrollPane.getVerticalScrollBar().setValue(index * getRowHeight());
\r
1205 getSelectionModel().setSelectionInterval(index, index);
\r
1210 * 新しい {@link MidiEditor} を構築します。
\r
1211 * @param deviceModelList MIDIデバイスモデルリスト
\r
1213 public MidiEditor(MidiSequencerModel sequencerModel) {
\r
1214 // テーブルモデルとテーブルビューの生成
\r
1215 sequenceListTable = new SequenceListTable(
\r
1216 new PlaylistTableModel(sequencerModel)
\r
1218 trackListTable = new TrackListTable(
\r
1219 new SequenceTrackListTableModel(sequenceListTable.getModel(), null, null)
\r
1221 eventListTable = new EventListTable(
\r
1222 new TrackEventListTableModel(trackListTable.getModel(), null)
\r
1225 setTitle("MIDI Editor/Playlist - MIDI Chord Helper");
\r
1226 setBounds( 150, 200, 900, 500 );
\r
1227 setLayout(new FlowLayout());
\r
1228 new DropTarget(this, DnDConstants.ACTION_COPY_OR_MOVE, this, true);
\r
1229 JPanel playlistPanel = new JPanel() {{
\r
1230 JPanel playlistOperationPanel = new JPanel() {{
\r
1231 setLayout(new BoxLayout(this, BoxLayout.LINE_AXIS));
\r
1232 add(Box.createRigidArea(new Dimension(10, 0)));
\r
1233 add(new JButton(newSequenceDialog.openAction) {{
\r
1234 setMargin(ZERO_INSETS);
\r
1236 if( sequenceListTable.midiFileChooser != null ) {
\r
1237 add( Box.createRigidArea(new Dimension(5, 0)) );
\r
1239 sequenceListTable.midiFileChooser.openMidiFileAction
\r
1241 setMargin(ZERO_INSETS);
\r
1244 if(sequenceListTable.base64EncodeAction != null) {
\r
1245 add(Box.createRigidArea(new Dimension(5, 0)));
\r
1246 add(new JButton(sequenceListTable.base64EncodeAction) {{
\r
1247 setMargin(ZERO_INSETS);
\r
1250 add(Box.createRigidArea(new Dimension(5, 0)));
\r
1251 PlaylistTableModel playlistTableModel = sequenceListTable.getModel();
\r
1252 add(new JButton(playlistTableModel.moveToTopAction) {{
\r
1253 setMargin(ZERO_INSETS);
\r
1255 add(Box.createRigidArea(new Dimension(5, 0)));
\r
1256 add(new JButton(playlistTableModel.moveToBottomAction) {{
\r
1257 setMargin(ZERO_INSETS);
\r
1259 if( sequenceListTable.midiFileChooser != null ) {
\r
1260 add(Box.createRigidArea(new Dimension(5, 0)));
\r
1262 sequenceListTable.midiFileChooser.saveMidiFileAction
\r
1264 setMargin(ZERO_INSETS);
\r
1267 add( Box.createRigidArea(new Dimension(5, 0)) );
\r
1268 add(new JButton(sequenceListTable.deleteSequenceAction) {{
\r
1269 setMargin(ZERO_INSETS);
\r
1271 add( Box.createRigidArea(new Dimension(5, 0)) );
\r
1272 add(new SequencerSpeedSlider(
\r
1273 playlistTableModel.sequencerModel.speedSliderModel
\r
1275 add( Box.createRigidArea(new Dimension(5, 0)) );
\r
1276 add(new JPanel() {{
\r
1277 add(new JLabel("SyncMode:"));
\r
1278 add(new JLabel("Master"));
\r
1279 add(new JComboBox<Sequencer.SyncMode>(
\r
1280 sequenceListTable.getModel().sequencerModel.masterSyncModeModel
\r
1282 add(new JLabel("Slave"));
\r
1283 add(new JComboBox<Sequencer.SyncMode>(
\r
1284 sequenceListTable.getModel().sequencerModel.slaveSyncModeModel
\r
1288 setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
\r
1289 add(new JScrollPane(sequenceListTable));
\r
1290 add(Box.createRigidArea(new Dimension(0, 10)));
\r
1291 add(playlistOperationPanel);
\r
1292 add(Box.createRigidArea(new Dimension(0, 10)));
\r
1294 JPanel trackListPanel = new JPanel() {{
\r
1295 JPanel trackListOperationPanel = new JPanel() {{
\r
1296 add(new JButton(trackListTable.addTrackAction) {{
\r
1297 setMargin(ZERO_INSETS);
\r
1299 add(new JButton(trackListTable.deleteTrackAction) {{
\r
1300 setMargin(ZERO_INSETS);
\r
1303 setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
\r
1304 add(trackListTable.titleLabel);
\r
1305 add(Box.createRigidArea(new Dimension(0, 5)));
\r
1306 add(new JScrollPane(trackListTable));
\r
1307 add(Box.createRigidArea(new Dimension(0, 5)));
\r
1308 add(trackListOperationPanel);
\r
1310 JPanel eventListPanel = new JPanel() {{
\r
1311 JPanel eventListOperationPanel = new JPanel() {{
\r
1312 add(new JCheckBox("Pair NoteON/OFF") {{
\r
1313 setModel(eventListTable.pairNoteOnOffModel);
\r
1314 setToolTipText("NoteON/OFFをペアで同時選択する");
\r
1316 add(new JButton(eventListTable.queryJumpEventAction) {{
\r
1317 setMargin(ZERO_INSETS);
\r
1319 add(new JButton(eventListTable.queryAddEventAction) {{
\r
1320 setMargin(ZERO_INSETS);
\r
1322 add(new JButton(eventListTable.copyEventAction) {{
\r
1323 setMargin(ZERO_INSETS);
\r
1325 add(new JButton(eventListTable.cutEventAction) {{
\r
1326 setMargin(ZERO_INSETS);
\r
1328 add(new JButton(eventListTable.queryPasteEventAction) {{
\r
1329 setMargin(ZERO_INSETS);
\r
1331 add(new JButton(eventListTable.deleteEventAction) {{
\r
1332 setMargin(ZERO_INSETS);
\r
1335 setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
\r
1336 add(eventListTable.titleLabel);
\r
1337 add(eventListTable.scrollPane);
\r
1338 add(eventListOperationPanel);
\r
1340 Container cp = getContentPane();
\r
1341 cp.setLayout(new BoxLayout(cp, BoxLayout.Y_AXIS));
\r
1342 cp.add(Box.createVerticalStrut(2));
\r
1344 new JSplitPane(JSplitPane.VERTICAL_SPLIT, playlistPanel,
\r
1345 new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, trackListPanel, eventListPanel) {{
\r
1346 setDividerLocation(300);
\r
1349 setDividerLocation(160);
\r