OSDN Git Service

リファクタリング(MIDI editor周り)
authorAkiyoshi Kamide <kamide@yk.rim.or.jp>
Wed, 19 Apr 2017 15:38:09 +0000 (00:38 +0900)
committerAkiyoshi Kamide <kamide@yk.rim.or.jp>
Wed, 19 Apr 2017 15:38:09 +0000 (00:38 +0900)
src/camidion/chordhelper/AboutMessagePane.java
src/camidion/chordhelper/ChordHelperApplet.java
src/camidion/chordhelper/MidiChordHelper.java
src/camidion/chordhelper/midieditor/MidiEventTable.java [new file with mode: 0644]
src/camidion/chordhelper/midieditor/MidiSequenceEditorDialog.java
src/camidion/chordhelper/midieditor/PlaylistTable.java [moved from src/camidion/chordhelper/midieditor/SequenceListTable.java with 89% similarity]

index 0598fff..a3973f1 100644 (file)
@@ -9,6 +9,7 @@ import java.net.URISyntaxException;
 import javax.swing.AbstractAction;
 import javax.swing.Action;
 import javax.swing.ImageIcon;
+import javax.swing.JComponent;
 import javax.swing.JEditorPane;
 import javax.swing.JOptionPane;
 import javax.swing.event.HyperlinkEvent;
@@ -36,8 +37,10 @@ public class AboutMessagePane extends JEditorPane {
                        }
                        @Override
                        public void actionPerformed(ActionEvent e) {
+                               JComponent c = (JComponent)e.getSource();
                                JOptionPane.showMessageDialog(
-                                       null, AboutMessagePane.this, getValue(NAME).toString(),
+                                       c.getRootPane(),
+                                       AboutMessagePane.this, getValue(NAME).toString(),
                                        JOptionPane.INFORMATION_MESSAGE, imageIcon
                                );
                        }
index 1fe9068..b4fe592 100644 (file)
@@ -140,23 +140,16 @@ public class ChordHelperApplet extends JApplet {
                return d.addToPlaylist();
        }
        /**
-        * プレイリスト上で現在選択されているMIDIシーケンスを、
-        * シーケンサへロードして再生します。
-        * @throws InvalidMidiDataException {@link Sequencer#setSequence(Sequence)} を参照
-        * @throws IllegalStateException MIDIシーケンサデバイスが閉じている場合
+        * プレイリスト上で現在選択されているMIDIシーケンスをシーケンサへロードして再生します。
         */
