import javax.swing.table.TableCellEditor;\r
import javax.swing.table.TableCellRenderer;\r
import javax.swing.table.TableColumn;\r
-import javax.swing.table.TableColumnModel;\r
import javax.swing.table.TableModel;\r
\r
/**\r
* http://www.yk.rim.or.jp/~kamide/music/chordhelper/\r
*/\r
class MidiEditor extends JDialog implements DropTargetListener {\r
- public static final Insets ZERO_INSETS = new Insets(0,0,0,0);\r
- static final Icon deleteIcon = new ButtonIcon(ButtonIcon.X_ICON);\r
/**\r
* このMIDIエディタの仮想MIDIデバイス\r
*/\r
* エラーメッセージダイアログを表示します。\r
* @param message エラーメッセージ\r
*/\r
- private void showError(String message) {\r
+ void showError(String message) {\r
JOptionPane.showMessageDialog(\r
this, message,\r
ChordHelperApplet.VersionInfo.NAME,\r
* 警告メッセージダイアログを表示します。\r
* @param message 警告メッセージ\r
*/\r
- private void showWarning(String message) {\r
+ void showWarning(String message) {\r
JOptionPane.showMessageDialog(\r
this, message,\r
ChordHelperApplet.VersionInfo.NAME,\r
) == JOptionPane.YES_OPTION ;\r
}\r
\r
- /**\r
- * 新しいMIDIシーケンスを生成するダイアログ\r
- */\r
- NewSequenceDialog newSequenceDialog = new NewSequenceDialog(this);\r
- /**\r
- * BASE64テキスト入力ダイアログ\r
- */\r
- Base64Dialog base64Dialog = new Base64Dialog(this);\r
+ @Override\r
+ public void dragEnter(DropTargetDragEvent event) {\r
+ if( event.isDataFlavorSupported(DataFlavor.javaFileListFlavor) ) {\r
+ event.acceptDrag(DnDConstants.ACTION_COPY_OR_MOVE);\r
+ }\r
+ }\r
+ @Override\r
+ public void dragExit(DropTargetEvent event) {}\r
+ @Override\r
+ public void dragOver(DropTargetDragEvent event) {}\r
+ @Override\r
+ public void dropActionChanged(DropTargetDragEvent event) {}\r
+ @Override\r
+ @SuppressWarnings("unchecked")\r
+ public void drop(DropTargetDropEvent event) {\r
+ event.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);\r
+ try {\r
+ int action = event.getDropAction();\r
+ if ( (action & DnDConstants.ACTION_COPY_OR_MOVE) != 0 ) {\r
+ Transferable t = event.getTransferable();\r
+ Object data = t.getTransferData(DataFlavor.javaFileListFlavor);\r
+ loadAndPlay((List<File>)data);\r
+ event.dropComplete(true);\r
+ return;\r
+ }\r
+ event.dropComplete(false);\r
+ }\r
+ catch (Exception ex) {\r
+ ex.printStackTrace();\r
+ event.dropComplete(false);\r
+ }\r
+ }\r
+\r
+ public static final Insets ZERO_INSETS = new Insets(0,0,0,0);\r
+ static final Icon deleteIcon = new ButtonIcon(ButtonIcon.X_ICON);\r
/**\r
* プレイリストのデータモデル\r
*/\r
*/\r
SequenceListTable sequenceListTableView;\r
/**\r
+ * 新しいMIDIシーケンスを生成するダイアログ\r
+ */\r
+ NewSequenceDialog newSequenceDialog = new NewSequenceDialog(this);\r
+ /**\r
+ * BASE64テキスト入力ダイアログ\r
+ */\r
+ Base64Dialog base64Dialog = new Base64Dialog(this);\r
+ /**\r
* プレイリストビュー\r
*/\r
private class SequenceListTable extends JTable {\r
// アクセスできないので、ファイル選択ダイアログは使用不可。\r
midiFileChooser = null;\r
}\r
- model.selectionModel.addListSelectionListener(\r
- new ListSelectionListener() {\r
- @Override\r
- public void valueChanged(ListSelectionEvent e) {\r
- if( e.getValueIsAdjusting() )\r
- return;\r
- updateButtonStatus();\r
- }\r
- }\r
- );\r
- model.addTableModelListener(new TableModelListener() {\r
- /**\r
- * 全シーケンスの合計時間長をヘッダータイトルに反映します。\r
- * @param e テーブルモデルイベント\r
- */\r
- @Override\r
- public void tableChanged(TableModelEvent e) {\r
- int sec = getModel().getTotalSeconds();\r
- SequenceListTableModel.Column c = SequenceListTableModel.Column.SEQ_LENGTH;\r
- TableColumn tc = getColumnModel().getColumn(c.ordinal());\r
- tc.setHeaderValue(String.format(c.title+" [%02d:%02d]", sec/60, sec%60));\r
- //\r
- // シーケンス削除時など、合計シーケンス長が変わっても\r
- // 列モデルからではヘッダタイトルが再描画されないことがある。\r
- // そこで、ヘッダビューから repaint() で突っついて再描画させる。\r
- getTableHeader().repaint();\r
- }\r
- });\r
+ // プレイリストの変更時に合計時間を再表示\r
+ model.addTableModelListener(totalLengthListener);\r
+ //\r
+ // プレイボタンの埋め込み\r
SequenceListTableModel.Column c = SequenceListTableModel.Column.SEQ_PLAY;\r
TableColumn tc = getColumnModel().getColumn(c.ordinal());\r
- tc.setCellRenderer(new TableCellRenderer() {\r
- @Override\r
- public Component getTableCellRendererComponent(\r
- JTable table, Object value, boolean isSelected,\r
- boolean hasFocus, int row, int column\r
- ) {\r
- if(getModel().sequenceList.get(row).isOnSequencer()) {\r
- // すでにロードされていたらボタンをレンダリング\r
- return playButton;\r
- }\r
- // ロードされていなかった場合、\r
- // デフォルトレンダラーでレンダリングする。こうすれば\r
- // レンダラーを設定しなかった場合と全く同じ動作になる。\r
- Class<?> columnClass = getModel().getColumnClass(column);\r
- TableCellRenderer defaultRenderer =\r
- table.getDefaultRenderer(columnClass);\r
- return defaultRenderer.getTableCellRendererComponent(\r
- table, value, isSelected, hasFocus, row, column\r
- );\r
- }\r
- });\r
+ tc.setCellRenderer(playButtonCellRenderer);\r
tc.setCellEditor(new PlayButtonCellEditor());\r
setAutoCreateColumnsFromModel(false);\r
+ //\r
+ // Base64エンコードアクションの生成\r
if( base64Dialog.isBase64Available() ) {\r
base64EncodeAction = getModel().new SelectedSequenceAction(\r
"Base64 Encode",\r
}\r
};\r
}\r
+ //\r
+ // 曲の選択が切り替わったらリスナーに通知\r
+ model.selectionModel.addListSelectionListener(\r
+ new ListSelectionListener() {\r
+ @Override\r
+ public void valueChanged(ListSelectionEvent e) {\r
+ if( e.getValueIsAdjusting() )\r
+ return;\r
+ SequenceListTableModel model = getModel();\r
+ SequenceTrackListTableModel sequenceModel = model.getSelectedSequenceModel();\r
+ if(sequenceModel == null) {\r
+ sequenceModel = model.emptySequenceModel;\r
+ trackListTableView.addTrackAction.setEnabled(false);\r
+ trackListTableView.deleteTrackAction.setEnabled(false);\r
+ eventListTableView.copyEventAction.setEnabled(false);\r
+ eventListTableView.deleteEventAction.setEnabled(false);\r
+ eventListTableView.cutEventAction.setEnabled(false);\r
+ eventListTableView.queryJumpEventAction.setEnabled(false);\r
+ eventListTableView.queryAddEventAction.setEnabled(false);\r
+ eventListTableView.queryPasteEventAction.setEnabled(false);\r
+ }\r
+ else {\r
+ trackListTableView.addTrackAction.setEnabled(true);\r
+ }\r
+ trackListTableView.setModel(sequenceModel);\r
+ }\r
+ }\r
+ );\r
}\r
- @Override\r
- public SequenceListTableModel getModel() {\r
- return (SequenceListTableModel)(super.getModel());\r
- }\r
+ /**\r
+ * セル埋め込み用プレイボタン\r
+ */\r
+ private JToggleButton playButton = new JToggleButton(\r
+ getModel().sequencerModel.startStopAction\r
+ ){{\r
+ setMargin(ZERO_INSETS);\r
+ }};\r
+ /**\r
+ * プレイボタンを埋め込んだセルエディタ\r
+ */\r
class PlayButtonCellEditor extends AbstractCellEditor implements TableCellEditor {\r
@Override\r
public Object getCellEditorValue() { return ""; }\r
JTable table, Object value, boolean isSelected,\r
int row, int column\r
) {\r
- return getModel().sequenceList.get(row).isOnSequencer() ? playButton : null;\r
+ SequenceListTableModel model = getModel();\r
+ SequenceTrackListTableModel sm = model.sequenceList.get(row);\r
+ return sm.isOnSequencer() ? playButton : null;\r
}\r
}\r
/**\r
- * ã\82»ã\83«å\9f\8bã\82\81è¾¼ã\81¿ç\94¨ã\83\97ã\83¬ã\82¤ã\83\9cã\82¿ã\83³\r
+ * ã\83\97ã\83¬ã\82¤ã\83\9cã\82¿ã\83³ã\82\92å\9f\8bã\82\81è¾¼ã\82\93ã\81 ã\82»ã\83«ã\83¬ã\83³ã\83\80ã\83©ã\83¼\r
*/\r
- private JToggleButton playButton = new JToggleButton(\r
- getModel().sequencerModel.startStopAction\r
- ){{ setMargin(ZERO_INSETS); }};\r
+ private TableCellRenderer playButtonCellRenderer = new TableCellRenderer() {\r
+ @Override\r
+ public Component getTableCellRendererComponent(\r
+ JTable table, Object value, boolean isSelected,\r
+ boolean hasFocus, int row, int column\r
+ ) {\r
+ SequenceListTableModel model = getModel();\r
+ if(model.sequenceList.get(row).isOnSequencer()) {\r
+ // すでにロードされていたらボタンをレンダリング\r
+ return playButton;\r
+ }\r
+ // ロードされていなかった場合、\r
+ // デフォルトレンダラーでレンダリングする。こうすれば\r
+ // レンダラーを設定しなかった場合と全く同じ動作になる。\r
+ Class<?> cc = model.getColumnClass(column);\r
+ TableCellRenderer defaultRenderer = table.getDefaultRenderer(cc);\r
+ return defaultRenderer.getTableCellRendererComponent(\r
+ table, value, isSelected, hasFocus, row, column\r
+ );\r
+ }\r
+ };\r
+ /**\r
+ * 全シーケンスの合計時間長をヘッダータイトルに反映するリスナー\r
+ */\r
+ private TableModelListener totalLengthListener = new TableModelListener() {\r
+ @Override\r
+ public void tableChanged(TableModelEvent event) {\r
+ int sec = getModel().getTotalSeconds();\r
+ SequenceListTableModel.Column c = SequenceListTableModel.Column.SEQ_LENGTH;\r
+ String title = String.format(c.title+" [%02d:%02d]", sec/60, sec%60);\r
+ getColumnModel().getColumn(c.ordinal()).setHeaderValue(title);\r
+ //\r
+ // シーケンス削除時など、合計シーケンス長が変わっても\r
+ // 列モデルからではヘッダタイトルが再描画されないことがある。\r
+ // そこで、ヘッダビューから repaint() で突っついて再描画させる。\r
+ getTableHeader().repaint();\r
+ }\r
+ };\r
+ @Override\r
+ public SequenceListTableModel getModel() {\r
+ return (SequenceListTableModel)(super.getModel());\r
+ }\r
/**\r
* BASE64エンコードボタン(ライブラリが見えている場合のみ有効)\r
*/\r
};\r
};\r
}\r
- //\r
- // Drag & drop\r
- public void dragEnter(DropTargetDragEvent event) {\r
- if( event.isDataFlavorSupported(DataFlavor.javaFileListFlavor) ) {\r
- event.acceptDrag(DnDConstants.ACTION_COPY_OR_MOVE);\r
- }\r
- }\r
- public void dragExit(DropTargetEvent event) {}\r
- public void dragOver(DropTargetDragEvent event) {}\r
- public void dropActionChanged(DropTargetDragEvent event) {}\r
- @SuppressWarnings("unchecked")\r
- public void drop(DropTargetDropEvent event) {\r
- event.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);\r
- try {\r
- int action = event.getDropAction();\r
- if ( (action & DnDConstants.ACTION_COPY_OR_MOVE) != 0 ) {\r
- Transferable t = event.getTransferable();\r
- Object data = t.getTransferData(DataFlavor.javaFileListFlavor);\r
- loadAndPlay((List<File>)data);\r
- event.dropComplete(true);\r
- return;\r
- }\r
- event.dropComplete(false);\r
- }\r
- catch (Exception ex) {\r
- ex.printStackTrace();\r
- event.dropComplete(false);\r
- }\r
- }\r
\r
- SequenceTrackListTableModel sequenceTrackListTableModel;\r
/**\r
- * MIDIトラック選択状態\r
+ * MIDIトラックリストテーブルビュー(選択中のシーケンスの中身)\r
*/\r
- ListSelectionModel trackSelectionModel;\r
+ private TrackListTable trackListTableView;\r
/**\r
- * ã\83\88ã\83©ã\83\83ã\82¯è¿½å\8a ã\82¢ã\82¯ã\82·ã\83§ã\83³\r
+ * ã\82·ã\83¼ã\82±ã\83³ã\82¹ï¼\88ã\83\88ã\83©ã\83\83ã\82¯ã\83ªã\82¹ã\83\88ï¼\89ã\83\86ã\83¼ã\83\96ã\83«ã\83\93ã\83¥ã\83¼\r
*/\r
- public Action addTrackAction = new AbstractAction("New") {\r
- {\r
- String tooltip = "Append new track - 新しいトラックの追加";\r
- putValue(Action.SHORT_DESCRIPTION, tooltip);\r
+ private class TrackListTable extends JTable {\r
+ /**\r
+ * トラックリストテーブルビューを構築します。\r
+ * @param model シーケンスモデル\r
+ */\r
+ public TrackListTable(SequenceTrackListTableModel model) {\r
+ super(model, null, model.selectionModel);\r
+ model.parent.selectionModel.addListSelectionListener(titleLabel);\r
+ model.selectionModel.addListSelectionListener(trackSelectionListener);\r
+ //\r
+ // 録音対象のMIDIチャンネルをコンボボックスで選択できるよう、\r
+ // 列モデルにセルエディタを設定\r
+ int colIndex = SequenceTrackListTableModel.Column.RECORD_CHANNEL.ordinal();\r
+ TableColumn tc = getColumnModel().getColumn(colIndex);\r
+ tc.setCellEditor(new DefaultCellEditor(new JComboBox<String>(){{\r
+ addItem("OFF");\r
+ for(int i=1; i <= MIDISpec.MAX_CHANNELS; i++)\r
+ addItem(String.format("%d", i));\r
+ addItem("ALL");\r
+ }}));\r
+ // データモデルが差し替えられると、列モデルが自動作成されて\r
+ // セルエディタがデフォルトに戻ってしまうので、\r
+ // 一度列モデルを作ったあとは自動作成しない。\r
+ setAutoCreateColumnsFromModel(false);\r
}\r
@Override\r
- public void actionPerformed(ActionEvent e) {\r
- int index = sequenceListTableModel.getSelectedSequenceModel().createTrack();\r
- trackSelectionModel.setSelectionInterval(index, index);\r
+ public SequenceTrackListTableModel getModel() {\r
+ return (SequenceTrackListTableModel) super.getModel();\r
}\r
- };\r
- /**\r
- * トラック削除アクション\r
- */\r
- public Action deleteTrackAction = new AbstractAction("Delete", deleteIcon) {\r
- {\r
- String tooltip = "Delete selected track - 選択したトラックを削除";\r
- putValue(Action.SHORT_DESCRIPTION, tooltip);\r
+ /**\r
+ * このテーブルのデータモデルを設定し、\r
+ * そのモデルにリスナーを追加します。\r
+ * データモデルに内蔵している選択モデルも設定し直されます。\r
+ *\r
+ * @param newModel 新しく設定するデータモデル\r
+ * @see {@link JTable#setModel(TableModel)}\r
+ */\r
+ public void setModel(SequenceTrackListTableModel newModel) {\r
+ getSelectionModel().clearSelection();\r
+ getSelectionModel().removeListSelectionListener(trackSelectionListener);\r
+ getModel().parent.selectionModel.removeListSelectionListener(titleLabel);\r
+ super.setModel(newModel);\r
+ setSelectionModel(newModel.selectionModel);\r
+ newModel.parent.selectionModel.addListSelectionListener(titleLabel);\r
+ newModel.selectionModel.addListSelectionListener(trackSelectionListener);\r
}\r
- @Override\r
- public void actionPerformed(ActionEvent e) {\r
- if( ! confirm("Do you want to delete selected track ?\n選択したトラックを削除しますか?"))\r
- return;\r
- sequenceListTableModel.getSelectedSequenceModel().deleteTracks(\r
- trackSelectionModel\r
- );\r
+ /**\r
+ * トラックの選択変更時にイベントリストビューの対象モデルを切り替えるリスナー\r
+ */\r
+ private ListSelectionListener trackSelectionListener = new ListSelectionListener() {\r
+ @Override\r
+ public void valueChanged(ListSelectionEvent e) {\r
+ if( e.getValueIsAdjusting() )\r
+ return;\r
+ SequenceTrackListTableModel sequenceModel = getModel();\r
+ TrackEventListTableModel trackModel;\r
+ if( sequenceModel == null ) {\r
+ trackModel = getModel().parent.emptySequenceModel.emptyTrackModel;\r
+ deleteTrackAction.setEnabled(false);\r
+ eventListTableView.queryJumpEventAction.setEnabled(false);\r
+ eventListTableView.queryAddEventAction.setEnabled(false);\r
+ eventListTableView.queryPasteEventAction.setEnabled(false);\r
+ eventListTableView.copyEventAction.setEnabled(false);\r
+ eventListTableView.deleteEventAction.setEnabled(false);\r
+ eventListTableView.cutEventAction.setEnabled(false);\r
+ }\r
+ else {\r
+ trackModel = sequenceModel.getSelectedTrackModel();\r
+ if( trackModel == null ) {\r
+ trackModel = sequenceModel.emptyTrackModel;\r
+ }\r
+ deleteTrackAction.setEnabled(true);\r
+ eventListTableView.queryJumpEventAction.setEnabled(true);\r
+ eventListTableView.queryAddEventAction.setEnabled(true);\r
+ }\r
+ eventListTableView.setModel(trackModel);\r
+ }\r
+ };\r
+ /**\r
+ * 曲番号を表示するタイトルラベル\r
+ */\r
+ TitleLabel titleLabel = new TitleLabel();\r
+ /**\r
+ * 曲番号を表示するタイトルラベル\r
+ */\r
+ private class TitleLabel extends JLabel implements ListSelectionListener {\r
+ private static final String TITLE = "Tracks";\r
+ public TitleLabel() {\r
+ setText(TITLE);\r
+ getModel().parent.selectionModel.addListSelectionListener(this);\r
+ }\r
+ @Override\r
+ public void valueChanged(ListSelectionEvent event) {\r
+ if( event.getValueIsAdjusting() )\r
+ return;\r
+ ListSelectionModel selectionModel = (ListSelectionModel)event.getSource();\r
+ int index = selectionModel.getMinSelectionIndex();\r
+ String text = TITLE;\r
+ if( index >= 0 ) {\r
+ text = String.format(text+" - MIDI file No.%d", index);\r
+ }\r
+ setText(text);\r
+ }\r
}\r
- };\r
- /**\r
- * MIDIトラックリストテーブルビュー(選択中のシーケンスの中身)\r
- */\r
- private JTable trackListTableView;\r
+ /**\r
+ * トラック追加アクション\r
+ */\r
+ Action addTrackAction = new AbstractAction("New") {\r
+ {\r
+ String tooltip = "Append new track - 新しいトラックの追加";\r
+ putValue(Action.SHORT_DESCRIPTION, tooltip);\r
+ setEnabled(false);\r
+ }\r
+ @Override\r
+ public void actionPerformed(ActionEvent e) {\r
+ getModel().createTrack();\r
+ }\r
+ };\r
+ /**\r
+ * トラック削除アクション\r
+ */\r
+ Action deleteTrackAction = new AbstractAction("Delete", deleteIcon) {\r
+ {\r
+ String tooltip = "Delete selected track - 選択したトラックを削除";\r
+ putValue(Action.SHORT_DESCRIPTION, tooltip);\r
+ setEnabled(false);\r
+ }\r
+ @Override\r
+ public void actionPerformed(ActionEvent e) {\r
+ String message = "Do you want to delete selected track ?\n"\r
+ + "選択したトラックを削除しますか?";\r
+ if( confirm(message) )\r
+ getModel().deleteSelectedTracks();\r
+ }\r
+ };\r
+ }\r
\r
/**\r
- * MIDIイベント選択状態\r
+ * MIDIイベントリストテーブルビュー(選択中のトラックの中身)\r
*/\r
- private ListSelectionModel eventSelectionModel = new DefaultListSelectionModel() {{\r
- setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);\r
- addListSelectionListener(new ListSelectionListener() {\r
+ class EventListTable extends JTable {\r
+ public EventListTable(TrackEventListTableModel model) {\r
+ super(model, null, model.selectionModel);\r
+ //\r
+ // 列モデルにセルエディタを設定\r
+ int index = TrackEventListTableModel.Column.MESSAGE.ordinal();\r
+ getColumnModel().getColumn(index).setCellEditor(eventCellEditor);\r
+ // 以後、データモデルが変わっても列モデルを作り直さない\r
+ setAutoCreateColumnsFromModel(false);\r
+ //\r
+ // 選択されているトラックやイベントが変わったときのリスナーを設定\r
+ model.parent.selectionModel.addListSelectionListener(titleLabel);\r
+ model.selectionModel.addListSelectionListener(eventSelectionListener);\r
+ }\r
+ /**\r
+ * データモデルを設定し、リスナーを付け替えます。\r
+ *\r
+ * @param model トラック(イベントリスト)データモデル\r
+ */\r
+ public void setModel(TrackEventListTableModel model) {\r
+ getSelectionModel().clearSelection();\r
+ getSelectionModel().removeListSelectionListener(eventSelectionListener);\r
+ getModel().parent.selectionModel.removeListSelectionListener(titleLabel);\r
+ super.setModel(model);\r
+ super.setSelectionModel(model.selectionModel);\r
+ model.parent.selectionModel.addListSelectionListener(titleLabel);\r
+ model.selectionModel.addListSelectionListener(eventSelectionListener);\r
+ }\r
+ @Override\r
+ public TrackEventListTableModel getModel() {\r
+ return (TrackEventListTableModel) super.getModel();\r
+ }\r
+ /**\r
+ * トラック番号を表示するタイトルラベル\r
+ */\r
+ TitleLabel titleLabel = new TitleLabel();\r
+ /**\r
+ * トラック番号を表示するタイトルラベル\r
+ */\r
+ private class TitleLabel extends JLabel implements ListSelectionListener {\r
+ private static final String TITLE = "MIDI Events";\r
+ public TitleLabel() { super(TITLE); }\r
+ @Override\r
+ public void valueChanged(ListSelectionEvent event) {\r
+ String text = TITLE;\r
+ ListSelectionModel selectionModel = (ListSelectionModel)event.getSource();\r
+ int index = selectionModel.getMinSelectionIndex();\r
+ if( index >= 0 ) {\r
+ text = String.format(TITLE+" - track No.%d", index);\r
+ }\r
+ setText(text);\r
+ }\r
+ }\r
+ ListSelectionListener eventSelectionListener = new ListSelectionListener() {\r
@Override\r
public void valueChanged(ListSelectionEvent e) {\r
- if( e.getValueIsAdjusting() ) return;\r
- if( ! isSelectionEmpty() ) {\r
- TrackEventListTableModel trackModel = (TrackEventListTableModel)trackEventListTableView.getModel();\r
- int minIndex = getMinSelectionIndex();\r
- if( trackModel.hasTrack() ) {\r
- MidiEvent midiEvent = trackModel.getMidiEvent(minIndex);\r
- MidiMessage msg = midiEvent.getMessage();\r
- if( msg instanceof ShortMessage ) {\r
- ShortMessage sm = (ShortMessage)msg;\r
- int cmd = sm.getCommand();\r
- if( cmd == 0x80 || cmd == 0x90 || cmd == 0xA0 ) {\r
- // ノート番号を持つ場合、音を鳴らす。\r
- MidiChannel outMidiChannels[] = virtualMidiDevice.getChannels();\r
- int ch = sm.getChannel();\r
- int note = sm.getData1();\r
- int vel = sm.getData2();\r
- outMidiChannels[ch].noteOn(note, vel);\r
- outMidiChannels[ch].noteOff(note, vel);\r
- }\r
+ if( e.getValueIsAdjusting() )\r
+ return;\r
+ if( getSelectionModel().isSelectionEmpty() ) {\r
+ queryPasteEventAction.setEnabled(false);\r
+ copyEventAction.setEnabled(false);\r
+ deleteEventAction.setEnabled(false);\r
+ cutEventAction.setEnabled(false);\r
+ }\r
+ else {\r
+ copyEventAction.setEnabled(true);\r
+ deleteEventAction.setEnabled(true);\r
+ cutEventAction.setEnabled(true);\r
+ TrackEventListTableModel trackModel = getModel();\r
+ int minIndex = getSelectionModel().getMinSelectionIndex();\r
+ MidiEvent midiEvent = trackModel.getMidiEvent(minIndex);\r
+ MidiMessage msg = midiEvent.getMessage();\r
+ if( msg instanceof ShortMessage ) {\r
+ ShortMessage sm = (ShortMessage)msg;\r
+ int cmd = sm.getCommand();\r
+ if( cmd == 0x80 || cmd == 0x90 || cmd == 0xA0 ) {\r
+ // ノート番号を持つ場合、音を鳴らす。\r
+ MidiChannel outMidiChannels[] = virtualMidiDevice.getChannels();\r
+ int ch = sm.getChannel();\r
+ int note = sm.getData1();\r
+ int vel = sm.getData2();\r
+ outMidiChannels[ch].noteOn(note, vel);\r
+ outMidiChannels[ch].noteOff(note, vel);\r
}\r
}\r
- if( pairNoteCheckbox.isSelected() ) {\r
- int maxIndex = getMaxSelectionIndex();\r
+ if( pairNoteOnOffModel.isSelected() ) {\r
+ int maxIndex = getSelectionModel().getMaxSelectionIndex();\r
int partnerIndex;\r
- for( int i=minIndex; i<=maxIndex; i++ )\r
- if(\r
- isSelectedIndex(i) &&\r
- (partnerIndex = trackModel.getIndexOfPartnerFor(i)) >= 0 &&\r
- ! isSelectedIndex(partnerIndex)\r
- ) addSelectionInterval(partnerIndex, partnerIndex);\r
+ for( int i=minIndex; i<=maxIndex; i++ ) {\r
+ if( ! getSelectionModel().isSelectedIndex(i) ) continue;\r
+ partnerIndex = trackModel.getIndexOfPartnerFor(i);\r
+ if( partnerIndex >= 0 && ! getSelectionModel().isSelectedIndex(partnerIndex) )\r
+ getSelectionModel().addSelectionInterval(partnerIndex, partnerIndex);\r
+ }\r
}\r
}\r
- updateButtonStatus();\r
}\r
- });\r
- }};\r
- /**\r
- * MIDIイベントリストテーブルビュー\r
- */\r
- private JTable trackEventListTableView;\r
- private class MidiEventsLabel extends JLabel implements ListSelectionListener {\r
- private static final String TITLE = "MIDI Events";\r
- public MidiEventsLabel() {\r
- super(TITLE);\r
- trackSelectionModel.addListSelectionListener(this);\r
- }\r
- @Override\r
- public void valueChanged(ListSelectionEvent e) {\r
- String text = TITLE;\r
- int index = trackSelectionModel.getMinSelectionIndex();\r
- if( index >= 0 )\r
- text = String.format(TITLE+" - track No.%d", index);\r
- setText(text);\r
- }\r
- }\r
- /**\r
- * スクロール可能なMIDIイベントテーブルビュー\r
- */\r
- private JScrollPane scrollableEventTableView;\r
- /**\r
- * 指定の MIDI tick のイベントへスクロールします。\r
- * @param tick MIDI tick\r
- */\r
- public void scrollToEventAt(long tick) {\r
- TrackEventListTableModel trackModel = (TrackEventListTableModel)trackEventListTableView.getModel();\r
- int index = trackModel.tickToIndex(tick);\r
- scrollableEventTableView.getVerticalScrollBar().setValue(\r
- index * trackEventListTableView.getRowHeight()\r
- );\r
- eventSelectionModel.setSelectionInterval(index, index);\r
- }\r
- /**\r
- * MIDIイベント表のセルエディタ\r
- */\r
- MidiEventCellEditor eventCellEditor = new MidiEventCellEditor();\r
- /**\r
- * Pair note on/off チェックボックス\r
- */\r
- private JCheckBox pairNoteCheckbox = new JCheckBox("Pair NoteON/OFF") {\r
- { setModel(eventCellEditor.pairNoteOnOffModel); }\r
- };\r
- /**\r
- * MIDIイベント表のセルエディタ\r
- */\r
- class MidiEventCellEditor extends AbstractCellEditor implements TableCellEditor {\r
+ };\r
/**\r
* MIDIイベント入力ダイアログ\r
*/\r
MidiEventDialog eventDialog = new MidiEventDialog();\r
/**\r
- * 削除対象にする変更前イベント(null可)\r
- */\r
- private MidiEvent[] midiEventsToBeRemoved;\r
- /**\r
- * 対象トラック\r
+ * Pair noteON/OFF トグルボタンモデル\r
*/\r
- private TrackEventListTableModel midiTrackTableModel;\r
+ private JToggleButton.ToggleButtonModel\r
+ pairNoteOnOffModel = new JToggleButton.ToggleButtonModel() {\r
+ {\r
+ addItemListener(\r
+ new ItemListener() {\r
+ public void itemStateChanged(ItemEvent e) {\r
+ eventDialog.midiMessageForm.durationForm.setEnabled(isSelected());\r
+ }\r
+ }\r
+ );\r
+ setSelected(true);\r
+ }\r
+ };\r
/**\r
- * 対象シーケンス\r
+ * tick位置入力モデル\r
*/\r
- private SequenceTrackListTableModel sequenceTableModel;\r
+ private TickPositionModel tickPositionModel = new TickPositionModel();\r
/**\r
* 選択されたイベント\r
*/\r
* 選択されたイベントのtick位置\r
*/\r
private long currentTick = 0;\r
- /**\r
- * tick位置入力モデル\r
- */\r
- private TickPositionModel tickPositionModel = new TickPositionModel();\r
- /**\r
- * Pair noteON/OFF トグルボタンモデル\r
- */\r
- private JToggleButton.ToggleButtonModel pairNoteOnOffModel =\r
- new JToggleButton.ToggleButtonModel() {{\r
- addItemListener(new ItemListener() {\r
- public void itemStateChanged(ItemEvent e) {\r
- eventDialog.midiMessageForm.durationForm.setEnabled(isSelected());\r
- }\r
- });\r
- setSelected(true);\r
- }};\r
-\r
private void setSelectedEvent() {\r
- sequenceTableModel = sequenceListTableModel.getSelectedSequenceModel();\r
+ SequenceTrackListTableModel sequenceTableModel = getModel().parent;\r
eventDialog.midiMessageForm.durationForm.setPPQ(sequenceTableModel.getSequence().getResolution());\r
tickPositionModel.setSequenceIndex(sequenceTableModel.getSequenceTickIndex());\r
selectedIndex = -1;\r
currentTick = 0;\r
selectedMidiEvent = null;\r
- midiTrackTableModel = (TrackEventListTableModel)trackEventListTableView.getModel();\r
- if( ! eventSelectionModel.isSelectionEmpty() ) {\r
- selectedIndex = eventSelectionModel.getMinSelectionIndex();\r
- selectedMidiEvent = midiTrackTableModel.getMidiEvent(selectedIndex);\r
+ if( ! getSelectionModel().isSelectionEmpty() ) {\r
+ selectedIndex = getSelectionModel().getMinSelectionIndex();\r
+ selectedMidiEvent = getModel().getMidiEvent(selectedIndex);\r
currentTick = selectedMidiEvent.getTick();\r
tickPositionModel.setTickPosition(currentTick);\r
}\r
}\r
/**\r
- * イベント入力をキャンセルするアクション\r
- */\r
- Action cancelAction = new AbstractAction() {\r
- {\r
- putValue(NAME,"Cancel");\r
- eventDialog.cancelButton.setAction(this);\r
- }\r
- public void actionPerformed(ActionEvent e) {\r
- fireEditingCanceled();\r
- eventDialog.setVisible(false);\r
- }\r
- };\r
- /**\r
* 指定のTick位置へジャンプするアクション\r
*/\r
Action queryJumpEventAction = new AbstractAction() {\r
+ {\r
+ putValue(NAME,"Jump to ...");\r
+ setEnabled(false);\r
+ }\r
private Action jumpEventAction = new AbstractAction() {\r
{ putValue(NAME,"Jump"); }\r
public void actionPerformed(ActionEvent e) {\r
eventDialog.setVisible(false);\r
}\r
};\r
- { putValue(NAME,"Jump to ..."); }\r
public void actionPerformed(ActionEvent e) {\r
setSelectedEvent();\r
eventDialog.setTitle("Jump selection to");\r
* 指定のTick位置へ貼り付けるアクション\r
*/\r
Action queryPasteEventAction = new AbstractAction() {\r
- { putValue(NAME,"Paste to ..."); }\r
+ {\r
+ putValue(NAME,"Paste to ...");\r
+ setEnabled(false);\r
+ }\r
private Action pasteEventAction = new AbstractAction() {\r
{ putValue(NAME,"Paste"); }\r
public void actionPerformed(ActionEvent e) {\r
long tick = tickPositionModel.getTickPosition();\r
- ((TrackEventListTableModel)trackEventListTableView.getModel()).addMidiEvents(\r
- copiedEventsToPaste, tick, copiedEventsPPQ\r
- );\r
+ getModel().addMidiEvents(copiedEventsToPaste, tick, copiedEventsPPQ);\r
scrollToEventAt(tick);\r
// プレイリストの曲の長さ表示を更新\r
- sequenceListTableModel.fireSelectedSequenceChanged();\r
+ getModel().parent.parent.fireSelectedSequenceChanged();\r
eventDialog.setVisible(false);\r
}\r
};\r
* 新しいイベントの追加を行うアクション\r
*/\r
Action queryAddEventAction = new AbstractAction() {\r
- { putValue(NAME,"New"); }\r
+ {\r
+ putValue(NAME,"New");\r
+ setEnabled(false);\r
+ }\r
public void actionPerformed(ActionEvent e) {\r
setSelectedEvent();\r
- midiEventsToBeRemoved = null;\r
+ midiEventsToBeOverwritten = null; // 追加なので、上書きされるイベントなし\r
eventDialog.setTitle("Add a new MIDI event");\r
- eventDialog.okButton.setAction(addEventAction);\r
- int ch = midiTrackTableModel.getChannel();\r
+ eventDialog.okButton.setAction(eventCellEditor.addEventAction);\r
+ int ch = getModel().getChannel();\r
if( ch >= 0 ) {\r
eventDialog.midiMessageForm.channelText.setSelectedChannel(ch);\r
}\r
}\r
};\r
/**\r
- * イベントの追加(または変更)を行うアクション\r
+ * 上書きして削除対象にする変更前イベント(null可)\r
+ */\r
+ private MidiEvent[] midiEventsToBeOverwritten;\r
+ /**\r
+ * ペースト用にコピーされたMIDIイベントの配列\r
*/\r
- private Action addEventAction = new AbstractAction() {\r
- { putValue(NAME,"OK"); }\r
+ private MidiEvent copiedEventsToPaste[];\r
+ /**\r
+ * ペースト用にコピーされたMIDIイベントのタイミング解像度\r
+ */\r
+ private int copiedEventsPPQ = 0;\r
+ /**\r
+ * イベントカットアクション\r
+ */\r
+ public Action cutEventAction = new AbstractAction("Cut") {\r
+ {\r
+ setEnabled(false);\r
+ }\r
+ @Override\r
public void actionPerformed(ActionEvent e) {\r
- long tick = tickPositionModel.getTickPosition();\r
- MidiMessage msg = eventDialog.midiMessageForm.getMessage();\r
- MidiEvent newMidiEvent = new MidiEvent(msg,tick);\r
- if( midiEventsToBeRemoved != null ) {\r
- midiTrackTableModel.removeMidiEvents(midiEventsToBeRemoved);\r
- }\r
- if( ! midiTrackTableModel.addMidiEvent(newMidiEvent) ) {\r
- System.out.println("addMidiEvent failure");\r
+ if( ! confirm("Do you want to cut selected event ?\n選択したMIDIイベントを切り取りますか?"))\r
return;\r
- }\r
- if(pairNoteOnOffModel.isSelected() && eventDialog.midiMessageForm.isNote()) {\r
- ShortMessage sm = eventDialog.midiMessageForm.getPartnerMessage();\r
- if( sm == null ) scrollToEventAt( tick );\r
- else {\r
- int duration = eventDialog.midiMessageForm.durationForm.getDuration();\r
- if( eventDialog.midiMessageForm.isNote(false) ) { // Note Off\r
- duration = -duration;\r
- }\r
- long partnerTick = tick + (long)duration;\r
- if( partnerTick < 0L ) partnerTick = 0L;\r
- MidiEvent partner_midi_event =\r
- new MidiEvent( (MidiMessage)sm, partnerTick );\r
- if( ! midiTrackTableModel.addMidiEvent(partner_midi_event) ) {\r
- System.out.println("addMidiEvent failure (note on/off partner message)");\r
- }\r
- scrollToEventAt( partnerTick > tick ? partnerTick : tick );\r
- }\r
- }\r
- sequenceListTableModel.fireSequenceModified(sequenceTableModel);\r
- eventDialog.setVisible(false);\r
- fireEditingStopped();\r
+ copiedEventsToPaste = getModel().getSelectedMidiEvents();\r
+ copiedEventsPPQ = getModel().parent.parent.getSelectedSequenceModel().getSequence().getResolution();\r
+ getModel().removeMidiEvents(copiedEventsToPaste);\r
+ queryPasteEventAction.setEnabled(\r
+ copiedEventsToPaste != null &&\r
+ copiedEventsToPaste.length > 0\r
+ );\r
+ }\r
+ };\r
+ /**\r
+ * イベントコピーアクション\r
+ */\r
+ public Action copyEventAction = new AbstractAction("Copy") {\r
+ {\r
+ setEnabled(false);\r
+ }\r
+ @Override\r
+ public void actionPerformed(ActionEvent e) {\r
+ copiedEventsToPaste = getModel().getSelectedMidiEvents();\r
+ copiedEventsPPQ = getModel().parent.parent.getSelectedSequenceModel().getSequence().getResolution();\r
+ queryPasteEventAction.setEnabled(\r
+ copiedEventsToPaste != null &&\r
+ copiedEventsToPaste.length > 0\r
+ );\r
+ }\r
+ };\r
+ /**\r
+ * イベント削除アクション\r
+ */\r
+ public Action deleteEventAction = new AbstractAction("Delete", deleteIcon) {\r
+ {\r
+ setEnabled(false);\r
+ }\r
+ @Override\r
+ public void actionPerformed(ActionEvent e) {\r
+ if( ! confirm("Do you want to delete selected event ?\n選択したMIDIイベントを削除しますか?"))\r
+ return;\r
+ getModel().removeSelectedMidiEvents();\r
}\r
};\r
/**\r
* イベント編集アクション\r
*/\r
private Action editEventAction = new AbstractAction() {\r
+ {\r
+ setEnabled(false);\r
+ }\r
public void actionPerformed(ActionEvent e) {\r
setSelectedEvent();\r
if( selectedMidiEvent == null )\r
MidiEvent partnerEvent = null;\r
eventDialog.midiMessageForm.setMessage(selectedMidiEvent.getMessage());\r
if( eventDialog.midiMessageForm.isNote() ) {\r
- int partnerIndex = midiTrackTableModel.getIndexOfPartnerFor(selectedIndex);\r
+ int partnerIndex = getModel().getIndexOfPartnerFor(selectedIndex);\r
if( partnerIndex < 0 ) {\r
eventDialog.midiMessageForm.durationForm.setDuration(0);\r
}\r
else {\r
- partnerEvent = midiTrackTableModel.getMidiEvent(partnerIndex);\r
+ partnerEvent = getModel().getMidiEvent(partnerIndex);\r
long partnerTick = partnerEvent.getTick();\r
long duration = currentTick > partnerTick ?\r
currentTick - partnerTick : partnerTick - currentTick ;\r
events[0] = selectedMidiEvent;\r
events[1] = partnerEvent;\r
}\r
- midiEventsToBeRemoved = events;\r
+ midiEventsToBeOverwritten = events;\r
eventDialog.setTitle("Change MIDI event");\r
- eventDialog.okButton.setAction(addEventAction);\r
+ eventDialog.okButton.setAction(eventCellEditor.addEventAction);\r
eventDialog.openEventForm();\r
}\r
- };\r
- public MidiEventCellEditor() {\r
- eventDialog.midiMessageForm.setOutputMidiChannels(virtualMidiDevice.getChannels());\r
- eventDialog.tickPositionInputForm.setModel(tickPositionModel);\r
- }\r
- @Override\r
- public boolean isCellEditable(EventObject e) {\r
- // ダブルクリックで編集\r
- return e instanceof MouseEvent && ((MouseEvent)e).getClickCount() == 2;\r
+ };\r
+ /**\r
+ * MIDIイベント表のセルエディタ\r
+ */\r
+ private MidiEventCellEditor eventCellEditor = new MidiEventCellEditor();\r
+ /**\r
+ * MIDIイベント表のセルエディタ\r
+ */\r
+ class MidiEventCellEditor extends AbstractCellEditor implements TableCellEditor {\r
+ /**\r
+ * イベント入力をキャンセルするアクション\r
+ */\r
+ Action cancelAction = new AbstractAction() {\r
+ {\r
+ putValue(NAME,"Cancel");\r
+ eventDialog.cancelButton.setAction(this);\r
+ }\r
+ public void actionPerformed(ActionEvent e) {\r
+ fireEditingCanceled();\r
+ eventDialog.setVisible(false);\r
+ }\r
+ };\r
+ /**\r
+ * イベントの追加(または変更)を行うアクション\r
+ */\r
+ private Action addEventAction = new AbstractAction() {\r
+ { putValue(NAME,"OK"); }\r
+ public void actionPerformed(ActionEvent e) {\r
+ long tick = tickPositionModel.getTickPosition();\r
+ MidiMessage msg = eventDialog.midiMessageForm.getMessage();\r
+ MidiEvent newMidiEvent = new MidiEvent(msg,tick);\r
+ if( midiEventsToBeOverwritten != null ) {\r
+ // 上書き消去するための選択済イベントがあった場合\r
+ getModel().removeMidiEvents(midiEventsToBeOverwritten);\r
+ }\r
+ if( ! getModel().addMidiEvent(newMidiEvent) ) {\r
+ System.out.println("addMidiEvent failure");\r
+ return;\r
+ }\r
+ if(pairNoteOnOffModel.isSelected() && eventDialog.midiMessageForm.isNote()) {\r
+ ShortMessage sm = eventDialog.midiMessageForm.getPartnerMessage();\r
+ if( sm == null ) scrollToEventAt( tick );\r
+ else {\r
+ int duration = eventDialog.midiMessageForm.durationForm.getDuration();\r
+ if( eventDialog.midiMessageForm.isNote(false) ) { // Note Off\r
+ duration = -duration;\r
+ }\r
+ long partnerTick = tick + (long)duration;\r
+ if( partnerTick < 0L ) partnerTick = 0L;\r
+ MidiEvent partner_midi_event =\r
+ new MidiEvent( (MidiMessage)sm, partnerTick );\r
+ if( ! getModel().addMidiEvent(partner_midi_event) ) {\r
+ System.out.println("addMidiEvent failure (note on/off partner message)");\r
+ }\r
+ scrollToEventAt( partnerTick > tick ? partnerTick : tick );\r
+ }\r
+ }\r
+ getModel().parent.parent.fireSequenceModified(getModel().parent);\r
+ eventDialog.setVisible(false);\r
+ fireEditingStopped();\r
+ }\r
+ };\r
+ public MidiEventCellEditor() {\r
+ eventDialog.midiMessageForm.setOutputMidiChannels(virtualMidiDevice.getChannels());\r
+ eventDialog.tickPositionInputForm.setModel(tickPositionModel);\r
+ }\r
+ @Override\r
+ public boolean isCellEditable(EventObject e) {\r
+ // ダブルクリックで編集\r
+ return e instanceof MouseEvent && ((MouseEvent)e).getClickCount() == 2;\r
+ }\r
+ @Override\r
+ public Object getCellEditorValue() { return ""; }\r
+ private JButton editEventButton = new JButton(editEventAction){\r
+ {\r
+ setHorizontalAlignment(JButton.LEFT);\r
+ }\r
+ };\r
+ @Override\r
+ public Component getTableCellEditorComponent(\r
+ JTable table, Object value, boolean isSelected, int row, int column\r
+ ) {\r
+ editEventButton.setText((String)value);\r
+ return editEventButton;\r
+ }\r
}\r
- @Override\r
- public Object getCellEditorValue() { return ""; }\r
- private JButton editEventButton = new JButton(editEventAction){\r
- {\r
- setHorizontalAlignment(JButton.LEFT);\r
- }\r
- };\r
- @Override\r
- public Component getTableCellEditorComponent(\r
- JTable table, Object value, boolean isSelected, int row, int column\r
- ) {\r
- editEventButton.setText((String)value);\r
- return editEventButton;\r
+ /**\r
+ * スクロール可能なMIDIイベントテーブルビュー\r
+ */\r
+ private JScrollPane scrollPane = new JScrollPane(this);\r
+ /**\r
+ * 指定の MIDI tick のイベントへスクロールします。\r
+ * @param tick MIDI tick\r
+ */\r
+ public void scrollToEventAt(long tick) {\r
+ int index = getModel().tickToIndex(tick);\r
+ scrollPane.getVerticalScrollBar().setValue(index * getRowHeight());\r
+ getSelectionModel().setSelectionInterval(index, index);\r
}\r
}\r
-\r
- /**\r
- * ペースト用にコピーされたMIDIイベントの配列\r
- */\r
- private MidiEvent copiedEventsToPaste[];\r
/**\r
- * ペースト用にコピーされたMIDIイベントのタイミング解像度\r
- */\r
- private int copiedEventsPPQ = 0;\r
- /**\r
- * イベントカットアクション\r
- */\r
- public Action cutEventAction = new AbstractAction("Cut") {\r
- @Override\r
- public void actionPerformed(ActionEvent e) {\r
- if( ! confirm("Do you want to cut selected event ?\n選択したMIDIイベントを切り取りますか?"))\r
- return;\r
- TrackEventListTableModel trackTableModel = (TrackEventListTableModel)trackEventListTableView.getModel();\r
- copiedEventsToPaste = trackTableModel.getMidiEvents(eventSelectionModel);\r
- copiedEventsPPQ = sequenceListTableModel.getSelectedSequenceModel().getSequence().getResolution();\r
- trackTableModel.removeMidiEvents(copiedEventsToPaste);\r
- // プレイリストの曲の長さ表示を更新\r
- sequenceListTableModel.fireSelectedSequenceChanged();\r
- }\r
- };\r
- /**\r
- * イベントコピーアクション\r
- */\r
- public Action copyEventAction = new AbstractAction("Copy") {\r
- @Override\r
- public void actionPerformed(ActionEvent e) {\r
- TrackEventListTableModel trackTableModel = (TrackEventListTableModel)trackEventListTableView.getModel();\r
- copiedEventsToPaste = trackTableModel.getMidiEvents(eventSelectionModel);\r
- copiedEventsPPQ = sequenceListTableModel.getSelectedSequenceModel().getSequence().getResolution();\r
- updateButtonStatus();\r
- }\r
- };\r
- /**\r
- * イベント削除アクション\r
+ * MIDIイベントリストテーブルビュー\r
*/\r
- public Action deleteEventAction = new AbstractAction("Delete", deleteIcon) {\r
- @Override\r
- public void actionPerformed(ActionEvent e) {\r
- if( ! confirm("Do you want to delete selected event ?\n選択したMIDIイベントを削除しますか?"))\r
- return;\r
- ((TrackEventListTableModel)trackEventListTableView.getModel()).removeMidiEvents(eventSelectionModel);\r
- // プレイリストの曲の長さ表示を更新\r
- sequenceListTableModel.fireSelectedSequenceChanged();\r
- }\r
- };\r
+ EventListTable eventListTableView;\r
\r
/**\r
* 新しい {@link MidiEditor} を構築します。\r
* @param deviceModelList MIDIデバイスモデルリスト\r
*/\r
public MidiEditor(MidiSequencerModel sequencerModel) {\r
- sequenceListTableModel = new SequenceListTableModel(sequencerModel) ;\r
- sequenceTrackListTableModel = new SequenceTrackListTableModel(sequenceListTableModel);\r
+ // テーブルモデルとテーブルビューの生成\r
+ sequenceListTableView = new SequenceListTable(\r
+ sequenceListTableModel = new SequenceListTableModel(sequencerModel)\r
+ );\r
+ trackListTableView = new TrackListTable(\r
+ sequenceListTableModel.emptySequenceModel\r
+ );\r
+ eventListTableView = new EventListTable(\r
+ sequenceListTableModel.emptySequenceModel.emptyTrackModel\r
+ );\r
+ // レイアウト\r
setTitle("MIDI Editor/Playlist - MIDI Chord Helper");\r
setBounds( 150, 200, 850, 500 );\r
setLayout(new FlowLayout());\r
new DropTarget(this, DnDConstants.ACTION_COPY_OR_MOVE, this, true);\r
JPanel playlistPanel = new JPanel() {{\r
setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));\r
- add(new JScrollPane(\r
- sequenceListTableView = new SequenceListTable(sequenceListTableModel)\r
- ));\r
+ add(new JScrollPane(sequenceListTableView));\r
add(Box.createRigidArea(new Dimension(0, 10)));\r
add(new JPanel() {{\r
setLayout(new BoxLayout(this, BoxLayout.LINE_AXIS));\r
}};\r
JPanel trackListPanel = new JPanel() {{\r
setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));\r
- add(new JLabel() {\r
- private static final String TITLE = "Tracks";\r
- {\r
- sequenceListTableModel.selectionModel.addListSelectionListener(\r
- new ListSelectionListener() {\r
- @Override\r
- public void valueChanged(ListSelectionEvent e) {\r
- if( e.getValueIsAdjusting() )\r
- return;\r
- int index = sequenceListTableModel.selectionModel.getMinSelectionIndex();\r
- String text = TITLE;\r
- if( index >= 0 )\r
- text = String.format(text+" - MIDI file No.%d", index);\r
- setText(text);\r
- }\r
- }\r
- );\r
- setText(TITLE);\r
- }\r
- });\r
+ add(trackListTableView.titleLabel);\r
add(Box.createRigidArea(new Dimension(0, 5)));\r
- add(new JScrollPane(trackListTableView = new JTable(\r
- sequenceTrackListTableModel, null,\r
- trackSelectionModel = new DefaultListSelectionModel() {{\r
- setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);\r
- addListSelectionListener(new ListSelectionListener() {\r
- @Override\r
- public void valueChanged(ListSelectionEvent e) {\r
- if( e.getValueIsAdjusting() )\r
- return;\r
- SequenceTrackListTableModel sequenceModel =\r
- sequenceListTableModel.getSelectedSequenceModel();\r
- if( sequenceModel == null || isSelectionEmpty() ) {\r
- trackEventListTableView.setModel(new TrackEventListTableModel());\r
- }\r
- else {\r
- TrackEventListTableModel trackModel = sequenceModel.getTrackModel(getMinSelectionIndex());\r
- if( trackModel == null ) {\r
- trackEventListTableView.setModel(new TrackEventListTableModel());\r
- }\r
- else {\r
- trackEventListTableView.setModel(trackModel);\r
- TableColumnModel tcm = trackEventListTableView.getColumnModel();\r
- trackModel.sizeColumnWidthToFit(tcm);\r
- TableColumn midiMessageColumn = tcm.getColumn(TrackEventListTableModel.Column.MESSAGE.ordinal());\r
- midiMessageColumn.setCellEditor(eventCellEditor);\r
- }\r
- }\r
- updateButtonStatus();\r
- }\r
- });\r
- }}\r
- ) {{\r
- // 録音対象のMIDIチャンネルをコンボボックスで選択できるよう、\r
- // セルエディタを差し替える。\r
- getColumnModel().getColumn(\r
- SequenceTrackListTableModel.Column.RECORD_CHANNEL.ordinal()\r
- ).setCellEditor(\r
- new DefaultCellEditor(new JComboBox<String>() {{\r
- addItem("OFF");\r
- for(int i=1; i <= MIDISpec.MAX_CHANNELS; i++)\r
- addItem(String.format("%d", i));\r
- addItem("ALL");\r
- }} )\r
- );\r
- // デフォルトでは、データモデルが差し替えられると列が再作成される。\r
- // しかしこれでは、シーケンスモデルを差し替えたとたん、\r
- // せっかく差し替えたセルエディタがデフォルトに戻ってしまう。\r
- //\r
- // そこで、一度列データモデルが自動作成されたら、\r
- // 以後は自動作成しないようにする。\r
- setAutoCreateColumnsFromModel(false);\r
- }}));\r
+ add(new JScrollPane(trackListTableView));\r
add(Box.createRigidArea(new Dimension(0, 5)));\r
add(new JPanel() {{\r
- add(new JButton(addTrackAction) {{ setMargin(ZERO_INSETS); }});\r
- add(new JButton(deleteTrackAction) {{ setMargin(ZERO_INSETS); }});\r
+ add(new JButton(trackListTableView.addTrackAction) {{\r
+ setMargin(ZERO_INSETS);\r
+ }});\r
+ add(new JButton(trackListTableView.deleteTrackAction) {{\r
+ setMargin(ZERO_INSETS);\r
+ }});\r
}});\r
}};\r
JPanel eventListPanel = new JPanel() {{\r
- add(new MidiEventsLabel());\r
- add(scrollableEventTableView = new JScrollPane(\r
- trackEventListTableView = new JTable(\r
- new TrackEventListTableModel(),\r
- null,\r
- eventSelectionModel\r
- ) {{\r
- setAutoCreateColumnsFromModel(false);\r
- }}\r
- ));\r
+ add(eventListTableView.titleLabel);\r
+ add(eventListTableView.scrollPane);\r
add(new JPanel() {{\r
- add(pairNoteCheckbox);\r
- add(new JButton(eventCellEditor.queryJumpEventAction) {{\r
+ add(new JCheckBox("Pair NoteON/OFF") {{\r
+ setModel(eventListTableView.pairNoteOnOffModel);\r
+ setToolTipText("NoteON/OFFをペアで同時選択する");\r
+ }});\r
+ add(new JButton(eventListTableView.queryJumpEventAction) {{\r
+ setMargin(ZERO_INSETS);\r
+ }});\r
+ add(new JButton(eventListTableView.queryAddEventAction) {{\r
setMargin(ZERO_INSETS);\r
}});\r
- add(new JButton(eventCellEditor.queryAddEventAction) {{\r
+ add(new JButton(eventListTableView.copyEventAction) {{\r
setMargin(ZERO_INSETS);\r
}});\r
- add(new JButton(copyEventAction) {{ setMargin(ZERO_INSETS); }});\r
- add(new JButton(cutEventAction) {{ setMargin(ZERO_INSETS); }});\r
- add(new JButton(eventCellEditor.queryPasteEventAction) {{\r
+ add(new JButton(eventListTableView.cutEventAction) {{\r
+ setMargin(ZERO_INSETS);\r
+ }});\r
+ add(new JButton(eventListTableView.queryPasteEventAction) {{\r
+ setMargin(ZERO_INSETS);\r
+ }});\r
+ add(new JButton(eventListTableView.deleteEventAction) {{\r
setMargin(ZERO_INSETS);\r
}});\r
- add(new JButton(deleteEventAction) {{ setMargin(ZERO_INSETS); }});\r
}});\r
setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));\r
}};\r
setDividerLocation(160);\r
}}\r
);\r
- updateButtonStatus();\r
- }\r
-\r
- /**\r
- * ボタン状態の更新\r
- */\r
- public void updateButtonStatus() {\r
- SequenceTrackListTableModel\r
- sequenceModel = sequenceListTableModel.getSelectedSequenceModel();\r
- if(sequenceModel == null)\r
- trackListTableView.setModel(sequenceListTableModel.emptySequenceModel);\r
- else\r
- trackListTableView.setModel(sequenceModel);\r
- addTrackAction.setEnabled(sequenceModel != null);\r
- boolean isTrackSelected = (\r
- ! trackSelectionModel.isSelectionEmpty()\r
- &&\r
- sequenceModel != null && sequenceModel.getRowCount() > 0\r
- );\r
- deleteTrackAction.setEnabled(isTrackSelected);\r
- //\r
- TableModel tm = trackEventListTableView.getModel();\r
- if( ! (tm instanceof TrackEventListTableModel) )\r
- return;\r
- TrackEventListTableModel trackTableModel = (TrackEventListTableModel)tm;\r
- boolean isEventSelected = (\r
- isTrackSelected &&\r
- ! eventSelectionModel.isSelectionEmpty() &&\r
- trackTableModel != null && trackTableModel.getRowCount() > 0\r
- );\r
- copyEventAction.setEnabled(isEventSelected);\r
- deleteEventAction.setEnabled(isEventSelected);\r
- cutEventAction.setEnabled(isEventSelected);\r
- eventCellEditor.queryJumpEventAction.setEnabled(\r
- trackTableModel != null && isTrackSelected\r
- );\r
- eventCellEditor.queryAddEventAction.setEnabled(\r
- trackTableModel != null && isTrackSelected\r
- );\r
- eventCellEditor.queryPasteEventAction.setEnabled(\r
- trackTableModel != null && isTrackSelected &&\r
- copiedEventsToPaste != null && copiedEventsToPaste.length > 0\r
- );\r
- }\r
- /**\r
- * MIDIシーケンスを追加します。\r
- * シーケンサーが停止中の場合、追加したシーケンスから再生を開始します。\r
- * @param sequence MIDIシーケンス\r
- * @return 追加先インデックス(先頭が 0)\r
- */\r
- public int addSequenceAndPlay(Sequence sequence) {\r
- int lastIndex = sequenceListTableModel.addSequence(sequence,"");\r
- if( ! sequenceListTableModel.sequencerModel.getSequencer().isRunning() ) {\r
- sequenceListTableModel.loadToSequencer(lastIndex);\r
- sequenceListTableModel.sequencerModel.start();\r
- }\r
- return lastIndex;\r
- }\r
- /**\r
- * バイト列とファイル名からMIDIシーケンスを追加します。\r
- * バイト列が null の場合、空のMIDIシーケンスを追加します。\r
- * @param data バイト列\r
- * @param filename ファイル名\r
- * @return 追加先インデックス(先頭が 0、失敗した場合は -1)\r
- */\r
- public int addSequence(byte[] data, String filename) {\r
- if( data == null ) {\r
- return sequenceListTableModel.addDefaultSequence();\r
- }\r
- int lastIndex;\r
- try (InputStream in = new ByteArrayInputStream(data)) {\r
- Sequence seq = MidiSystem.getSequence(in);\r
- lastIndex = sequenceListTableModel.addSequence(seq, filename);\r
- } catch( IOException|InvalidMidiDataException e ) {\r
- showWarning(e.getMessage());\r
- return -1;\r
- }\r
- return lastIndex;\r
- }\r
- /**\r
- * URLから読み込んだMIDIシーケンスを追加します。\r
- * @param midiFileUrl MIDIファイルのURL\r
- * @return 追加先インデックス(先頭が 0、失敗した場合は -1)\r
- */\r
- public int addSequenceFromURL(String midiFileUrl) {\r
- Sequence seq = null;\r
- String filename = null;\r
- try {\r
- URI uri = new URI(midiFileUrl);\r
- URL url = uri.toURL();\r
- seq = MidiSystem.getSequence(url);\r
- filename = url.getFile().replaceFirst("^.*/","");\r
- } catch( URISyntaxException|IOException|InvalidMidiDataException e ) {\r
- showWarning(e.getMessage());\r
- } catch( AccessControlException e ) {\r
- showError(e.getMessage());\r
- e.printStackTrace();\r
- }\r
- if( seq == null ) return -1;\r
- return sequenceListTableModel.addSequence(seq, filename);\r
}\r
\r
/**\r
sequenceListTableModel.sequencerModel.start();\r
}\r
}\r
- /**\r
- * 選択されているシーケンスが、\r
- * ユーザ操作により録音可能な設定になったかどうか調べます。\r
- * @return 選択されているシーケンスが録音可能な設定ならtrue\r
- */\r
- public boolean isRecordable() {\r
- SequenceTrackListTableModel sequenceTableModel =\r
- sequenceListTableModel.getSelectedSequenceModel();\r
- return sequenceTableModel == null ? false : sequenceTableModel.isRecordable();\r
- }\r
}\r
\r
/**\r
/**\r
* プレイリスト(MIDIシーケンスリスト)のテーブルデータモデル\r
*/\r
-class SequenceListTableModel extends AbstractTableModel\r
- implements MetaEventListener\r
-{\r
+class SequenceListTableModel extends AbstractTableModel {\r
/**\r
* 新しいプレイリストのテーブルモデルを構築します。\r
* @param sequencerModel MIDIシーケンサーモデル\r
*/\r
public SequenceListTableModel(MidiSequencerModel sequencerModel) {\r
this.sequencerModel = sequencerModel;\r
- sequencerModel.addChangeListener(secondPosition);\r
- sequencerModel.getSequencer().addMetaEventListener(this);\r
+ //\r
+ // 秒位置を監視\r
+ sequencerModel.addChangeListener(secondPosition = new SecondPosition());\r
+ //\r
+ // メタイベントを監視\r
+ sequencerModel.getSequencer().addMetaEventListener(\r
+ new MetaEventListener() {\r
+ /**\r
+ * {@inheritDoc}\r
+ *\r
+ * <p>EOT (End Of Track、type==0x2F) を受信したときの処理です。\r
+ * </p>\r
+ * <p>これは MetaEventListener のための実装なので、多くの場合\r
+ * Swing EDT ではなく MIDI シーケンサの EDT から起動されます。\r
+ * Swing EDT とは違うスレッドで動いていた場合は Swing EDT に振り直されます。\r
+ * </p>\r
+ */\r
+ @Override\r
+ public void meta(MetaMessage msg) {\r
+ if( msg.getType() == 0x2F ) {\r
+ if( ! SwingUtilities.isEventDispatchThread() ) {\r
+ SwingUtilities.invokeLater(\r
+ new Runnable() {\r
+ @Override\r
+ public void run() { goNext(); }\r
+ }\r
+ );\r
+ return;\r
+ }\r
+ goNext();\r
+ }\r
+ }\r
+ }\r
+ );\r
+ }\r
+ /**\r
+ * 次の曲へ進みます。\r
+ *\r
+ * <p>リピートモードの場合は同じ曲をもう一度再生、\r
+ * そうでない場合は次の曲へ進んで再生します。\r
+ * 次の曲がなければ、そこで停止します。\r
+ * いずれの場合も局の先頭へ戻ります。\r
+ * </p>\r
+ */\r
+ private void goNext() {\r
+ // とりあえず曲の先頭へ戻る\r
+ sequencerModel.getSequencer().setMicrosecondPosition(0);\r
+ if( (Boolean)toggleRepeatAction.getValue(Action.SELECTED_KEY) || loadNext(1)) {\r
+ // リピートモードのときはもう一度同じ曲を、\r
+ // そうでない場合は次の曲を再生開始\r
+ sequencerModel.start();\r
+ }\r
+ else {\r
+ // 最後の曲が終わったので、停止状態にする\r
+ sequencerModel.stop();\r
+ // ここでボタンが停止状態に変わったはずなので、\r
+ // 通常であれば再生ボタンが自力で再描画するところだが、\r
+ //\r
+ // セルのレンダラーが描く再生ボタンには効かないようなので、\r
+ // セルを突っついて再表示させる。\r
+ int rowIndex = indexOfSequenceOnSequencer();\r
+ int colIndex = Column.SEQ_PLAY.ordinal();\r
+ fireTableCellUpdated(rowIndex, colIndex);\r
+ }\r
}\r
/**\r
* MIDIシーケンサモデル\r
*/\r
List<SequenceTrackListTableModel> sequenceList = new Vector<>();\r
/**\r
- * 行の選択状態\r
+ * 選択されているシーケンスのインデックス\r
*/\r
ListSelectionModel selectionModel = new DefaultListSelectionModel() {{\r
setSelectionMode(ListSelectionModel.SINGLE_SELECTION);\r
public void actionPerformed(ActionEvent event) { }\r
};\r
/**\r
- * {@inheritDoc}\r
- *\r
- * <p>EOT (End Of Track、type==0x2F) を受信したときの処理です。\r
- * 通常はシーケンサの EDT から起動されるため、\r
- * Swing の EDT へ振りなおします。\r
- * </p>\r
- */\r
- @Override\r
- public void meta(MetaMessage msg) {\r
- if( msg.getType() == 0x2F ) {\r
- if( ! SwingUtilities.isEventDispatchThread() ) {\r
- SwingUtilities.invokeLater(new Runnable() {\r
- @Override\r
- public void run() { goNext(); }\r
- });\r
- return;\r
- }\r
- goNext();\r
- }\r
- }\r
- /**\r
- * 次の曲へ進みます。\r
- *\r
- * <p>リピートモードの場合は同じ曲をもう一度再生、\r
- * そうでない場合は次の曲へ進んで再生します。\r
- * 次の曲がなければ、そこで停止します。\r
- * いずれの場合も局の先頭へ戻ります。\r
- * </p>\r
- */\r
- private void goNext() {\r
- sequencerModel.getSequencer().setMicrosecondPosition(0);\r
- if( (Boolean)toggleRepeatAction.getValue(Action.SELECTED_KEY) || loadNext(1) ) {\r
- sequencerModel.start();\r
- }\r
- else {\r
- sequencerModel.stop();\r
- }\r
- }\r
- /**\r
- * 再生中のシーケンサーの秒位置\r
- */\r
- private SecondPosition secondPosition = new SecondPosition();\r
- /**\r
* 再生中のシーケンサーの秒位置\r
*/\r
private class SecondPosition implements ChangeListener {\r
private int value = 0;\r
/**\r
* 再生中のシーケンサーの秒位置が変わったときに表示を更新します。\r
+ * @param event 変更イベント\r
*/\r
@Override\r
- public void stateChanged(ChangeEvent e) {\r
- if( e.getSource() instanceof MidiSequencerModel ) {\r
- MidiSequencerModel m = (MidiSequencerModel)e.getSource();\r
- int newValue = m.getValue() / 1000;\r
- if(value == newValue)\r
- return;\r
- // 秒が変わったときだけ更新(小数点以下は無視)\r
+ public void stateChanged(ChangeEvent event) {\r
+ Object src = event.getSource();\r
+ if( src instanceof MidiSequencerModel ) {\r
+ int newValue = ((MidiSequencerModel)src).getValue() / 1000;\r
+ if(value == newValue) return;\r
value = newValue;\r
- fireTableCellUpdated(\r
- indexOfSequenceOnSequencer(), Column.SEQ_POSITION.ordinal()\r
- );\r
+ int rowIndex = indexOfSequenceOnSequencer();\r
+ fireTableCellUpdated(rowIndex, Column.SEQ_POSITION.ordinal());\r
}\r
}\r
@Override\r
public boolean isCellEditable(int row, int column) {\r
return Column.values()[column].isCellEditable();\r
}\r
+ /** 再生中のシーケンサーの秒位置 */\r
+ private SecondPosition secondPosition;\r
@Override\r
public Object getValueAt(int row, int column) {\r
Column c = Column.values()[column];\r
}\r
case RESOLUTION: return sequenceModel.getSequence().getResolution();\r
case TRACKS: return sequenceModel.getSequence().getTracks().length;\r
- case SEQ_POSITION: return sequenceModel.isOnSequencer() ? secondPosition : "";\r
+ case SEQ_POSITION:\r
+ return sequenceModel.isOnSequencer() ? secondPosition : "";\r
case SEQ_LENGTH: {\r
long usec = sequenceModel.getSequence().getMicrosecondLength();\r
int sec = (int)( (usec < 0 ? usec += 0x100000000L : usec) / 1000L / 1000L );\r
}\r
/**\r
* 選択されたMIDIシーケンスのテーブルモデルを返します。\r
- * @param selectionModel 選択状態\r
- * @return 選択されたMIDIシーケンスのテーブルモデル\r
+ * @return 選択されたMIDIシーケンスのテーブルモデル(非選択時はnull)\r
*/\r
public SequenceTrackListTableModel getSelectedSequenceModel() {\r
if( selectionModel.isSelectionEmpty() )\r
fireTableRowsUpdated(minIndex, maxIndex);\r
}\r
/**\r
+ * バイト列とファイル名からMIDIシーケンスを追加します。\r
+ * バイト列が null の場合、空のMIDIシーケンスを追加します。\r
+ * @param data バイト列\r
+ * @param filename ファイル名\r
+ * @return 追加先インデックス(先頭が 0)\r
+ * @throws IOException ファイル読み込みに失敗した場合\r
+ * @throws InvalidMidiDataException MIDIデータが正しくない場合\r
+ */\r
+ public int addSequence(byte[] data, String filename)\r
+ throws IOException, InvalidMidiDataException\r
+ {\r
+ if( data == null ) return addDefaultSequence();\r
+ int lastIndex;\r
+ try (InputStream in = new ByteArrayInputStream(data)) {\r
+ Sequence seq = MidiSystem.getSequence(in);\r
+ lastIndex = addSequence(seq, filename);\r
+ } catch( IOException|InvalidMidiDataException e ) {\r
+ throw e;\r
+ }\r
+ return lastIndex;\r
+ }\r
+ /**\r
+ * MIDIシーケンスを追加します。\r
+ * シーケンサーが停止中の場合、追加したシーケンスから再生を開始します。\r
+ * @param sequence MIDIシーケンス\r
+ * @return 追加先インデックス(先頭が 0)\r
+ */\r
+ public int addSequenceAndPlay(Sequence sequence) {\r
+ int lastIndex = addSequence(sequence,"");\r
+ if( ! sequencerModel.getSequencer().isRunning() ) {\r
+ loadToSequencer(lastIndex);\r
+ sequencerModel.start();\r
+ }\r
+ return lastIndex;\r
+ }\r
+ /**\r
* MIDIシーケンスを追加します。\r
- * @param seq MIDIシーケンス\r
+ * @param sequence MIDIシーケンス\r
* @param filename ファイル名\r
* @return 追加されたシーケンスのインデックス(先頭が 0)\r
*/\r
- public int addSequence(Sequence seq, String filename) {\r
- sequenceList.add(new SequenceTrackListTableModel(this, seq, filename));\r
+ public int addSequence(Sequence sequence, String filename) {\r
+ sequenceList.add(\r
+ new SequenceTrackListTableModel(this, sequence, filename)\r
+ );\r
int lastIndex = sequenceList.size() - 1;\r
fireTableRowsInserted(lastIndex, lastIndex);\r
return lastIndex;\r
* @throws InvalidMidiDataException ファイル内のMIDIデータが正しくない場合\r
* @throws IOException ファイル入出力に失敗した場合\r
*/\r
- public int addSequences(List<File> fileList) throws InvalidMidiDataException, IOException {\r
+ public int addSequences(List<File> fileList)\r
+ throws InvalidMidiDataException, IOException\r
+ {\r
int firstIndex = -1;\r
for( File file : fileList ) {\r
int lastIndex = addSequence(file);\r
return firstIndex;\r
}\r
/**\r
+ * URLから読み込んだMIDIシーケンスを追加します。\r
+ * @param midiFileUrl MIDIファイルのURL\r
+ * @return 追加先インデックス(先頭が 0、失敗した場合は -1)\r
+ * @throws URISyntaxException URLの形式に誤りがある場合\r
+ * @throws IOException 入出力に失敗した場合\r
+ * @throws InvalidMidiDataException MIDIデータが正しくない場合\r
+ */\r
+ public int addSequenceFromURL(String midiFileUrl)\r
+ throws URISyntaxException, IOException, InvalidMidiDataException\r
+ {\r
+ URI uri = new URI(midiFileUrl);\r
+ URL url = uri.toURL();\r
+ Sequence seq = MidiSystem.getSequence(url);\r
+ String filename = url.getFile().replaceFirst("^.*/","");\r
+ return addSequence(seq, filename);\r
+ }\r
+\r
+ /**\r
* 選択したシーケンスを除去します。\r
* @param listSelectionModel 選択状態\r
*/\r
/**\r
* 親のプレイリスト\r
*/\r
- SequenceListTableModel sequenceListTableModel;\r
+ SequenceListTableModel parent;\r
/**\r
* ラップされたMIDIシーケンス\r
*/\r
*/\r
private List<TrackEventListTableModel> trackModelList = new ArrayList<>();\r
/**\r
+ * 空のトラックモデル\r
+ */\r
+ TrackEventListTableModel emptyTrackModel = new TrackEventListTableModel(this);\r
+ /**\r
+ * 選択されているトラックのインデックス\r
+ */\r
+ ListSelectionModel selectionModel = new DefaultListSelectionModel() {{\r
+ setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);\r
+ }};\r
+ /**\r
* 空の {@link SequenceTrackListTableModel} を構築します。\r
* @param sequenceListTableModel 親のプレイリスト\r
*/\r
public SequenceTrackListTableModel(SequenceListTableModel sequenceListTableModel) {\r
- this.sequenceListTableModel = sequenceListTableModel;\r
+ this.parent = sequenceListTableModel;\r
}\r
/**\r
* MIDIシーケンスとファイル名から {@link SequenceTrackListTableModel} を構築します。\r
case TRACK_NUMBER: return row;\r
case EVENTS: return sequence.getTracks()[row].size();\r
case MUTE:\r
- return isOnSequencer() ? getSequencer().getTrackMute(row) : "";\r
+ return isOnSequencer() ? parent.sequencerModel.getSequencer().getTrackMute(row) : "";\r
case SOLO:\r
- return isOnSequencer() ? getSequencer().getTrackSolo(row) : "";\r
+ return isOnSequencer() ? parent.sequencerModel.getSequencer().getTrackSolo(row) : "";\r
case RECORD_CHANNEL:\r
return isOnSequencer() ? trackModelList.get(row).getRecordingChannel() : "";\r
case CHANNEL: {\r
Column c = Column.values()[column];\r
switch(c) {\r
case MUTE:\r
- getSequencer().setTrackMute(row, ((Boolean)val).booleanValue());\r
+ parent.sequencerModel.getSequencer().setTrackMute(row, ((Boolean)val).booleanValue());\r
break;\r
case SOLO:\r
- getSequencer().setTrackSolo(row, ((Boolean)val).booleanValue());\r
+ parent.sequencerModel.getSequencer().setTrackSolo(row, ((Boolean)val).booleanValue());\r
break;\r
case RECORD_CHANNEL:\r
trackModelList.get(row).setRecordingChannel((String)val);\r
* @param sequence MIDIシーケンス\r
*/\r
private void setSequence(Sequence sequence) {\r
- getSequencer().recordDisable(null); // The "null" means all tracks\r
+ parent.sequencerModel.getSequencer().recordDisable(null); // The "null" means all tracks\r
this.sequence = sequence;\r
int oldSize = trackModelList.size();\r
if( oldSize > 0 ) {\r
int row = indexOf(track);\r
if( row < 0 ) return;\r
fireTableRowsUpdated(row, row);\r
- sequenceListTableModel.fireSequenceModified(this);\r
+ parent.fireSequenceModified(this);\r
}\r
/**\r
- * 指定のインデックスのトラックモデルを返します。\r
+ * 選択されているトラックモデルを返します。\r
* @param index トラックのインデックス\r
* @return トラックモデル(見つからない場合null)\r
*/\r
- public TrackEventListTableModel getTrackModel(int index) {\r
+ public TrackEventListTableModel getSelectedTrackModel() {\r
+ if( selectionModel.isSelectionEmpty() )\r
+ return null;\r
+ int index = selectionModel.getMinSelectionIndex();\r
Track tracks[] = sequence.getTracks();\r
if( tracks.length != 0 ) {\r
Track track = tracks[index];\r
trackModelList.add(new TrackEventListTableModel(sequence.createTrack(), this));\r
int lastRow = sequence.getTracks().length - 1;\r
fireTableRowsInserted(lastRow, lastRow);\r
+ //\r
// 親のプレイリストのトラック数表示を更新\r
- sequenceListTableModel.fireSelectedSequenceChanged();\r
+ parent.fireSelectedSequenceChanged();\r
+ //\r
+ // 追加したトラックを選択\r
+ selectionModel.setSelectionInterval(lastRow, lastRow);\r
return lastRow;\r
}\r
/**\r
* 選択されているトラックを削除します。\r
- * @param selectionModel 選択状態\r
*/\r
- public void deleteTracks(ListSelectionModel selectionModel) {\r
+ public void deleteSelectedTracks() {\r
if( selectionModel.isSelectionEmpty() )\r
return;\r
int minIndex = selectionModel.getMinSelectionIndex();\r
}\r
fireTableRowsDeleted(minIndex, maxIndex);\r
// 親のプレイリストのトラック数表示を更新\r
- sequenceListTableModel.fireSelectedSequenceChanged();\r
- }\r
- /**\r
- * MIDIシーケンサを返します。\r
- * @return MIDIシーケンサ\r
- */\r
- public Sequencer getSequencer() {\r
- return sequenceListTableModel.sequencerModel.getSequencer();\r
+ parent.fireSelectedSequenceChanged();\r
}\r
/**\r
* このシーケンスモデルのシーケンスをシーケンサーが操作しているか調べます。\r
* @return シーケンサーが操作していたらtrue\r
*/\r
public boolean isOnSequencer() {\r
- return sequence == getSequencer().getSequence();\r
+ return sequence == parent.sequencerModel.getSequencer().getSequence();\r
}\r
/**\r
- * 録音可能かどうかを返します。\r
- *\r
- * <p>シーケンサーにロード済みで、\r
- * かつ録音しようとしているチャンネルの設定されたトラックが一つでもあれば、\r
- * 録音可能です。\r
- * </p>\r
- * @return 録音可能であればtrue\r
+ * 録音しようとしているチャンネルの設定されたトラックがあるか調べます。\r
+ * @return 該当トラックがあればtrue\r
*/\r
- public boolean isRecordable() {\r
- if( isOnSequencer() ) {\r
- int rowCount = getRowCount();\r
- int col = Column.RECORD_CHANNEL.ordinal();\r
- for( int row=0; row < rowCount; row++ )\r
- if( ! "OFF".equals(getValueAt(row, col)) ) return true;\r
+ public boolean hasRecordChannel() {\r
+ int rowCount = getRowCount();\r
+ for( int row=0; row < rowCount; row++ ) {\r
+ Object value = getValueAt(row, Column.RECORD_CHANNEL.ordinal());\r
+ if( ! "OFF".equals(value) ) return true;\r
}\r
return false;\r
}\r
*/\r
public enum Column {\r
/** MIDIイベント番号 */\r
- EVENT_NUMBER("No.", 30, Integer.class),\r
+ EVENT_NUMBER("No.", Integer.class),\r
/** tick位置 */\r
- TICK_POSITION("TickPos.", 40, Long.class),\r
+ TICK_POSITION("TickPos.", Long.class),\r
/** tick位置に対応する小節 */\r
- MEASURE_POSITION("Measure", 20, Integer.class),\r
+ MEASURE_POSITION("Measure", Integer.class),\r
/** tick位置に対応する拍 */\r
- BEAT_POSITION("Beat", 20, Integer.class),\r
+ BEAT_POSITION("Beat", Integer.class),\r
/** tick位置に対応する余剰tick(拍に収まらずに余ったtick数) */\r
- EXTRA_TICK_POSITION("ExTick", 20, Integer.class),\r
+ EXTRA_TICK_POSITION("ExTick", Integer.class),\r
/** MIDIメッセージ */\r
- MESSAGE("MIDI Message", 280, String.class);\r
+ MESSAGE("MIDI Message", String.class);\r
private String title;\r
- private int widthRatio;\r
private Class<?> columnClass;\r
/**\r
* 列の識別子を構築します。\r
* @param widthRatio 幅の割合\r
* @param columnClass 列のクラス\r
*/\r
- private Column(String title, int widthRatio, Class<?> columnClass) {\r
+ private Column(String title, Class<?> columnClass) {\r
this.title = title;\r
- this.widthRatio = widthRatio;\r
this.columnClass = columnClass;\r
}\r
- /**\r
- * 幅の割合の合計を返します。\r
- * @return 幅の割合の合計\r
- */\r
- public static int totalWidthRatio() {\r
- int total = 0;\r
- for( Column c : values() ) total += c.widthRatio;\r
- return total;\r
- }\r
}\r
/**\r
* ラップされているMIDIトラック\r
/**\r
* 親のシーケンスモデル\r
*/\r
- private SequenceTrackListTableModel parent;\r
+ SequenceTrackListTableModel parent;\r
+ /**\r
+ * 選択されているイベントのインデックス\r
+ */\r
+ ListSelectionModel selectionModel = new DefaultListSelectionModel() {\r
+ {\r
+ setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);\r
+ }\r
+ };\r
/**\r
* 空のMIDIトラックモデルを構築します。\r
*/\r
fireTableDataChanged();\r
if( MIDISpec.isEOT(msg) ) {\r
// EOTの場所が変わると曲の長さが変わるので、親モデルへ通知する。\r
- parent.sequenceListTableModel.fireSequenceModified(parent);\r
- }\r
- }\r
- /**\r
- * 列に合わせて幅を調整します。\r
- * @param columnModel テーブル列モデル\r
- */\r
- public void sizeColumnWidthToFit(TableColumnModel columnModel) {\r
- int totalWidth = columnModel.getTotalColumnWidth();\r
- int totalWidthRatio = Column.totalWidthRatio();\r
- for( Column c : Column.values() ) {\r
- int w = totalWidth * c.widthRatio / totalWidthRatio;\r
- columnModel.getColumn(c.ordinal()).setPreferredWidth(w);\r
+ parent.parent.fireSequenceModified(parent);\r
}\r
}\r
/**\r
if(name.equals(toString()) || ! MIDISpec.setNameOf(track, name))\r
return false;\r
parent.setModified(true);\r
- parent.sequenceListTableModel.fireSequenceModified(parent);\r
+ parent.parent.fireSequenceModified(parent);\r
fireTableDataChanged();\r
return true;\r
}\r
* @param recordingChannel 録音中のMIDIチャンネル\r
*/\r
public void setRecordingChannel(String recordingChannel) {\r
- Sequencer sequencer = parent.getSequencer();\r
+ Sequencer sequencer = parent.parent.sequencerModel.getSequencer();\r
if( recordingChannel.equals("OFF") ) {\r
sequencer.recordDisable( track );\r
}\r
int cmd = ((ShortMessage)msg).getCommand();\r
return cmd == ShortMessage.NOTE_ON || cmd == ShortMessage.NOTE_OFF ;\r
}\r
- public boolean hasTrack() { return track != null; }\r
/**\r
* 指定の行インデックスのMIDIイベントを返します。\r
* @param index 行インデックス\r
public MidiEvent getMidiEvent(int index) { return track.get(index); }\r
/**\r
* 選択されているMIDIイベントを返します。\r
- * @param selectionModel 選択状態モデル\r
* @return 選択されているMIDIイベント\r
*/\r
- public MidiEvent[] getMidiEvents(ListSelectionModel selectionModel) {\r
+ public MidiEvent[] getSelectedMidiEvents() {\r
Vector<MidiEvent> events = new Vector<MidiEvent>();\r
if( ! selectionModel.isSelectionEmpty() ) {\r
int i = selectionModel.getMinSelectionIndex();\r
}\r
/**\r
* MIDIイベントを除去します。\r
+ * 曲の長さが変わることがあるので、プレイリストにも通知します。\r
* @param midiEvents 除去するMIDIイベント\r
*/\r
public void removeMidiEvents(MidiEvent midiEvents[]) {\r
hadTimeSignature = true;\r
track.remove(e);\r
}\r
- if( hadTimeSignature ) parent.fireTimeSignatureChanged();\r
+ if( hadTimeSignature ) {\r
+ parent.fireTimeSignatureChanged();\r
+ }\r
parent.fireTrackChanged(track);\r
int lastIndex = track.size() - 1;\r
int oldLastIndex = lastIndex + midiEvents.length;\r
if(lastIndex < 0) lastIndex = 0;\r
fireTableRowsDeleted(oldLastIndex, lastIndex);\r
+ parent.parent.fireSelectedSequenceChanged();\r
}\r
/**\r
* 引数の選択内容が示すMIDIイベントを除去します。\r
* @param selectionModel 選択内容\r
*/\r
- public void removeMidiEvents(ListSelectionModel selectionModel) {\r
- removeMidiEvents(getMidiEvents(selectionModel));\r
+ public void removeSelectedMidiEvents() {\r
+ removeMidiEvents(getSelectedMidiEvents());\r
}\r
private boolean isRhythmPart(int ch) { return (ch == 9); }\r
/**\r