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.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.nio.charset.Charset;
-import java.security.AccessControlException;
-import java.util.Arrays;
-import java.util.EventObject;
-import java.util.Iterator;
import java.util.List;
-import javax.sound.midi.InvalidMidiDataException;
-import javax.sound.midi.MidiChannel;
-import javax.sound.midi.MidiEvent;
-import javax.sound.midi.MidiMessage;
-import javax.sound.midi.MidiSystem;
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;
-import javax.swing.DefaultCellEditor;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JDialog;
-import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
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;
-import javax.swing.event.ListSelectionEvent;
-import javax.swing.event.ListSelectionListener;
-import javax.swing.event.TableModelEvent;
-import javax.swing.filechooser.FileNameExtensionFilter;
-import javax.swing.table.JTableHeader;
-import javax.swing.table.TableCellEditor;
-import javax.swing.table.TableCellRenderer;
-import javax.swing.table.TableColumn;
-import javax.swing.table.TableColumnModel;
-import javax.swing.table.TableModel;
import camidion.chordhelper.ButtonIcon;
import camidion.chordhelper.ChordHelperApplet;
import camidion.chordhelper.mididevice.MidiSequencerModel;
import camidion.chordhelper.mididevice.VirtualMidiDevice;
-import camidion.chordhelper.music.MIDISpec;
/**
* MIDIエディタ(MIDI Editor/Playlist for MIDI Chord Helper)
*
* @author
- * Copyright (C) 2006-2016 Akiyoshi Kamide
+ * Copyright (C) 2006-2017 Akiyoshi Kamide
* http://www.yk.rim.or.jp/~kamide/music/chordhelper/
*/
public class MidiSequenceEditorDialog extends JDialog {
/**
- * このダイアログを表示するアクション
+ * このダイアログを開きます。表示されていなければ表示し、すでに表示されていたら最前面に移動します。
*/
- public Action openAction = new AbstractAction("Edit/Playlist/Speed", new ButtonIcon(ButtonIcon.EDIT_ICON)) {
+ public void open() { if( isVisible() ) toFront(); else setVisible(true); }
+ /**
+ * このダイアログを表示するボタン用アクション
+ */
+ public final Action openAction = new AbstractAction("Edit/Playlist/Speed", new ButtonIcon(ButtonIcon.EDIT_ICON)) {
{
String tooltip = "MIDIシーケンスの編集/プレイリスト/再生速度調整";
putValue(Action.SHORT_DESCRIPTION, tooltip);
}
@Override
- public void actionPerformed(ActionEvent e) {
- if( isVisible() ) toFront(); else setVisible(true);
- }
+ public void actionPerformed(ActionEvent e) { open(); }
};
/**
* ドロップされた複数のMIDIファイルを読み込むハンドラー
@Override
public boolean importData(TransferSupport support) {
try {
- play((List<File>)support.getTransferable().getTransferData(DataFlavor.javaFileListFlavor));
+ Object data = support.getTransferable().getTransferData(DataFlavor.javaFileListFlavor);
+ play((List<File>)data);
return true;
- } catch (Exception e) {
+ } catch (Exception ex) {
JOptionPane.showMessageDialog(
- MidiSequenceEditorDialog.this,
- e,
+ MidiSequenceEditorDialog.this, ex,
ChordHelperApplet.VersionInfo.NAME,
JOptionPane.ERROR_MESSAGE);
return false;
}
};
/**
+ * プレイリストビュー(シーケンスリスト)
+ */
+ public PlaylistTable playlistTable;
+ /**
* このエディタダイアログが表示しているプレイリストモデルを返します。
* @return プレイリストモデル
*/
public PlaylistTableModel getPlaylistModel() {
- return sequenceListTable.getModel();
+ return playlistTable.getModel();
}
/**
* 指定されたリストに格納されたMIDIファイルを読み込んで再生します。
* @param fileList 読み込むMIDIファイルのリスト
*/
public void play(List<File> fileList) {
- PlaylistTableModel playlist = getPlaylistModel();
- int firstIndex = -1;
- Iterator<File> itr = fileList.iterator();
- while(itr.hasNext()) {
- File file = itr.next();
- try (FileInputStream in = new FileInputStream(file)) {
- int lastIndex = playlist.add(MidiSystem.getSequence(in), file.getName());
- if( firstIndex < 0 ) firstIndex = lastIndex;
- } catch(IOException|InvalidMidiDataException e) {
- String message = "Could not open as MIDI file "+file+"\n"+e;
- if( ! itr.hasNext() ) {
- JOptionPane.showMessageDialog(this, message, ChordHelperApplet.VersionInfo.NAME, JOptionPane.WARNING_MESSAGE);
- break;
- }
- if( JOptionPane.showConfirmDialog(this,
- message + "\n\nContinue to open next file ?",
- ChordHelperApplet.VersionInfo.NAME,
- JOptionPane.YES_NO_OPTION,
- JOptionPane.WARNING_MESSAGE) != JOptionPane.YES_OPTION
- ) break;
- } catch(Exception e) {
- JOptionPane.showMessageDialog(this, e, ChordHelperApplet.VersionInfo.NAME, JOptionPane.ERROR_MESSAGE);
- break;
- }
- }
+ play(playlistTable.add(fileList));
+ }
+ /**
+ * 指定されたインデックス値(先頭が0)のMIDIシーケンスから再生します。
+ * すでに再生されていた場合、このエディタダイアログを表示します。
+ * @param index プレイリスト内にあるMIDIシーケンスのインデックス値
+ */
+ public void play(int index) {
try {
+ PlaylistTableModel playlist = getPlaylistModel();
MidiSequencerModel sequencerModel = playlist.getSequencerModel();
- if( sequencerModel.getSequencer().isRunning() ) {
- String command = (String)openAction.getValue(Action.NAME);
- openAction.actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, command));
- return;
+ if( sequencerModel.getSequencer().isRunning() ) { open(); return; }
+ if( index >= 0 ) {
+ playlist.play(index);
+ playlistTable.getSelectionModel().setSelectionInterval(index, index);
}
- if( firstIndex >= 0 ) playlist.play(firstIndex);
} catch (Exception e) {
JOptionPane.showMessageDialog(this, e, ChordHelperApplet.VersionInfo.NAME, JOptionPane.ERROR_MESSAGE);
}
}
-
- private static final Icon deleteIcon = new ButtonIcon(ButtonIcon.X_ICON);
- /**
- * 新しいMIDIシーケンスを生成するダイアログ
- */
- public NewSequenceDialog newSequenceDialog;
- /**
- * プレイリストビュー(シーケンスリスト)
- */
- public SequenceListTable sequenceListTable;
- /**
- * MIDIトラックリストテーブルビュー(選択中のシーケンスの中身)
- */
- private TrackListTable trackListTable;
- /**
- * MIDIイベントリストテーブルビュー(選択中のトラックの中身)
- */
- private EventListTable eventListTable;
- /**
- * MIDIイベント入力ダイアログ(イベント入力とイベント送出で共用)
- */
- public MidiEventDialog eventDialog = new MidiEventDialog();
- /**
- * 操作音を鳴らすMIDI出力デバイス
- */
- private VirtualMidiDevice outputMidiDevice;
/**
- * プレイリストビュー(シーケンスリスト)
+ * 現在選択されているMIDIシーケンスから再生します。
+ * すでに再生されていた場合、このエディタダイアログを表示します。
*/
- public class SequenceListTable extends JTable {
- /**
- * ファイル選択ダイアログ(アプレットの場合は使用不可なのでnull)
- */
- private MidiFileChooser midiFileChooser;
- /**
- * BASE64エンコードアクション
- */
- private Action base64EncodeAction;
- /**
- * BASE64ダイアログ
- */
- public Base64Dialog base64Dialog;
- /**
- * MIDIデバイスダイアログを開くアクション
- */
- private Action midiDeviceDialogOpenAction;
- /**
- * プレイリストビューを構築します。
- * @param model プレイリストデータモデル
- */
- public SequenceListTable(PlaylistTableModel model, Action midiDeviceDialogOpenAction) {
- super(model, null, model.sequenceListSelectionModel);
- this.midiDeviceDialogOpenAction = midiDeviceDialogOpenAction;
- try {
- midiFileChooser = new MidiFileChooser();
- }
- catch( ExceptionInInitializerError|NoClassDefFoundError|AccessControlException e ) {
- // アプレットの場合、Webクライアントマシンのローカルファイルには
- // アクセスできないので、ファイル選択ダイアログは使用不可。
- midiFileChooser = null;
- }
- // 再生ボタンを埋め込む
- new PlayButtonCellEditor();
- new PositionCellEditor();
- //
- // 文字コード選択をプルダウンにする
- int column = PlaylistTableModel.Column.CHARSET.ordinal();
- TableCellEditor ce = new DefaultCellEditor(new JComboBox<Charset>() {{
- Charset.availableCharsets().values().stream().forEach(v->addItem(v));
- }});
- getColumnModel().getColumn(column).setCellEditor(ce);
- setAutoCreateColumnsFromModel(false);
- //
- // Base64画面を開くアクションの生成
- base64Dialog = new Base64Dialog(model);
- base64EncodeAction = new AbstractAction("Base64") {
- {
- String tooltip = "Base64 text conversion - Base64テキスト変換";
- putValue(Action.SHORT_DESCRIPTION, tooltip);
- }
- @Override
- public void actionPerformed(ActionEvent e) {
- SequenceTrackListTableModel mstm = getModel().getSelectedSequenceModel();
- byte[] data = null;
- String filename = null;
- if( mstm != null ) {
- filename = mstm.getFilename();
- try {
- data = mstm.getMIDIdata();
- } catch (IOException ioe) {
- base64Dialog.setText("File["+filename+"]:"+ioe.toString());
- base64Dialog.setVisible(true);
- return;
- }
- }
- base64Dialog.setMIDIData(data, filename);
- base64Dialog.setVisible(true);
- }
- };
- TableColumnModel colModel = getColumnModel();
- Arrays.stream(PlaylistTableModel.Column.values()).forEach(c->{
- TableColumn tc = colModel.getColumn(c.ordinal());
- tc.setPreferredWidth(c.preferredWidth);
- if( c == PlaylistTableModel.Column.LENGTH ) lengthColumn = tc;
- });
- }
- private TableColumn lengthColumn;
- @Override
- public void tableChanged(TableModelEvent event) {
- super.tableChanged(event);
- //
- // タイトルに合計シーケンス長を表示
- if( lengthColumn != null ) {
- int sec = getModel().getSecondLength();
- String title = PlaylistTableModel.Column.LENGTH.title;
- title = String.format(title+" [%02d:%02d]", sec/60, sec%60);
- lengthColumn.setHeaderValue(title);
- }
- // シーケンス削除時など、合計シーケンス長が変わっても
- // 列モデルからではヘッダタイトルが再描画されないことがある。
- // そこで、ヘッダビューから repaint() で突っついて再描画させる。
- JTableHeader th = getTableHeader();
- if( th != null ) th.repaint();
- }
- /** 時間位置を表示し、ダブルクリックによるシーケンサへのロードのみを受け付けるセルエディタ */
- private class PositionCellEditor extends AbstractCellEditor implements TableCellEditor {
- public PositionCellEditor() {
- getColumnModel().getColumn(PlaylistTableModel.Column.POSITION.ordinal()).setCellEditor(this);
- }
- /**
- * セルをダブルクリックしたときだけ編集モードに入るようにします。
- * @param e イベント(マウスイベント)
- * @return 編集可能な場合true
- */
- @Override
- public boolean isCellEditable(EventObject e) {
- return (e instanceof MouseEvent) && ((MouseEvent)e).getClickCount() == 2;
- }
- @Override
- public Object getCellEditorValue() { return null; }
- /**
- * 編集モード時のコンポーネントを返すタイミングで
- * そのシーケンスをシーケンサーにロードしたあと、すぐに編集モードを解除します。
- * @return 常にnull
- */
- @Override
- public Component getTableCellEditorComponent(
- JTable table, Object value, boolean isSelected, int row, int column
- ) {
- try {
- getModel().loadToSequencer(row);
- } catch (InvalidMidiDataException|IllegalStateException e) {
- JOptionPane.showMessageDialog(
- SequenceListTable.this.getRootPane(),
- e,
- ChordHelperApplet.VersionInfo.NAME,
- JOptionPane.ERROR_MESSAGE);
- }
- fireEditingStopped();
- return null;
- }
- }
- /** 再生ボタンを埋め込んだセルの編集、描画を行うクラスです。 */
- private class PlayButtonCellEditor extends AbstractCellEditor implements TableCellEditor, TableCellRenderer {
- /** 埋め込み用の再生ボタン */
- private JToggleButton playButton = new JToggleButton(getModel().getSequencerModel().getStartStopAction()) {
- { setMargin(ChordHelperApplet.ZERO_INSETS); }
- };
- /**
- * 埋め込み用のMIDIデバイス接続ボタン(そのシーケンスをロードしているシーケンサが開いていなかったときに表示)
- */
- private JButton midiDeviceConnectionButton = new JButton(midiDeviceDialogOpenAction) {
- { setMargin(ChordHelperApplet.ZERO_INSETS); }
- };
- /**
- * 再生ボタンを埋め込むセルエディタを構築し、列に対するレンダラ、エディタとして登録します。
- */
- public PlayButtonCellEditor() {
- TableColumn tc = getColumnModel().getColumn(PlaylistTableModel.Column.PLAY.ordinal());
- tc.setCellRenderer(this);
- tc.setCellEditor(this);
- }
- /**
- * {@inheritDoc}
- *
- * <p>この実装では、クリックしたセルのシーケンスがシーケンサーで再生可能な場合に
- * trueを返して再生ボタンを押せるようにします。
- * それ以外のセルについては、新たにシーケンサーへのロードを可能にするため、
- * ダブルクリックされたときだけtrueを返します。
- * </p>
- */
- @Override
- public boolean isCellEditable(EventObject e) {
- // マウスイベントのみを受け付け、それ以外はデフォルトエディタに振る
- if( ! (e instanceof MouseEvent) ) return super.isCellEditable(e);
- //
- // エディタが編集を終了したことをリスナーに通知
- fireEditingStopped();
- //
- // クリックされたセルの行位置を把握(欄外だったら編集不可)
- MouseEvent me = (MouseEvent)e;
- int row = rowAtPoint(me.getPoint());
- if( row < 0 ) return false;
- //
- // シーケンサーにロード済みの場合は、シングルクリックを受け付ける。
- // それ以外は、ダブルクリックのみ受け付ける。
- return getModel().getSequenceModelList().get(row).isOnSequencer() || me.getClickCount() == 2;
- }
- @Override
- public Object getCellEditorValue() { return null; }
- /**
- * {@inheritDoc}
- *
- * <p>この実装では、行の表すシーケンスの状態に応じたボタンを表示します。
- * それ以外の場合は、新たにそのシーケンスをシーケンサーにロードしますが、
- * 以降の編集は不可としてnullを返します。
- * </p>
- */
- @Override
- public Component getTableCellEditorComponent(
- JTable table, Object value, boolean isSelected, int row, int column
- ) {
- fireEditingStopped();
- PlaylistTableModel model = getModel();
- if( model.getSequenceModelList().get(row).isOnSequencer() ) {
- return model.getSequencerModel().getSequencer().isOpen() ? playButton : midiDeviceConnectionButton;
- }
- try {
- model.loadToSequencer(row);
- } catch (InvalidMidiDataException e) {
- JOptionPane.showMessageDialog(
- SequenceListTable.this.getRootPane(),
- e,
- ChordHelperApplet.VersionInfo.NAME,
- JOptionPane.ERROR_MESSAGE);
- }
- return null;
- }
- /**
- * {@inheritDoc}
- *
- * <p>この実装では、行の表すシーケンスの状態に応じたボタンを表示します。
- * それ以外の場合はデフォルトレンダラーに描画させます。
- * </p>
- */
- @Override
- public Component getTableCellRendererComponent(
- JTable table, Object value, boolean isSelected,
- boolean hasFocus, int row, int column
- ) {
- PlaylistTableModel model = getModel();
- if( model.getSequenceModelList().get(row).isOnSequencer() ) {
- return model.getSequencerModel().getSequencer().isOpen() ? playButton : midiDeviceConnectionButton;
- }
- return table.getDefaultRenderer(model.getColumnClass(column))
- .getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
- }
- }
- /**
- * このプレイリスト(シーケンスリスト)が表示するデータを提供する
- * プレイリストモデルを返します。
- * @return プレイリストモデル
- */
- @Override
- public PlaylistTableModel getModel() {
- return (PlaylistTableModel)super.getModel();
- }
- /**
- * シーケンスを削除するアクション
- */
- Action deleteSequenceAction = getModel().new SelectedSequenceAction(
- "Delete", MidiSequenceEditorDialog.deleteIcon,
- "Delete selected MIDI sequence - 選択した曲をプレイリストから削除"
- ) {
- private static final String CONFIRM_MESSAGE =
- "Selected MIDI sequence not saved - delete it from the playlist ?\n" +
- "選択したMIDIシーケンスはまだ保存されていません。プレイリストから削除しますか?";
- @Override
- public void actionPerformed(ActionEvent event) {
- PlaylistTableModel model = getModel();
- if( midiFileChooser != null ) {
- SequenceTrackListTableModel seqModel = model.getSelectedSequenceModel();
- if( seqModel != null && seqModel.isModified() && JOptionPane.showConfirmDialog(
- SequenceListTable.this.getRootPane(),
- CONFIRM_MESSAGE,
- ChordHelperApplet.VersionInfo.NAME,
- JOptionPane.YES_NO_OPTION,
- JOptionPane.WARNING_MESSAGE) != JOptionPane.YES_OPTION
- ) return;
- }
- try {
- model.removeSelectedSequence();
- } catch (InvalidMidiDataException|IllegalStateException ex) {
- JOptionPane.showMessageDialog(
- SequenceListTable.this.getRootPane(),
- ex,
- ChordHelperApplet.VersionInfo.NAME,
- JOptionPane.ERROR_MESSAGE);
- }
- }
- };
- /**
- * ファイル選択ダイアログ(アプレットでは使用不可)
- */
- private class MidiFileChooser extends JFileChooser {
- { setFileFilter(new FileNameExtensionFilter("MIDI sequence (*.mid)", "mid")); }
- /**
- * ファイル保存アクション
- */
- public Action saveMidiFileAction = getModel().new SelectedSequenceAction(
- "Save",
- "Save selected MIDI sequence to file - 選択したMIDIシーケンスをファイルに保存"
- ) {
- @Override
- public void actionPerformed(ActionEvent event) {
- 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;
- File f = getSelectedFile();
- if( f.exists() ) {
- fn = f.getName();
- if( JOptionPane.showConfirmDialog(
- SequenceListTable.this.getRootPane(),
- "Overwrite " + fn + " ?\n" + fn + " を上書きしてよろしいですか?",
- ChordHelperApplet.VersionInfo.NAME,
- JOptionPane.YES_NO_OPTION,
- JOptionPane.WARNING_MESSAGE) != JOptionPane.YES_OPTION
- ) return;
- }
- try ( FileOutputStream o = new FileOutputStream(f) ) {
- o.write(sequenceModel.getMIDIdata());
- sequenceModel.setModified(false);
- }
- catch( Exception ex ) {
- JOptionPane.showMessageDialog(
- SequenceListTable.this.getRootPane(),
- ex,
- ChordHelperApplet.VersionInfo.NAME,
- JOptionPane.ERROR_MESSAGE);
- }
- }
- };
- /**
- * ファイルを開くアクション
- */
- public Action openMidiFileAction = new AbstractAction("Open") {
- { putValue(Action.SHORT_DESCRIPTION, "Open MIDI file - MIDIファイルを開く"); }
- @Override
- public void actionPerformed(ActionEvent event) {
- try {
- if( showOpenDialog((Component)event.getSource()) == JFileChooser.APPROVE_OPTION ) {
- play(Arrays.asList(getSelectedFile()));
- }
- } catch( Exception ex ) {
- JOptionPane.showMessageDialog(
- SequenceListTable.this.getRootPane(),
- ex,
- ChordHelperApplet.VersionInfo.NAME,
- JOptionPane.ERROR_MESSAGE);
- }
- }
- };
- };
+ public void play() {
+ play(playlistTable.getSelectedRow());
}
+ static final Icon deleteIcon = new ButtonIcon(ButtonIcon.X_ICON);
/**
- * シーケンス(トラックリスト)テーブルビュー
- */
- public class TrackListTable extends JTable {
- /**
- * トラックリストテーブルビューを構築します。
- * @param model シーケンス(トラックリスト)データモデル
- */
- public TrackListTable(SequenceTrackListTableModel model) {
- super(model, null, model.getSelectionModel());
- //
- // 録音対象のMIDIチャンネルをコンボボックスで選択できるようにする
- getColumnModel()
- .getColumn(SequenceTrackListTableModel.Column.RECORD_CHANNEL.ordinal())
- .setCellEditor(new DefaultCellEditor(new JComboBox<String>(){{
- addItem("OFF");
- for(int i=1; i <= MIDISpec.MAX_CHANNELS; i++) addItem(String.format("%d", i));
- addItem("ALL");
- }}));
- setAutoCreateColumnsFromModel(false);
- model.getParent().sequenceListSelectionModel.addListSelectionListener(titleLabel);
- TableColumnModel colModel = getColumnModel();
- Arrays.stream(SequenceTrackListTableModel.Column.values()).forEach(c->
- colModel.getColumn(c.ordinal()).setPreferredWidth(c.preferredWidth)
- );
- }
- /**
- * このテーブルビューが表示するデータを提供する
- * シーケンス(トラックリスト)データモデルを返します。
- * @return シーケンス(トラックリスト)データモデル
- */
- @Override
- public SequenceTrackListTableModel getModel() {
- return (SequenceTrackListTableModel) super.getModel();
- }
- /**
- * タイトルラベル
- */
- TitleLabel titleLabel = new TitleLabel();
- /**
- * 親テーブルの選択シーケンスの変更に反応する
- * 曲番号表示付きタイトルラベル
- */
- private class TitleLabel extends JLabel implements ListSelectionListener {
- private static final String TITLE = "Tracks";
- public TitleLabel() { setText(TITLE); }
- @Override
- public void valueChanged(ListSelectionEvent event) {
- if( event.getValueIsAdjusting() ) return;
- SequenceTrackListTableModel oldModel = getModel();
- SequenceTrackListTableModel newModel = oldModel.getParent().getSelectedSequenceModel();
- if( oldModel == newModel ) return;
- //
- // MIDIチャンネル選択中のときはキャンセルする
- cancelCellEditing();
- //
- String text = TITLE;
- ListSelectionModel sm = oldModel.getParent().sequenceListSelectionModel;
- if( ! sm.isSelectionEmpty() ) {
- int index = sm.getMinSelectionIndex();
- if( index >= 0 ) text = String.format(text+" - MIDI file #%d", index);
- }
- setText(text);
- if( newModel == null ) {
- newModel = oldModel.getParent().emptyTrackListTableModel;
- addTrackAction.setEnabled(false);
- }
- else {
- addTrackAction.setEnabled(true);
- }
- oldModel.getSelectionModel().removeListSelectionListener(trackSelectionListener);
- setModel(newModel);
- setSelectionModel(newModel.getSelectionModel());
- newModel.getSelectionModel().addListSelectionListener(trackSelectionListener);
- trackSelectionListener.valueChanged(null);
- }
- }
- /**
- * トラック選択リスナー
- */
- ListSelectionListener trackSelectionListener = new ListSelectionListener() {
- @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());
- }
- };
- /**
- * {@inheritDoc}
- *
- * <p>このトラックリストテーブルのデータが変わったときに編集を解除します。
- * 例えば、イベントが編集された場合や、
- * シーケンサーからこのモデルが外された場合がこれに該当します。
- * </p>
- */
- @Override
- public void tableChanged(TableModelEvent e) {
- super.tableChanged(e);
- cancelCellEditing();
- }
- /**
- * このトラックリストテーブルが編集モードになっていたら解除します。
- */
- private void cancelCellEditing() {
- TableCellEditor currentCellEditor = getCellEditor();
- if( currentCellEditor != null ) currentCellEditor.cancelCellEditing();
- }
- /**
- * トラック追加アクション
- */
- Action addTrackAction = new AbstractAction("New") {
- {
- String tooltip = "Append new track - 新しいトラックの追加";
- putValue(Action.SHORT_DESCRIPTION, tooltip);
- setEnabled(false);
- }
- @Override
- public void actionPerformed(ActionEvent e) { getModel().createTrack(); }
- };
- /**
- * トラック削除アクション
- */
- Action deleteTrackAction = new AbstractAction("Delete", deleteIcon) {
- public static final String CONFIRM_MESSAGE =
- "Do you want to delete selected track ?\n選択したトラックを削除しますか?";
- {
- putValue(Action.SHORT_DESCRIPTION, "Delete selected track - 選択したトラックを削除");
- setEnabled(false);
- }
- @Override
- public void actionPerformed(ActionEvent e) {
- if( JOptionPane.showConfirmDialog(
- TrackListTable.this.getRootPane(),
- CONFIRM_MESSAGE,
- ChordHelperApplet.VersionInfo.NAME,
- JOptionPane.YES_NO_OPTION,
- JOptionPane.WARNING_MESSAGE) == JOptionPane.YES_OPTION
- ) getModel().deleteSelectedTracks();
- }
- };
- }
-
- /**
- * MIDIイベントリストテーブルビュー(選択中のトラックの中身)
+ * 新しい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);
- }
- }
-
+ public NewSequenceDialog newSequenceDialog;
/**
* 新しい {@link MidiSequenceEditorDialog} を構築します。
* @param playlistTableModel このエディタが参照するプレイリストモデル
+ * @param eventDialog MIDIイベント入力ダイアログ
* @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);
- newSequenceDialog = new NewSequenceDialog(playlistTableModel, outputMidiDevice);
+ public MidiSequenceEditorDialog(PlaylistTableModel playlistTableModel, MidiEventDialog eventDialog, VirtualMidiDevice outputMidiDevice, Action midiDeviceDialogOpenAction) {
+ MidiEventTable eventListTable = new MidiEventTable(playlistTableModel.emptyEventListTableModel, eventDialog, outputMidiDevice);
+ SequenceTrackListTable trackListTable = new SequenceTrackListTable(playlistTableModel.emptyTrackListTableModel, eventListTable);
+ playlistTable = new PlaylistTable(playlistTableModel, midiDeviceDialogOpenAction, trackListTable);
+ newSequenceDialog = new NewSequenceDialog(playlistTable, outputMidiDevice);
setTitle("MIDI Editor/Playlist - "+ChordHelperApplet.VersionInfo.NAME);
setBounds( 150, 200, 900, 500 );
setLayout(new FlowLayout());
add(new JButton(newSequenceDialog.openAction) {
{ setMargin(ChordHelperApplet.ZERO_INSETS); }
});
- if( sequenceListTable.midiFileChooser != null ) {
+ if( playlistTable.midiFileChooser != null ) {
add( Box.createRigidArea(new Dimension(5, 0)) );
- add(new JButton(sequenceListTable.midiFileChooser.openMidiFileAction) {
+ add(new JButton(playlistTable.midiFileChooser.openMidiFileAction) {
{ setMargin(ChordHelperApplet.ZERO_INSETS); }
});
}
- if(sequenceListTable.base64EncodeAction != null) {
- add(Box.createRigidArea(new Dimension(5, 0)));
- add(new JButton(sequenceListTable.base64EncodeAction) {{ setMargin(ChordHelperApplet.ZERO_INSETS); }});
- }
+ add(Box.createRigidArea(new Dimension(5, 0)));
+ add(new JButton(playlistTable.base64EncodeAction) {
+ { setMargin(ChordHelperApplet.ZERO_INSETS); }
+ });
add(Box.createRigidArea(new Dimension(5, 0)));
add(new JButton(playlistTableModel.getMoveToTopAction()) {
{ setMargin(ChordHelperApplet.ZERO_INSETS); }
add(new JButton(playlistTableModel.getMoveToBottomAction()) {
{ setMargin(ChordHelperApplet.ZERO_INSETS); }
});
- if( sequenceListTable.midiFileChooser != null ) {
+ if( playlistTable.midiFileChooser != null ) {
add(Box.createRigidArea(new Dimension(5, 0)));
- add(new JButton(sequenceListTable.midiFileChooser.saveMidiFileAction) {
+ add(new JButton(playlistTable.midiFileChooser.saveMidiFileAction) {
{ setMargin(ChordHelperApplet.ZERO_INSETS); }
});
}
add( Box.createRigidArea(new Dimension(5, 0)) );
- add(new JButton(sequenceListTable.deleteSequenceAction) {
+ add(new JButton(playlistTable.deleteSequenceAction) {
{ setMargin(ChordHelperApplet.ZERO_INSETS); }
});
add( Box.createRigidArea(new Dimension(5, 0)) );
}});
}};
setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
- add(new JScrollPane(sequenceListTable));
+ add(new JScrollPane(playlistTable));
add(Box.createRigidArea(new Dimension(0, 10)));
add(playlistOperationPanel);
add(Box.createRigidArea(new Dimension(0, 10)));