-       public void play() throws InvalidMidiDataException {
+       public void play() {
                play(playlistModel.sequenceListSelectionModel.getMinSelectionIndex());
        }
        /**
         * 指定されたインデックス値が示すプレイリスト上のMIDIシーケンスをシーケンサへロードして再生します。
         * @param index インデックス値(0から始まる)
-        * @throws InvalidMidiDataException {@link Sequencer#setSequence(Sequence)} を参照
-        * @throws IllegalStateException MIDIシーケンサデバイスが閉じている場合
         */
-       public void play(int index) throws InvalidMidiDataException {
-               playlistModel.play(index);
-       }
+       public void play(int index) { midiEditor.play(index); }
        /**
         * シーケンサが実行中かどうかを返します。
         * {@link Sequencer#isRunning()} の戻り値をそのまま返します。
@@ -272,7 +265,7 @@ public class ChordHelperApplet extends JApplet {
         */
        public static class VersionInfo {
                public static final String NAME = "MIDI Chord Helper";
-               public static final String VERSION = "Ver.20170417.1";
+               public static final String VERSION = "Ver.20170419.1";
                public static final String COPYRIGHT = "Copyright (C) 2004-2017";
                public static final String AUTHER = "@きよし - Akiyoshi Kamide";
                public static final String URL = "http://www.yk.rim.or.jp/~kamide/music/chordhelper/";
@@ -381,7 +374,7 @@ public class ChordHelperApplet extends JApplet {
                setTransferHandler(midiEditor.transferHandler);
                //
                // MIDIエディタのイベントダイアログを、ピアノ鍵盤のイベント送出ダイアログと共用
-               keyboardPanel.setEventDialog(midiEditor.eventDialog);
+               keyboardPanel.setEventDialog(midiEditor.eventListTable.eventDialog);
                //
                // 歌詞表示/コード入力フィールド
                (lyricDisplay = new ChordTextField(sequencerModel)).addActionListener(
index bd807cf..75810c4 100644 (file)
@@ -47,7 +47,7 @@ public class MidiChordHelper extends JFrame implements AppletStub, AppletContext
                SwingUtilities.invokeLater(()->new MidiChordHelper(fileList));
        }
        private static boolean confirmBeforeExit() {
-               String message = "MIDI file not saved, exit anyway ?\n"+
+               String message = "MIDI file not saved - Really exit ?\n"+
                                "MIDIファイルが保存されていません。終了してよろしいですか?";
                return JOptionPane.showConfirmDialog(
                                null, message, ChordHelperApplet.VersionInfo.NAME,
diff --git a/src/camidion/chordhelper/midieditor/MidiEventTable.java b/src/camidion/chordhelper/midieditor/MidiEventTable.java
new file mode 100644 (file)
index 0000000..3466733
--- /dev/null
@@ -0,0 +1,515 @@
+package camidion.chordhelper.midieditor;
+
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+import java.awt.event.ComponentAdapter;
+import java.awt.event.ComponentEvent;
+import java.awt.event.ComponentListener;
+import java.awt.event.MouseEvent;
+import java.util.Arrays;
+import java.util.EventObject;
+
+import javax.sound.midi.MidiChannel;
+import javax.sound.midi.MidiEvent;
+import javax.sound.midi.MidiMessage;
+import javax.sound.midi.ShortMessage;
+import javax.swing.AbstractAction;
+import javax.swing.AbstractCellEditor;
+import javax.swing.Action;
+import javax.swing.JButton;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JScrollPane;
+import javax.swing.JTable;
+import javax.swing.JToggleButton;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+import javax.swing.table.TableCellEditor;
+import javax.swing.table.TableColumnModel;
+import javax.swing.table.TableModel;
+
+import camidion.chordhelper.ChordHelperApplet;
+import camidion.chordhelper.mididevice.VirtualMidiDevice;
+
+/**
+ * MIDIイベントリストテーブルビュー(選択中のトラックの中身)
+ */
+public class MidiEventTable extends JTable {
+       /**
+        * 操作音を鳴らすMIDI出力デバイス
+        */
+       private VirtualMidiDevice outputMidiDevice;
+       /**
+        * MIDIイベント入力ダイアログ(イベント入力とイベント送出で共用)
+        */
+       public MidiEventDialog eventDialog = new MidiEventDialog();
+       /**
+        * 新しいイベントリストテーブルを構築します。
+        * <p>データモデルとして一つのトラックのイベントリストを指定できます。
+        * トラックを切り替えたいときは {@link #setModel(TableModel)}
+        * でデータモデルを異なるトラックのものに切り替えます。
+        * </p>
+        *
+        * @param model トラック(イベントリスト)データモデル
+        * @param outputMidiDevice 操作音出力先MIDIデバイス
+        */
+       public MidiEventTable(TrackEventListTableModel model, VirtualMidiDevice outputMidiDevice) {
+               super(model, null, model.getSelectionModel());
+               this.outputMidiDevice = outputMidiDevice;
+               //
+               // 列モデルにセルエディタを設定
+               eventCellEditor = new MidiEventCellEditor();
+               setAutoCreateColumnsFromModel(false);
+               //
+               eventSelectionListener = new EventSelectionListener();
+               titleLabel = new TitleLabel();
+               //
+               TableColumnModel cm = getColumnModel();
+               Arrays.stream(TrackEventListTableModel.Column.values()).forEach(c->
+                       cm.getColumn(c.ordinal()).setPreferredWidth(c.preferredWidth)
+               );
+       }
+       /**
+        * このテーブルビューが表示するデータを提供する
+        * トラック(イベントリスト)データモデルを返します。
+        * @return トラック(イベントリスト)データモデル
+        */
+       @Override
+       public TrackEventListTableModel getModel() {
+               return (TrackEventListTableModel) super.getModel();
+       }
+       public void updateTo(SequenceTrackListTableModel sequenceModel) {
+               titleLabel.updateTrackNumber(sequenceModel.getSelectionModel().getMinSelectionIndex());
+               TrackEventListTableModel oldTrackModel = getModel();
+               TrackEventListTableModel newTrackModel = sequenceModel.getSelectedTrackModel();
+               if( oldTrackModel == newTrackModel )
+                       return;
+               if( newTrackModel == null ) {
+                       newTrackModel = getModel().getParent().getParent().emptyEventListTableModel;
+                       queryJumpEventAction.setEnabled(false);
+                       queryAddEventAction.setEnabled(false);
+
+                       queryPasteEventAction.setEnabled(false);
+                       copyEventAction.setEnabled(false);
+                       deleteEventAction.setEnabled(false);
+                       cutEventAction.setEnabled(false);
+               }
+               else {
+                       queryJumpEventAction.setEnabled(true);
+                       queryAddEventAction.setEnabled(true);
+               }
+               oldTrackModel.getSelectionModel().removeListSelectionListener(eventSelectionListener);
+               setModel(newTrackModel);
+               setSelectionModel(newTrackModel.getSelectionModel());
+               newTrackModel.getSelectionModel().addListSelectionListener(eventSelectionListener);
+       }
+       /**
+        * タイトルラベル
+        */
+       TitleLabel titleLabel;
+       /**
+        * 親テーブルの選択トラックの変更に反応する
+        * トラック番号つきタイトルラベル
+        */
+       private class TitleLabel extends JLabel {
+               private static final String TITLE = "MIDI Events";
+               private TitleLabel() { super(TITLE); }
+               private void updateTrackNumber(int index) {
+                       String text = TITLE;
+                       if( index >= 0 ) text = String.format(TITLE+" - track #%d", index);
+                       setText(text);
+               }
+       }
+
+       /**
+        * イベント選択リスナー
+        */
+       private EventSelectionListener eventSelectionListener;
+       /**
+        * 選択イベントの変更に反応するリスナー
+        */
+       private class EventSelectionListener implements ListSelectionListener {
+               public EventSelectionListener() {
+                       getModel().getSelectionModel().addListSelectionListener(this);
+               }
+               @Override
+               public void valueChanged(ListSelectionEvent e) {
+                       if( e.getValueIsAdjusting() )
+                               return;
+                       if( getSelectionModel().isSelectionEmpty() ) {
+                               queryPasteEventAction.setEnabled(false);
+                               copyEventAction.setEnabled(false);
+                               deleteEventAction.setEnabled(false);
+                               cutEventAction.setEnabled(false);
+                       }
+                       else {
+                               copyEventAction.setEnabled(true);
+                               deleteEventAction.setEnabled(true);
+                               cutEventAction.setEnabled(true);
+                               TrackEventListTableModel trackModel = getModel();
+                               int minIndex = getSelectionModel().getMinSelectionIndex();
+                               MidiEvent midiEvent = trackModel.getMidiEvent(minIndex);
+                               if( midiEvent != null ) {
+                                       MidiMessage msg = midiEvent.getMessage();
+                                       if( msg instanceof ShortMessage ) {
+                                               ShortMessage sm = (ShortMessage)msg;
+                                               int cmd = sm.getCommand();
+                                               if( cmd == 0x80 || cmd == 0x90 || cmd == 0xA0 ) {
+                                                       // ノート番号を持つ場合、音を鳴らす。
+                                                       MidiChannel outMidiChannels[] = outputMidiDevice.getChannels();
+                                                       int ch = sm.getChannel();
+                                                       int note = sm.getData1();
+                                                       int vel = sm.getData2();
+                                                       outMidiChannels[ch].noteOn(note, vel);
+                                                       outMidiChannels[ch].noteOff(note, vel);
+                                               }
+                                       }
+                               }
+                               if( pairNoteOnOffModel.isSelected() ) {
+                                       int maxIndex = getSelectionModel().getMaxSelectionIndex();
+                                       int partnerIndex;
+                                       for( int i=minIndex; i<=maxIndex; i++ ) {
+                                               if( ! getSelectionModel().isSelectedIndex(i) ) continue;
+                                               partnerIndex = trackModel.getIndexOfPartnerFor(i);
+                                               if( partnerIndex >= 0 && ! getSelectionModel().isSelectedIndex(partnerIndex) )
+                                                       getSelectionModel().addSelectionInterval(partnerIndex, partnerIndex);
+                                       }
+                               }
+                       }
+               }
+       }
+       /**
+        * Pair noteON/OFF トグルボタンモデル
+        */
+       JToggleButton.ToggleButtonModel
+               pairNoteOnOffModel = new JToggleButton.ToggleButtonModel() {
+                       {
+                               addItemListener(e->eventDialog.midiMessageForm.durationForm.setEnabled(isSelected()));
+                               setSelected(true);
+                       }
+               };
+       private class EventEditContext {
+               /**
+                * 編集対象トラック
+                */
+               private TrackEventListTableModel trackModel;
+               /**
+                * tick位置入力モデル
+                */
+               private TickPositionModel tickPositionModel = new TickPositionModel();
+               /**
+                * 選択されたイベント
+                */
+               private MidiEvent selectedMidiEvent = null;
+               /**
+                * 選択されたイベントの場所
+                */
+               private int selectedIndex = -1;
+               /**
+                * 選択されたイベントのtick位置
+                */
+               private long currentTick = 0;
+               /**
+                * 上書きして削除対象にする変更前イベント(null可)
+                */
+               private MidiEvent[] midiEventsToBeOverwritten;
+               /**
+                * 選択したイベントを入力ダイアログなどに反映します。
+                * @param model 対象データモデル
+                */
+               private void setSelectedEvent(TrackEventListTableModel trackModel) {
+                       this.trackModel = trackModel;
+                       SequenceTrackListTableModel sequenceTableModel = trackModel.getParent();
+                       int ppq = sequenceTableModel.getSequence().getResolution();
+                       eventDialog.midiMessageForm.durationForm.setPPQ(ppq);
+                       tickPositionModel.setSequenceIndex(sequenceTableModel.getSequenceTickIndex());
+
+                       selectedIndex = trackModel.getSelectionModel().getMinSelectionIndex();
+                       selectedMidiEvent = selectedIndex < 0 ? null : trackModel.getMidiEvent(selectedIndex);
+                       currentTick = selectedMidiEvent == null ? 0 : selectedMidiEvent.getTick();
+                       tickPositionModel.setTickPosition(currentTick);
+               }
+               public void setupForEdit(TrackEventListTableModel trackModel) {
+                       MidiEvent partnerEvent = null;
+                       eventDialog.midiMessageForm.setMessage(
+                               selectedMidiEvent.getMessage(),
+                               trackModel.getParent().charset
+                       );
+                       if( eventDialog.midiMessageForm.isNote() ) {
+                               int partnerIndex = trackModel.getIndexOfPartnerFor(selectedIndex);
+                               if( partnerIndex < 0 ) {
+                                       eventDialog.midiMessageForm.durationForm.setDuration(0);
+                               }
+                               else {
+                                       partnerEvent = trackModel.getMidiEvent(partnerIndex);
+                                       long partnerTick = partnerEvent.getTick();
+                                       long duration = currentTick > partnerTick ?
+                                               currentTick - partnerTick : partnerTick - currentTick ;
+                                       eventDialog.midiMessageForm.durationForm.setDuration((int)duration);
+                               }
+                       }
+                       if(partnerEvent == null)
+                               midiEventsToBeOverwritten = new MidiEvent[] {selectedMidiEvent};
+                       else
+                               midiEventsToBeOverwritten = new MidiEvent[] {selectedMidiEvent, partnerEvent};
+               }
+               private Action jumpEventAction = new AbstractAction() {
+                       { putValue(NAME,"Jump"); }
+                       public void actionPerformed(ActionEvent e) {
+                               long tick = tickPositionModel.getTickPosition();
+                               scrollToEventAt(tick);
+                               eventDialog.setVisible(false);
+                               trackModel = null;
+                       }
+               };
+               private Action pasteEventAction = new AbstractAction() {
+                       { putValue(NAME,"Paste"); }
+                       public void actionPerformed(ActionEvent e) {
+                               long tick = tickPositionModel.getTickPosition();
+                               clipBoard.paste(trackModel, tick);
+                               scrollToEventAt(tick);
+                               // ペーストされたので変更フラグを立てる(曲の長さが変わるが、それも自動的にプレイリストに通知される)
+                               SequenceTrackListTableModel seqModel = trackModel.getParent();
+                               seqModel.setModified(true);
+                               eventDialog.setVisible(false);
+                               trackModel = null;
+                       }
+               };
+               private boolean applyEvent() {
+                       long tick = tickPositionModel.getTickPosition();
+                       MidiMessageForm form = eventDialog.midiMessageForm;
+                       SequenceTrackListTableModel seqModel = trackModel.getParent();
+                       MidiMessage msg = form.getMessage(seqModel.charset);
+                       if( msg == null ) {
+                               return false;
+                       }
+                       MidiEvent newMidiEvent = new MidiEvent(msg, tick);
+                       if( midiEventsToBeOverwritten != null ) {
+                               // 上書き消去するための選択済イベントがあった場合
+                               trackModel.removeMidiEvents(midiEventsToBeOverwritten);
+                       }
+                       if( ! trackModel.addMidiEvent(newMidiEvent) ) {
+                               System.out.println("addMidiEvent failure");
+                               return false;
+                       }
+                       if(pairNoteOnOffModel.isSelected() && form.isNote()) {
+                               ShortMessage sm = form.createPartnerMessage();
+                               if(sm == null)
+                                       scrollToEventAt( tick );
+                               else {
+                                       int duration = form.durationForm.getDuration();
+                                       if( form.isNote(false) ) {
+                                               duration = -duration;
+                                       }
+                                       long partnerTick = tick + (long)duration;
+                                       if( partnerTick < 0L ) partnerTick = 0L;
+                                       MidiEvent partner = new MidiEvent((MidiMessage)sm, partnerTick);
+                                       if( ! trackModel.addMidiEvent(partner) ) {
+                                               System.out.println("addMidiEvent failure (note on/off partner message)");
+                                       }
+                                       scrollToEventAt(partnerTick > tick ? partnerTick : tick);
+                               }
+                       }
+                       seqModel.setModified(true);
+                       eventDialog.setVisible(false);
+                       return true;
+               }
+       }
+       private EventEditContext editContext = new EventEditContext();
+       /**
+        * 指定のTick位置へジャンプするアクション
+        */
+       Action queryJumpEventAction = new AbstractAction() {
+               {
+                       putValue(NAME,"Jump to ...");
+                       setEnabled(false);
+               }
+               public void actionPerformed(ActionEvent e) {
+                       editContext.setSelectedEvent(getModel());
+                       eventDialog.openTickForm("Jump selection to", editContext.jumpEventAction);
+               }
+       };
+       /**
+        * 新しいイベントの追加を行うアクション
+        */
+       Action queryAddEventAction = new AbstractAction() {
+               {
+                       putValue(NAME,"New");
+                       setEnabled(false);
+               }
+               public void actionPerformed(ActionEvent e) {
+                       TrackEventListTableModel model = getModel();
+                       editContext.setSelectedEvent(model);
+                       editContext.midiEventsToBeOverwritten = null;
+                       eventDialog.openEventForm("New MIDI event", eventCellEditor.applyEventAction, model.getChannel());
+               }
+       };
+       /**
+        * MIDIイベントのコピー&ペーストを行うためのクリップボード
+        */
+       private class LocalClipBoard {
+               private MidiEvent copiedEventsToPaste[];
+               private int copiedEventsPPQ = 0;
+               public void copy(TrackEventListTableModel model, boolean withRemove) {
+                       copiedEventsToPaste = model.getSelectedMidiEvents();
+                       copiedEventsPPQ = model.getParent().getSequence().getResolution();
+                       if( withRemove ) model.removeMidiEvents(copiedEventsToPaste);
+                       boolean en = (copiedEventsToPaste != null && copiedEventsToPaste.length > 0);
+                       queryPasteEventAction.setEnabled(en);
+               }
+               public void cut(TrackEventListTableModel model) {copy(model,true);}
+               public void copy(TrackEventListTableModel model){copy(model,false);}
+               public void paste(TrackEventListTableModel model, long tick) {
+                       model.addMidiEvents(copiedEventsToPaste, tick, copiedEventsPPQ);
+               }
+       }
+       private LocalClipBoard clipBoard = new LocalClipBoard();
+       /**
+        * 指定のTick位置へ貼り付けるアクション
+        */
+       Action queryPasteEventAction = new AbstractAction() {
+               {
+                       putValue(NAME,"Paste to ...");
+                       setEnabled(false);
+               }
+               public void actionPerformed(ActionEvent e) {
+                       editContext.setSelectedEvent(getModel());
+                       eventDialog.openTickForm("Paste to", editContext.pasteEventAction);
+               }
+       };
+       /**
+        * イベントカットアクション
+        */
+       public Action cutEventAction = new AbstractAction("Cut") {
+               private static final String CONFIRM_MESSAGE =
+                               "Do you want to cut selected event ?\n選択したMIDIイベントを切り取りますか?";
+               { setEnabled(false); }
+               @Override
+               public void actionPerformed(ActionEvent event) {
+                       if( JOptionPane.showConfirmDialog(
+                                       ((JComponent)event.getSource()).getRootPane(),
+                                       CONFIRM_MESSAGE,
+                                       ChordHelperApplet.VersionInfo.NAME,
+                                       JOptionPane.YES_NO_OPTION,
+                                       JOptionPane.WARNING_MESSAGE) == JOptionPane.YES_OPTION
+                       ) clipBoard.cut(getModel());
+               }
+       };
+       /**
+        * イベントコピーアクション
+        */
+       public Action copyEventAction = new AbstractAction("Copy") {
+               { setEnabled(false); }
+               @Override
+               public void actionPerformed(ActionEvent e) { clipBoard.copy(getModel()); }
+       };
+       /**
+        * イベント削除アクション
+        */
+       public Action deleteEventAction = new AbstractAction("Delete", MidiSequenceEditorDialog.deleteIcon) {
+               private static final String CONFIRM_MESSAGE =
+                       "Do you want to delete selected event ?\n選択したMIDIイベントを削除しますか?";
+               { setEnabled(false); }
+               @Override
+               public void actionPerformed(ActionEvent event) {
+                       if( JOptionPane.showConfirmDialog(
+                                       ((JComponent)event.getSource()).getRootPane(),
+                                       CONFIRM_MESSAGE,
+                                       ChordHelperApplet.VersionInfo.NAME,
+                                       JOptionPane.YES_NO_OPTION,
+                                       JOptionPane.WARNING_MESSAGE) == JOptionPane.YES_OPTION
+                       ) getModel().removeSelectedMidiEvents();
+               }
+       };
+       /**
+        * MIDIイベント表のセルエディタ
+        */
+       private MidiEventCellEditor eventCellEditor;
+       /**
+        * MIDIイベント表のセルエディタ
+        */
+       class MidiEventCellEditor extends AbstractCellEditor implements TableCellEditor {
+               /**
+                * MIDIイベントセルエディタを構築します。
+                */
+               public MidiEventCellEditor() {
+                       eventDialog.midiMessageForm.setOutputMidiChannels(outputMidiDevice.getChannels());
+                       eventDialog.tickPositionInputForm.setModel(editContext.tickPositionModel);
+                       int index = TrackEventListTableModel.Column.MESSAGE.ordinal();
+                       getColumnModel().getColumn(index).setCellEditor(this);
+               }
+               /**
+                * セルをダブルクリックしないと編集できないようにします。
+                * @param e イベント(マウスイベント)
+                * @return 編集可能になったらtrue
+                */
+               @Override
+               public boolean isCellEditable(EventObject e) {
+                       return (e instanceof MouseEvent) ?
+                               ((MouseEvent)e).getClickCount() == 2 : super.isCellEditable(e);
+               }
+               @Override
+               public Object getCellEditorValue() { return null; }
+               /**
+                * MIDIメッセージダイアログが閉じたときにセル編集を中止するリスナー
+                */
+               private ComponentListener dialogComponentListener = new ComponentAdapter() {
+                       @Override
+                       public void componentHidden(ComponentEvent e) {
+                               fireEditingCanceled();
+                               // 用が済んだら当リスナーを除去
+                               eventDialog.removeComponentListener(this);
+                       }
+               };
+               /**
+                * 既存イベントを編集するアクション
+                */
+               private Action editEventAction = new AbstractAction() {
+                       public void actionPerformed(ActionEvent e) {
+                               TrackEventListTableModel model = getModel();
+                               editContext.setSelectedEvent(model);
+                               if( editContext.selectedMidiEvent == null )
+                                       return;
+                               editContext.setupForEdit(model);
+                               eventDialog.addComponentListener(dialogComponentListener);
+                               eventDialog.openEventForm("Change MIDI event", applyEventAction);
+                       }
+               };
+               /**
+                * イベント編集ボタン
+                */
+               private JButton editEventButton = new JButton(editEventAction){{
+                       setHorizontalAlignment(JButton.LEFT);
+               }};
+               @Override
+               public Component getTableCellEditorComponent(
+                       JTable table, Object value, boolean isSelected, int row, int column
+               ) {
+                       editEventButton.setText(value.toString());
+                       return editEventButton;
+               }
+               /**
+                * 入力したイベントを反映するアクション
+                */
+               private Action applyEventAction = new AbstractAction() {
+                       { putValue(NAME,"OK"); }
+                       public void actionPerformed(ActionEvent e) {
+                               if( editContext.applyEvent() ) fireEditingStopped();
+                       }
+               };
+       }
+       /**
+        * スクロール可能なMIDIイベントテーブルビュー
+        */
+       JScrollPane scrollPane = new JScrollPane(this);
+       /**
+        * 指定の MIDI tick のイベントへスクロールします。
+        * @param tick MIDI tick
+        */
+       public void scrollToEventAt(long tick) {
+               int index = getModel().tickToIndex(tick);
+               scrollPane.getVerticalScrollBar().setValue(index * getRowHeight());
+               getSelectionModel().setSelectionInterval(index, index);
+       }
+}
\ No newline at end of file
index 48a8388..14009f4 100644 (file)
@@ -1,27 +1,16 @@
 package camidion.chordhelper.midieditor;
 
-import java.awt.Component;
 import java.awt.Container;
 import java.awt.Dimension;
 import java.awt.FlowLayout;
 import java.awt.datatransfer.DataFlavor;
 import java.awt.event.ActionEvent;
-import java.awt.event.ComponentAdapter;
-import java.awt.event.ComponentEvent;
-import java.awt.event.ComponentListener;
-import java.awt.event.MouseEvent;
 import java.io.File;
 import java.util.Arrays;
-import java.util.EventObject;
 import java.util.List;
 
-import javax.sound.midi.MidiChannel;
-import javax.sound.midi.MidiEvent;
-import javax.sound.midi.MidiMessage;
 import javax.sound.midi.Sequencer;
-import javax.sound.midi.ShortMessage;
 import javax.swing.AbstractAction;
-import javax.swing.AbstractCellEditor;
 import javax.swing.Action;
 import javax.swing.Box;
 import javax.swing.BoxLayout;
@@ -30,6 +19,7 @@ import javax.swing.Icon;
 import javax.swing.JButton;
 import javax.swing.JCheckBox;
 import javax.swing.JComboBox;
+import javax.swing.JComponent;
 import javax.swing.JDialog;
 import javax.swing.JLabel;
 import javax.swing.JOptionPane;
@@ -37,7 +27,6 @@ import javax.swing.JPanel;
 import javax.swing.JScrollPane;
 import javax.swing.JSplitPane;
 import javax.swing.JTable;
-import javax.swing.JToggleButton;
 import javax.swing.ListSelectionModel;
 import javax.swing.TransferHandler;
 import javax.swing.border.EtchedBorder;
@@ -46,7 +35,6 @@ import javax.swing.event.ListSelectionListener;
 import javax.swing.event.TableModelEvent;
 import javax.swing.table.TableCellEditor;
 import javax.swing.table.TableColumnModel;
-import javax.swing.table.TableModel;
 
 import camidion.chordhelper.ButtonIcon;
 import camidion.chordhelper.ChordHelperApplet;
@@ -65,9 +53,7 @@ public class MidiSequenceEditorDialog extends JDialog {
        /**
         * このダイアログを開きます。表示されていなければ表示し、すでに表示されていたら最前面に移動します。
         */
-       public void open() {
-               if( isVisible() ) toFront(); else setVisible(true);
-       }
+       public void open() { if( isVisible() ) toFront(); else setVisible(true); }
        /**
         * このダイアログを表示するボタン用アクション
         */
@@ -125,7 +111,7 @@ public class MidiSequenceEditorDialog extends JDialog {
         */
        public void play(int index) {
                try {
-                       PlaylistTableModel playlist = sequenceListTable.getModel();
+                       PlaylistTableModel playlist = getPlaylistModel();
                        MidiSequencerModel sequencerModel = playlist.getSequencerModel();
                        if( sequencerModel.getSequencer().isRunning() ) { open(); return; }
                        if( index >= 0 ) playlist.play(index);
@@ -142,32 +128,24 @@ public class MidiSequenceEditorDialog extends JDialog {
        /**
         * プレイリストビュー(シーケンスリスト)
         */
-       public SequenceListTable sequenceListTable;
+       public PlaylistTable sequenceListTable;
        /**
         * MIDIトラックリストテーブルビュー(選択中のシーケンスの中身)
         */
-       private TrackListTable trackListTable;
+       private SequenceTrackListTable trackListTable;
        /**
         * MIDIイベントリストテーブルビュー(選択中のトラックの中身)
         */
-       private EventListTable eventListTable;
-       /**
-        * MIDIイベント入力ダイアログ(イベント入力とイベント送出で共用)
-        */
-       public MidiEventDialog eventDialog = new MidiEventDialog();
-       /**
-        * 操作音を鳴らすMIDI出力デバイス
-        */
-       private VirtualMidiDevice outputMidiDevice;
+       public MidiEventTable eventListTable;
        /**
         * シーケンス(トラックリスト)テーブルビュー
         */
-       public class TrackListTable extends JTable {
+       public class SequenceTrackListTable extends JTable {
                /**
                 * トラックリストテーブルビューを構築します。
                 * @param model シーケンス(トラックリスト)データモデル
                 */
-               public TrackListTable(SequenceTrackListTableModel model) {
+               public SequenceTrackListTable(SequenceTrackListTableModel model) {
                        super(model, null, model.getSelectionModel());
                        //
                        // 録音対象のMIDIチャンネルをコンボボックスで選択できるようにする
@@ -192,7 +170,7 @@ public class MidiSequenceEditorDialog extends JDialog {
                 */
                @Override
                public SequenceTrackListTableModel getModel() {
-                       return (SequenceTrackListTableModel) super.getModel();
+                       return (SequenceTrackListTableModel)dataModel;
                }
                /**
                 * タイトルラベル
@@ -243,9 +221,9 @@ public class MidiSequenceEditorDialog extends JDialog {
                        @Override
                        public void valueChanged(ListSelectionEvent e) {
                                if( e != null && e.getValueIsAdjusting() ) return;
-                               ListSelectionModel tlsm = getModel().getSelectionModel();
-                               deleteTrackAction.setEnabled(! tlsm.isSelectionEmpty());
-                               eventListTable.titleLabel.update(tlsm, getModel());
+                               ListSelectionModel selModel = getModel().getSelectionModel();
+                               deleteTrackAction.setEnabled(! selModel.isSelectionEmpty());
+                               eventListTable.updateTo(getModel());
                        }
                };
                /**
@@ -291,9 +269,9 @@ public class MidiSequenceEditorDialog extends JDialog {
                                setEnabled(false);
                        }
                        @Override
-                       public void actionPerformed(ActionEvent e) {
+                       public void actionPerformed(ActionEvent event) {
                                if( JOptionPane.showConfirmDialog(
-                                               TrackListTable.this.getRootPane(),
+                                               ((JComponent)event.getSource()).getRootPane(),
                                                CONFIRM_MESSAGE,
                                                ChordHelperApplet.VersionInfo.NAME,
                                                JOptionPane.YES_NO_OPTION,
@@ -304,488 +282,15 @@ public class MidiSequenceEditorDialog extends JDialog {
        }
 
        /**
-        * MIDIイベントリストテーブルビュー(選択中のトラックの中身)
-        */
-       public class EventListTable extends JTable {
-               /**
-                * 新しいイベントリストテーブルを構築します。
-                * <p>データモデルとして一つのトラックのイベントリストを指定できます。
-                * トラックを切り替えたいときは {@link #setModel(TableModel)}
-                * でデータモデルを異なるトラックのものに切り替えます。
-                * </p>
-                *
-                * @param model トラック(イベントリスト)データモデル
-                */
-               public EventListTable(TrackEventListTableModel model) {
-                       super(model, null, model.getSelectionModel());
-                       //
-                       // 列モデルにセルエディタを設定
-                       eventCellEditor = new MidiEventCellEditor();
-                       setAutoCreateColumnsFromModel(false);
-                       //
-                       eventSelectionListener = new EventSelectionListener();
-                       titleLabel = new TitleLabel();
-                       //
-                       TableColumnModel cm = getColumnModel();
-                       Arrays.stream(TrackEventListTableModel.Column.values()).forEach(c->
-                               cm.getColumn(c.ordinal()).setPreferredWidth(c.preferredWidth)
-                       );
-               }
-               /**
-                * このテーブルビューが表示するデータを提供する
-                * トラック(イベントリスト)データモデルを返します。
-                * @return トラック(イベントリスト)データモデル
-                */
-               @Override
-               public TrackEventListTableModel getModel() {
-                       return (TrackEventListTableModel) super.getModel();
-               }
-               /**
-                * タイトルラベル
-                */
-               TitleLabel titleLabel;
-               /**
-                * 親テーブルの選択トラックの変更に反応する
-                * トラック番号つきタイトルラベル
-                */
-               private class TitleLabel extends JLabel {
-                       private static final String TITLE = "MIDI Events";
-                       public TitleLabel() { super(TITLE); }
-                       public void update(ListSelectionModel tlsm, SequenceTrackListTableModel sequenceModel) {
-                               String text = TITLE;
-                               TrackEventListTableModel oldTrackModel = getModel();
-                               int index = tlsm.getMinSelectionIndex();
-                               if( index >= 0 ) {
-                                       text = String.format(TITLE+" - track #%d", index);
-                               }
-                               setText(text);
-                               TrackEventListTableModel newTrackModel = sequenceModel.getSelectedTrackModel();
-                               if( oldTrackModel == newTrackModel )
-                                       return;
-                               if( newTrackModel == null ) {
-                                       newTrackModel = getModel().getParent().getParent().emptyEventListTableModel;
-                                       queryJumpEventAction.setEnabled(false);
-                                       queryAddEventAction.setEnabled(false);
-
-                                       queryPasteEventAction.setEnabled(false);
-                                       copyEventAction.setEnabled(false);
-                                       deleteEventAction.setEnabled(false);
-                                       cutEventAction.setEnabled(false);
-                               }
-                               else {
-                                       queryJumpEventAction.setEnabled(true);
-                                       queryAddEventAction.setEnabled(true);
-                               }
-                               oldTrackModel.getSelectionModel().removeListSelectionListener(eventSelectionListener);
-                               setModel(newTrackModel);
-                               setSelectionModel(newTrackModel.getSelectionModel());
-                               newTrackModel.getSelectionModel().addListSelectionListener(eventSelectionListener);
-                       }
-               }
-
-               /**
-                * イベント選択リスナー
-                */
-               private EventSelectionListener eventSelectionListener;
-               /**
-                * 選択イベントの変更に反応するリスナー
-                */
-               private class EventSelectionListener implements ListSelectionListener {
-                       public EventSelectionListener() {
-                               getModel().getSelectionModel().addListSelectionListener(this);
-                       }
-                       @Override
-                       public void valueChanged(ListSelectionEvent e) {
-                               if( e.getValueIsAdjusting() )
-                                       return;
-                               if( getSelectionModel().isSelectionEmpty() ) {
-                                       queryPasteEventAction.setEnabled(false);
-                                       copyEventAction.setEnabled(false);
-                                       deleteEventAction.setEnabled(false);
-                                       cutEventAction.setEnabled(false);
-                               }
-                               else {
-                                       copyEventAction.setEnabled(true);
-                                       deleteEventAction.setEnabled(true);
-                                       cutEventAction.setEnabled(true);
-                                       TrackEventListTableModel trackModel = getModel();
-                                       int minIndex = getSelectionModel().getMinSelectionIndex();
-                                       MidiEvent midiEvent = trackModel.getMidiEvent(minIndex);
-                                       if( midiEvent != null ) {
-                                               MidiMessage msg = midiEvent.getMessage();
-                                               if( msg instanceof ShortMessage ) {
-                                                       ShortMessage sm = (ShortMessage)msg;
-                                                       int cmd = sm.getCommand();
-                                                       if( cmd == 0x80 || cmd == 0x90 || cmd == 0xA0 ) {
-                                                               // ノート番号を持つ場合、音を鳴らす。
-                                                               MidiChannel outMidiChannels[] = outputMidiDevice.getChannels();
-                                                               int ch = sm.getChannel();
-                                                               int note = sm.getData1();
-                                                               int vel = sm.getData2();
-                                                               outMidiChannels[ch].noteOn(note, vel);
-                                                               outMidiChannels[ch].noteOff(note, vel);
-                                                       }
-                                               }
-                                       }
-                                       if( pairNoteOnOffModel.isSelected() ) {
-                                               int maxIndex = getSelectionModel().getMaxSelectionIndex();
-                                               int partnerIndex;
-                                               for( int i=minIndex; i<=maxIndex; i++ ) {
-                                                       if( ! getSelectionModel().isSelectedIndex(i) ) continue;
-                                                       partnerIndex = trackModel.getIndexOfPartnerFor(i);
-                                                       if( partnerIndex >= 0 && ! getSelectionModel().isSelectedIndex(partnerIndex) )
-                                                               getSelectionModel().addSelectionInterval(partnerIndex, partnerIndex);
-                                               }
-                                       }
-                               }
-                       }
-               }
-               /**
-                * Pair noteON/OFF トグルボタンモデル
-                */
-               private JToggleButton.ToggleButtonModel
-                       pairNoteOnOffModel = new JToggleButton.ToggleButtonModel() {
-                               {
-                                       addItemListener(e->eventDialog.midiMessageForm.durationForm.setEnabled(isSelected()));
-                                       setSelected(true);
-                               }
-                       };
-               private class EventEditContext {
-                       /**
-                        * 編集対象トラック
-                        */
-                       private TrackEventListTableModel trackModel;
-                       /**
-                        * tick位置入力モデル
-                        */
-                       private TickPositionModel tickPositionModel = new TickPositionModel();
-                       /**
-                        * 選択されたイベント
-                        */
-                       private MidiEvent selectedMidiEvent = null;
-                       /**
-                        * 選択されたイベントの場所
-                        */
-                       private int selectedIndex = -1;
-                       /**
-                        * 選択されたイベントのtick位置
-                        */
-                       private long currentTick = 0;
-                       /**
-                        * 上書きして削除対象にする変更前イベント(null可)
-                        */
-                       private MidiEvent[] midiEventsToBeOverwritten;
-                       /**
-                        * 選択したイベントを入力ダイアログなどに反映します。
-                        * @param model 対象データモデル
-                        */
-                       private void setSelectedEvent(TrackEventListTableModel trackModel) {
-                               this.trackModel = trackModel;
-                               SequenceTrackListTableModel sequenceTableModel = trackModel.getParent();
-                               int ppq = sequenceTableModel.getSequence().getResolution();
-                               eventDialog.midiMessageForm.durationForm.setPPQ(ppq);
-                               tickPositionModel.setSequenceIndex(sequenceTableModel.getSequenceTickIndex());
-
-                               selectedIndex = trackModel.getSelectionModel().getMinSelectionIndex();
-                               selectedMidiEvent = selectedIndex < 0 ? null : trackModel.getMidiEvent(selectedIndex);
-                               currentTick = selectedMidiEvent == null ? 0 : selectedMidiEvent.getTick();
-                               tickPositionModel.setTickPosition(currentTick);
-                       }
-                       public void setupForEdit(TrackEventListTableModel trackModel) {
-                               MidiEvent partnerEvent = null;
-                               eventDialog.midiMessageForm.setMessage(
-                                       selectedMidiEvent.getMessage(),
-                                       trackModel.getParent().charset
-                               );
-                               if( eventDialog.midiMessageForm.isNote() ) {
-                                       int partnerIndex = trackModel.getIndexOfPartnerFor(selectedIndex);
-                                       if( partnerIndex < 0 ) {
-                                               eventDialog.midiMessageForm.durationForm.setDuration(0);
-                                       }
-                                       else {
-                                               partnerEvent = trackModel.getMidiEvent(partnerIndex);
-                                               long partnerTick = partnerEvent.getTick();
-                                               long duration = currentTick > partnerTick ?
-                                                       currentTick - partnerTick : partnerTick - currentTick ;
-                                               eventDialog.midiMessageForm.durationForm.setDuration((int)duration);
-                                       }
-                               }
-                               if(partnerEvent == null)
-                                       midiEventsToBeOverwritten = new MidiEvent[] {selectedMidiEvent};
-                               else
-                                       midiEventsToBeOverwritten = new MidiEvent[] {selectedMidiEvent, partnerEvent};
-                       }
-                       private Action jumpEventAction = new AbstractAction() {
-                               { putValue(NAME,"Jump"); }
-                               public void actionPerformed(ActionEvent e) {
-                                       long tick = tickPositionModel.getTickPosition();
-                                       scrollToEventAt(tick);
-                                       eventDialog.setVisible(false);
-                                       trackModel = null;
-                               }
-                       };
-                       private Action pasteEventAction = new AbstractAction() {
-                               { putValue(NAME,"Paste"); }
-                               public void actionPerformed(ActionEvent e) {
-                                       long tick = tickPositionModel.getTickPosition();
-                                       clipBoard.paste(trackModel, tick);
-                                       scrollToEventAt(tick);
-                                       // ペーストされたので変更フラグを立てる(曲の長さが変わるが、それも自動的にプレイリストに通知される)
-                                       SequenceTrackListTableModel seqModel = trackModel.getParent();
-                                       seqModel.setModified(true);
-                                       eventDialog.setVisible(false);
-                                       trackModel = null;
-                               }
-                       };
-                       private boolean applyEvent() {
-                               long tick = tickPositionModel.getTickPosition();
-                               MidiMessageForm form = eventDialog.midiMessageForm;
-                               SequenceTrackListTableModel seqModel = trackModel.getParent();
-                               MidiMessage msg = form.getMessage(seqModel.charset);
-                               if( msg == null ) {
-                                       return false;
-                               }
-                               MidiEvent newMidiEvent = new MidiEvent(msg, tick);
-                               if( midiEventsToBeOverwritten != null ) {
-                                       // 上書き消去するための選択済イベントがあった場合
-                                       trackModel.removeMidiEvents(midiEventsToBeOverwritten);
-                               }
-                               if( ! trackModel.addMidiEvent(newMidiEvent) ) {
-                                       System.out.println("addMidiEvent failure");
-                                       return false;
-                               }
-                               if(pairNoteOnOffModel.isSelected() && form.isNote()) {
-                                       ShortMessage sm = form.createPartnerMessage();
-                                       if(sm == null)
-                                               scrollToEventAt( tick );
-                                       else {
-                                               int duration = form.durationForm.getDuration();
-                                               if( form.isNote(false) ) {
-                                                       duration = -duration;
-                                               }
-                                               long partnerTick = tick + (long)duration;
-                                               if( partnerTick < 0L ) partnerTick = 0L;
-                                               MidiEvent partner = new MidiEvent((MidiMessage)sm, partnerTick);
-                                               if( ! trackModel.addMidiEvent(partner) ) {
-                                                       System.out.println("addMidiEvent failure (note on/off partner message)");
-                                               }
-                                               scrollToEventAt(partnerTick > tick ? partnerTick : tick);
-                                       }
-                               }
-                               seqModel.setModified(true);
-                               eventDialog.setVisible(false);
-                               return true;
-                       }
-               }
-               private EventEditContext editContext = new EventEditContext();
-               /**
-                * 指定のTick位置へジャンプするアクション
-                */
-               Action queryJumpEventAction = new AbstractAction() {
-                       {
-                               putValue(NAME,"Jump to ...");
-                               setEnabled(false);
-                       }
-                       public void actionPerformed(ActionEvent e) {
-                               editContext.setSelectedEvent(getModel());
-                               eventDialog.openTickForm("Jump selection to", editContext.jumpEventAction);
-                       }
-               };
-               /**
-                * 新しいイベントの追加を行うアクション
-                */
-               Action queryAddEventAction = new AbstractAction() {
-                       {
-                               putValue(NAME,"New");
-                               setEnabled(false);
-                       }
-                       public void actionPerformed(ActionEvent e) {
-                               TrackEventListTableModel model = getModel();
-                               editContext.setSelectedEvent(model);
-                               editContext.midiEventsToBeOverwritten = null;
-                               eventDialog.openEventForm("New MIDI event", eventCellEditor.applyEventAction, model.getChannel());
-                       }
-               };
-               /**
-                * MIDIイベントのコピー&ペーストを行うためのクリップボード
-                */
-               private class LocalClipBoard {
-                       private MidiEvent copiedEventsToPaste[];
-                       private int copiedEventsPPQ = 0;
-                       public void copy(TrackEventListTableModel model, boolean withRemove) {
-                               copiedEventsToPaste = model.getSelectedMidiEvents();
-                               copiedEventsPPQ = model.getParent().getSequence().getResolution();
-                               if( withRemove ) model.removeMidiEvents(copiedEventsToPaste);
-                               boolean en = (copiedEventsToPaste != null && copiedEventsToPaste.length > 0);
-                               queryPasteEventAction.setEnabled(en);
-                       }
-                       public void cut(TrackEventListTableModel model) {copy(model,true);}
-                       public void copy(TrackEventListTableModel model){copy(model,false);}
-                       public void paste(TrackEventListTableModel model, long tick) {
-                               model.addMidiEvents(copiedEventsToPaste, tick, copiedEventsPPQ);
-                       }
-               }
-               private LocalClipBoard clipBoard = new LocalClipBoard();
-               /**
-                * 指定のTick位置へ貼り付けるアクション
-                */
-               Action queryPasteEventAction = new AbstractAction() {
-                       {
-                               putValue(NAME,"Paste to ...");
-                               setEnabled(false);
-                       }
-                       public void actionPerformed(ActionEvent e) {
-                               editContext.setSelectedEvent(getModel());
-                               eventDialog.openTickForm("Paste to", editContext.pasteEventAction);
-                       }
-               };
-               /**
-                * イベントカットアクション
-                */
-               public Action cutEventAction = new AbstractAction("Cut") {
-                       private static final String CONFIRM_MESSAGE =
-                                       "Do you want to cut selected event ?\n選択したMIDIイベントを切り取りますか?";
-                       { setEnabled(false); }
-                       @Override
-                       public void actionPerformed(ActionEvent e) {
-                               if( JOptionPane.showConfirmDialog(
-                                               EventListTable.this.getRootPane(),
-                                               CONFIRM_MESSAGE,
-                                               ChordHelperApplet.VersionInfo.NAME,
-                                               JOptionPane.YES_NO_OPTION,
-                                               JOptionPane.WARNING_MESSAGE) == JOptionPane.YES_OPTION
-                               ) clipBoard.cut(getModel());
-                       }
-               };
-               /**
-                * イベントコピーアクション
-                */
-               public Action copyEventAction = new AbstractAction("Copy") {
-                       { setEnabled(false); }
-                       @Override
-                       public void actionPerformed(ActionEvent e) { clipBoard.copy(getModel()); }
-               };
-               /**
-                * イベント削除アクション
-                */
-               public Action deleteEventAction = new AbstractAction("Delete", deleteIcon) {
-                       private static final String CONFIRM_MESSAGE =
-                               "Do you want to delete selected event ?\n選択したMIDIイベントを削除しますか?";
-                       { setEnabled(false); }
-                       @Override
-                       public void actionPerformed(ActionEvent e) {
-                               if( JOptionPane.showConfirmDialog(
-                                               EventListTable.this.getRootPane(),
-                                               CONFIRM_MESSAGE,
-                                               ChordHelperApplet.VersionInfo.NAME,
-                                               JOptionPane.YES_NO_OPTION,
-                                               JOptionPane.WARNING_MESSAGE) == JOptionPane.YES_OPTION
-                               ) getModel().removeSelectedMidiEvents();
-                       }
-               };
-               /**
-                * MIDIイベント表のセルエディタ
-                */
-               private MidiEventCellEditor eventCellEditor;
-               /**
-                * MIDIイベント表のセルエディタ
-                */
-               class MidiEventCellEditor extends AbstractCellEditor implements TableCellEditor {
-                       /**
-                        * MIDIイベントセルエディタを構築します。
-                        */
-                       public MidiEventCellEditor() {
-                               eventDialog.midiMessageForm.setOutputMidiChannels(outputMidiDevice.getChannels());
-                               eventDialog.tickPositionInputForm.setModel(editContext.tickPositionModel);
-                               int index = TrackEventListTableModel.Column.MESSAGE.ordinal();
-                               getColumnModel().getColumn(index).setCellEditor(this);
-                       }
-                       /**
-                        * セルをダブルクリックしないと編集できないようにします。
-                        * @param e イベント(マウスイベント)
-                        * @return 編集可能になったらtrue
-                        */
-                       @Override
-                       public boolean isCellEditable(EventObject e) {
-                               return (e instanceof MouseEvent) ?
-                                       ((MouseEvent)e).getClickCount() == 2 : super.isCellEditable(e);
-                       }
-                       @Override
-                       public Object getCellEditorValue() { return null; }
-                       /**
-                        * MIDIメッセージダイアログが閉じたときにセル編集を中止するリスナー
-                        */
-                       private ComponentListener dialogComponentListener = new ComponentAdapter() {
-                               @Override
-                               public void componentHidden(ComponentEvent e) {
-                                       fireEditingCanceled();
-                                       // 用が済んだら当リスナーを除去
-                                       eventDialog.removeComponentListener(this);
-                               }
-                       };
-                       /**
-                        * 既存イベントを編集するアクション
-                        */
-                       private Action editEventAction = new AbstractAction() {
-                               public void actionPerformed(ActionEvent e) {
-                                       TrackEventListTableModel model = getModel();
-                                       editContext.setSelectedEvent(model);
-                                       if( editContext.selectedMidiEvent == null )
-                                               return;
-                                       editContext.setupForEdit(model);
-                                       eventDialog.addComponentListener(dialogComponentListener);
-                                       eventDialog.openEventForm("Change MIDI event", applyEventAction);
-                               }
-                       };
-                       /**
-                        * イベント編集ボタン
-                        */
-                       private JButton editEventButton = new JButton(editEventAction){{
-                               setHorizontalAlignment(JButton.LEFT);
-                       }};
-                       @Override
-                       public Component getTableCellEditorComponent(
-                               JTable table, Object value, boolean isSelected, int row, int column
-                       ) {
-                               editEventButton.setText(value.toString());
-                               return editEventButton;
-                       }
-                       /**
-                        * 入力したイベントを反映するアクション
-                        */
-                       private Action applyEventAction = new AbstractAction() {
-                               { putValue(NAME,"OK"); }
-                               public void actionPerformed(ActionEvent e) {
-                                       if( editContext.applyEvent() ) fireEditingStopped();
-                               }
-                       };
-               }
-               /**
-                * スクロール可能なMIDIイベントテーブルビュー
-                */
-               private JScrollPane scrollPane = new JScrollPane(this);
-               /**
-                * 指定の MIDI tick のイベントへスクロールします。
-                * @param tick MIDI tick
-                */
-               public void scrollToEventAt(long tick) {
-                       int index = getModel().tickToIndex(tick);
-                       scrollPane.getVerticalScrollBar().setValue(index * getRowHeight());
-                       getSelectionModel().setSelectionInterval(index, index);
-               }
-       }
-
-       /**
         * 新しい {@link MidiSequenceEditorDialog} を構築します。
         * @param playlistTableModel このエディタが参照するプレイリストモデル
         * @param outputMidiDevice イベントテーブルの操作音出力先MIDIデバイス
         * @param midiDeviceDialogOpenAction MIDIデバイスダイアログを開くアクション
         */
        public MidiSequenceEditorDialog(PlaylistTableModel playlistTableModel, VirtualMidiDevice outputMidiDevice, Action midiDeviceDialogOpenAction) {
-               this.outputMidiDevice = outputMidiDevice;
-               sequenceListTable = new SequenceListTable(playlistTableModel, midiDeviceDialogOpenAction);
-               trackListTable = new TrackListTable(playlistTableModel.emptyTrackListTableModel);
-               eventListTable = new EventListTable(playlistTableModel.emptyEventListTableModel);
+               sequenceListTable = new PlaylistTable(playlistTableModel, midiDeviceDialogOpenAction);
+               trackListTable = new SequenceTrackListTable(playlistTableModel.emptyTrackListTableModel);
+               eventListTable = new MidiEventTable(playlistTableModel.emptyEventListTableModel, outputMidiDevice);
                newSequenceDialog = new NewSequenceDialog(playlistTableModel, outputMidiDevice);
                setTitle("MIDI Editor/Playlist - "+ChordHelperApplet.VersionInfo.NAME);
                setBounds( 150, 200, 900, 500 );
@@ -808,7 +313,9 @@ public class MidiSequenceEditorDialog extends JDialog {
                                }
                                if(sequenceListTable.base64EncodeAction != null) {
                                        add(Box.createRigidArea(new Dimension(5, 0)));
-                                       add(new JButton(sequenceListTable.base64EncodeAction) {{ setMargin(ChordHelperApplet.ZERO_INSETS); }});
+                                       add(new JButton(sequenceListTable.base64EncodeAction) {
+                                               { setMargin(ChordHelperApplet.ZERO_INSETS); }
+                                       });
                                }
                                add(Box.createRigidArea(new Dimension(5, 0)));
                                add(new JButton(playlistTableModel.getMoveToTopAction()) {
@@ -23,6 +23,7 @@ import javax.swing.Action;
 import javax.swing.DefaultCellEditor;
 import javax.swing.JButton;
 import javax.swing.JComboBox;
+import javax.swing.JComponent;
 import javax.swing.JFileChooser;
 import javax.swing.JOptionPane;
 import javax.swing.JRootPane;
@@ -42,28 +43,20 @@ import camidion.chordhelper.mididevice.MidiSequencerModel;
 /**
  * プレイリストビュー(シーケンスリスト)
  */
-public class SequenceListTable extends JTable {
-       /**
-        * ファイル選択ダイアログ(アプレットの場合は使用不可なのでnull)
-        */
+public class PlaylistTable extends JTable {
+       /** ファイル選択ダイアログ(アプレットの場合は使用不可なのでnull) */
        MidiFileChooser midiFileChooser;
-       /**
-        * BASE64エンコードアクション
-        */
+       /** BASE64エンコードアクション */
        Action base64EncodeAction;
-       /**
-        * BASE64ダイアログ
-        */
+       /** BASE64ダイアログ */
        public Base64Dialog base64Dialog;
-       /**
-        * MIDIデバイスダイアログを開くアクション
-        */
+       /** MIDIデバイスダイアログを開くアクション */
        private Action midiDeviceDialogOpenAction;
        /**
         * プレイリストビューを構築します。
         * @param model プレイリストデータモデル
         */
-       public SequenceListTable(PlaylistTableModel model, Action midiDeviceDialogOpenAction) {
+       public PlaylistTable(PlaylistTableModel model, Action midiDeviceDialogOpenAction) {
                super(model, null, model.sequenceListSelectionModel);
                this.midiDeviceDialogOpenAction = midiDeviceDialogOpenAction;
                try {
@@ -95,13 +88,13 @@ public class SequenceListTable extends JTable {
                        }
                        @Override
                        public void actionPerformed(ActionEvent e) {
-                               SequenceTrackListTableModel mstm = getModel().getSelectedSequenceModel();
+                               SequenceTrackListTableModel sequenceModel = getModel().getSelectedSequenceModel();
                                byte[] data = null;
                                String filename = null;
-                               if( mstm != null ) {
-                                       filename = mstm.getFilename();
+                               if( sequenceModel != null ) {
+                                       filename = sequenceModel.getFilename();
                                        try {
-                                               data = mstm.getMIDIdata();
+                                               data = sequenceModel.getMIDIdata();
                                        } catch (IOException ioe) {
                                                base64Dialog.setText("File["+filename+"]:"+ioe.toString());
                                                base64Dialog.setVisible(true);
@@ -164,10 +157,9 @@ public class SequenceListTable extends JTable {
                ) {
                        try {
                                getModel().loadToSequencer(row);
-                       } catch (InvalidMidiDataException|IllegalStateException e) {
+                       } catch (InvalidMidiDataException|IllegalStateException ex) {
                                JOptionPane.showMessageDialog(
-                                               SequenceListTable.this.getRootPane(),
-                                               e,
+                                               table.getRootPane(), ex,
                                                ChordHelperApplet.VersionInfo.NAME,
                                                JOptionPane.ERROR_MESSAGE);
                        }
@@ -242,10 +234,9 @@ public class SequenceListTable extends JTable {
                        }
                        try {
                                model.loadToSequencer(row);
-                       } catch (InvalidMidiDataException e) {
+                       } catch (InvalidMidiDataException ex) {
                                JOptionPane.showMessageDialog(
-                                               SequenceListTable.this.getRootPane(),
-                                               e,
+                                               table.getRootPane(), ex,
                                                ChordHelperApplet.VersionInfo.NAME,
                                                JOptionPane.ERROR_MESSAGE);
                        }
@@ -272,14 +263,11 @@ public class SequenceListTable extends JTable {
                }
        }
        /**
-        * このプレイリスト(シーケンスリスト)が表示するデータを提供する
-        * プレイリストモデルを返します。
+        * このプレイリスト(シーケンスリスト)が表示するデータを提供するプレイリストモデルを返します。
         * @return プレイリストモデル
         */
        @Override
-       public PlaylistTableModel getModel() {
-               return (PlaylistTableModel)super.getModel();
-       }
+       public PlaylistTableModel getModel() { return (PlaylistTableModel)dataModel; }
        /**
         * このプレイリストにMIDIファイルを追加します。追加に失敗した場合はダイアログを表示し、
         * 後続のMIDIファイルが残っていればそれを追加するかどうかをユーザに尋ねます。
@@ -337,20 +325,18 @@ public class SequenceListTable extends JTable {
                        if( midiFileChooser != null ) {
                                SequenceTrackListTableModel seqModel = model.getSelectedSequenceModel();
                                if( seqModel != null && seqModel.isModified() && JOptionPane.showConfirmDialog(
-                                               SequenceListTable.this.getRootPane(),
+                                               ((JComponent)event.getSource()).getRootPane(),
                                                CONFIRM_MESSAGE,
                                                ChordHelperApplet.VersionInfo.NAME,
                                                JOptionPane.YES_NO_OPTION,
                                                JOptionPane.WARNING_MESSAGE) != JOptionPane.YES_OPTION
                                ) return;
                        }
-                       if( model.sequenceListSelectionModel.isSelectionEmpty() ) return;
-                       int index = model.sequenceListSelectionModel.getMinSelectionIndex();
-                       try {
-                               model.remove(index);
+                       if( ! model.sequenceListSelectionModel.isSelectionEmpty() ) try {
+                               model.remove(model.sequenceListSelectionModel.getMinSelectionIndex());
                        } catch (Exception ex) {
                                JOptionPane.showMessageDialog(
-                                               SequenceListTable.this.getRootPane(),
+                                               ((JComponent)event.getSource()).getRootPane(),
                                                ex,
                                                ChordHelperApplet.VersionInfo.NAME,
                                                JOptionPane.ERROR_MESSAGE);
@@ -371,17 +357,17 @@ public class SequenceListTable extends JTable {
                ) {
                        @Override
                        public void actionPerformed(ActionEvent event) {
-                               SequenceListTable playlistView = SequenceListTable.this;
-                               SequenceTrackListTableModel sequenceModel = playlistView.getModel().getSelectedSequenceModel();
+                               SequenceTrackListTableModel sequenceModel = getModel().getSelectedSequenceModel();
                                if( sequenceModel == null ) return;
                                String fn = sequenceModel.getFilename();
                                if( fn != null && ! fn.isEmpty() ) setSelectedFile(new File(fn));
-                               if( showSaveDialog((Component)event.getSource()) != JFileChooser.APPROVE_OPTION ) return;
+                               JRootPane rootPane = ((JComponent)event.getSource()).getRootPane();
+                               if( showSaveDialog(rootPane) != JFileChooser.APPROVE_OPTION ) return;
                                File f = getSelectedFile();
                                if( f.exists() ) {
                                        fn = f.getName();
                                        if( JOptionPane.showConfirmDialog(
-                                                       playlistView.getRootPane(),
+                                                       rootPane,
                                                        "Overwrite " + fn + " ?\n" + fn + " を上書きしてよろしいですか?",
                                                        ChordHelperApplet.VersionInfo.NAME,
                                                        JOptionPane.YES_NO_OPTION,
@@ -394,8 +380,7 @@ public class SequenceListTable extends JTable {
                                }
                                catch( Exception ex ) {
                                        JOptionPane.showMessageDialog(
-                                                       playlistView.getRootPane(),
-                                                       ex,
+                                                       rootPane, ex,
                                                        ChordHelperApplet.VersionInfo.NAME,
                                                        JOptionPane.ERROR_MESSAGE);
                                }
@@ -408,17 +393,16 @@ public class SequenceListTable extends JTable {
                        { putValue(Action.SHORT_DESCRIPTION, "Open MIDI file - MIDIファイルを開く"); }
                        @Override
                        public void actionPerformed(ActionEvent event) {
-                               SequenceListTable playlistView = SequenceListTable.this;
-                               JRootPane rootPane = playlistView.getRootPane();
+                               JRootPane rootPane = ((JComponent)event.getSource()).getRootPane();
                                try {
                                        if( showOpenDialog(rootPane) != JFileChooser.APPROVE_OPTION ) return;
                                } catch( HeadlessException ex ) {
                                        ex.printStackTrace();
                                        return;
                                }
-                               int firstIndex = playlistView.add(Arrays.asList(getSelectedFile()));
+                               int firstIndex = PlaylistTable.this.add(Arrays.asList(getSelectedFile()));
                                try {
-                                       PlaylistTableModel playlist = playlistView.getModel();
+                                       PlaylistTableModel playlist = getModel();
                                        MidiSequencerModel sequencerModel = playlist.getSequencerModel();
                                        if( sequencerModel.getSequencer().isRunning() ) return;
                                        if( firstIndex >= 0 ) playlist.play(firstIndex);