OSDN Git Service

USB MIDI デバイスの着脱後、アプリを終了しなくてもデバイスツリー表示の更新ができるようにした
authorAkiyoshi Kamide <kamide@yk.rim.or.jp>
Wed, 5 Oct 2016 16:51:45 +0000 (01:51 +0900)
committerAkiyoshi Kamide <kamide@yk.rim.or.jp>
Wed, 5 Oct 2016 16:51:45 +0000 (01:51 +0900)
13 files changed:
src/camidion/chordhelper/ChordHelperApplet.java
src/camidion/chordhelper/mididevice/MidiCablePane.java
src/camidion/chordhelper/mididevice/MidiDeviceDesktopPane.java [moved from src/camidion/chordhelper/mididevice/MidiOpenedDevicesView.java with 54% similarity]
src/camidion/chordhelper/mididevice/MidiDeviceDialog.java
src/camidion/chordhelper/mididevice/MidiDeviceFrame.java
src/camidion/chordhelper/mididevice/MidiDeviceModel.java
src/camidion/chordhelper/mididevice/MidiDeviceModelList.java [deleted file]
src/camidion/chordhelper/mididevice/MidiDeviceModelManager.java [new file with mode: 0644]
src/camidion/chordhelper/mididevice/MidiDeviceTreeModel.java
src/camidion/chordhelper/mididevice/MidiDeviceTreeView.java
src/camidion/chordhelper/mididevice/MidiSequencerModel.java
src/camidion/chordhelper/mididevice/ReceiverListModel.java
src/camidion/chordhelper/mididevice/TransmitterListModel.java

index 9278f74..28d771f 100644 (file)
@@ -56,7 +56,7 @@ import camidion.chordhelper.chordmatrix.ChordButtonLabel;
 import camidion.chordhelper.chordmatrix.ChordMatrix;
 import camidion.chordhelper.chordmatrix.ChordMatrixListener;
 import camidion.chordhelper.mididevice.MidiDeviceDialog;
-import camidion.chordhelper.mididevice.MidiDeviceModelList;
+import camidion.chordhelper.mididevice.MidiDeviceModelManager;
 import camidion.chordhelper.mididevice.MidiSequencerModel;
 import camidion.chordhelper.mididevice.SequencerMeasureView;
 import camidion.chordhelper.mididevice.SequencerTimeView;
@@ -283,7 +283,7 @@ public class ChordHelperApplet extends JApplet {
         */
        public static class VersionInfo {
                public static final String      NAME = "MIDI Chord Helper";
-               public static final String      VERSION = "Ver.20160922.1";
+               public static final String      VERSION = "Ver.20161005.1";
                public static final String      COPYRIGHT = "Copyright (C) 2004-2016";
                public static final String      AUTHER = "@きよし - Akiyoshi Kamide";
                public static final String      URL = "http://www.yk.rim.or.jp/~kamide/music/chordhelper/";
@@ -406,7 +406,7 @@ public class ChordHelperApplet extends JApplet {
        private KeySignatureLabel keysigLabel;
        private AnoGakkiPane anoGakkiPane;
        private JToggleButton anoGakkiToggleButton;
-       private MidiDeviceModelList deviceModelList;
+       private MidiDeviceModelManager deviceModelManager;
 
        public void init() {
                loadIconImage();
@@ -463,13 +463,13 @@ public class ChordHelperApplet extends JApplet {
                }};
                VirtualMidiDevice guiMidiDevice = keyboardPanel.keyboardCenterPanel.keyboard.midiDevice;
                //
-               // MIDIデバイス一覧を構築
-               deviceModelList = new MidiDeviceModelList(Arrays.asList(guiMidiDevice));
-               MidiDeviceDialog midiDeviceDialog = new MidiDeviceDialog(deviceModelList);
+               // MIDIデバイスマネージャを構築
+               deviceModelManager = new MidiDeviceModelManager(guiMidiDevice);
+               MidiDeviceDialog midiDeviceDialog = new MidiDeviceDialog(deviceModelManager);
                midiDeviceDialog.setIconImage(iconImage);
                //
                // MIDIデバイス一覧のシーケンサと連携するプレイリストを構築
-               playlistModel = new PlaylistTableModel(sequencerModel = deviceModelList.getSequencerModel());
+               playlistModel = new PlaylistTableModel(sequencerModel = deviceModelManager.getSequencerModel());
                //
                // MIDIエディタダイアログの構築
                (midiEditor = new MidiSequenceEditorDialog(playlistModel, guiMidiDevice)).setIconImage(iconImage);
@@ -708,7 +708,7 @@ public class ChordHelperApplet extends JApplet {
        }
        @Override
        public void destroy() {
-               deviceModelList.closeAllDevices();
+               deviceModelManager.close();
                super.destroy();
        }
        @Override
index ccb9107..43adf8f 100644 (file)
@@ -20,6 +20,7 @@ import java.util.Arrays;
 import java.util.Hashtable;
 import java.util.List;
 
+import javax.sound.midi.MidiDevice;
 import javax.sound.midi.Receiver;
 import javax.sound.midi.Transmitter;
 import javax.swing.JComponent;
@@ -132,7 +133,8 @@ public class MidiCablePane extends JComponent {
                        if( ! (frame instanceof MidiDeviceFrame) ) return;
                        MidiDeviceFrame f = (MidiDeviceFrame)frame;
                        MidiDeviceModel m = f.getMidiDeviceModel();
-                       List<Receiver> rxList = m.getMidiDevice().getReceivers();
+                       MidiDevice d = m.getMidiDevice();
+                       List<Receiver> rxList = d.getReceivers();
                        for( Receiver rx : rxList ) rxToColor.remove(rx);
                        repaint();
                }
@@ -150,8 +152,8 @@ public class MidiCablePane extends JComponent {
                public void intervalRemoved(ListDataEvent e) { repaint(); }
        };
 
-       private MidiOpenedDevicesView desktopPane;
-       public MidiCablePane(MidiOpenedDevicesView desktopPane) {
+       private MidiDeviceDesktopPane desktopPane;
+       public MidiCablePane(MidiDeviceDesktopPane desktopPane) {
                this.desktopPane = desktopPane;
                setOpaque(false);
                setVisible(true);
@@ -201,14 +203,10 @@ public class MidiCablePane extends JComponent {
                super.paint(g);
                Graphics2D g2 = (Graphics2D)g;
                JInternalFrame[] frames = desktopPane.getAllFramesInLayer(JLayeredPane.DEFAULT_LAYER);
-               //
-               // 各フレームをスキャン
-               for( JInternalFrame frame : frames ) {
-                       if( ! (frame instanceof MidiDeviceFrame) ) continue;
-                       MidiDeviceFrame fromFrame = (MidiDeviceFrame)frame;
-                       MidiDeviceModel fromDeviceModel = fromFrame.getMidiDeviceModel();
-                       //
-                       // Transmitterをスキャン
+               for( JInternalFrame fromFrame : frames ) {
+                       if( ! (fromFrame instanceof MidiDeviceFrame) || ! fromFrame.isVisible() ) continue;
+                       MidiDeviceFrame fromDeviceFrame = (MidiDeviceFrame)fromFrame;
+                       MidiDeviceModel fromDeviceModel = fromDeviceFrame.getMidiDeviceModel();
                        TransmitterListModel txListModel = fromDeviceModel.getTransmitterListModel();
                        int ntx = txListModel == null ? 0 : txListModel.getSize();
                        for( int index=0 ; index < ntx; index++ ) {
@@ -219,7 +217,7 @@ public class MidiCablePane extends JComponent {
                                        continue;
                                }
                                // Transmitterの表示場所を特定
-                               Rectangle txBounds = fromFrame.getBoundsOf(tx);
+                               Rectangle txBounds = fromDeviceFrame.getBoundsOf(tx);
                                if( txBounds == null ) continue;
                                int r = (txBounds.height - 5) / 2;
                                txBounds.translate(r+4, r+4);
@@ -228,8 +226,9 @@ public class MidiCablePane extends JComponent {
                                if( rx != null ) {
                                        // Receiverの表示場所を探す
                                        for( JInternalFrame toFrame : frames ) {
-                                               if( ! (toFrame instanceof MidiDeviceFrame) ) continue;
-                                               Rectangle rxBounds = ((MidiDeviceFrame)toFrame).getBoundsOf(rx);
+                                               if( ! (toFrame instanceof MidiDeviceFrame) || ! toFrame.isVisible()) continue;
+                                               MidiDeviceFrame toDeviceFrame = (MidiDeviceFrame)toFrame;
+                                               Rectangle rxBounds = toDeviceFrame.getBoundsOf(rx);
                                                if( rxBounds == null ) continue;
                                                r = (rxBounds.height - 5) / 2;
                                                rxBounds.translate(r+4, r+4);
@@ -256,7 +255,7 @@ public class MidiCablePane extends JComponent {
                        // Receiverからドラッグ中のケーブルを描画
                        if( draggingLocation != null && fromDeviceModel.getMidiDevice().getReceivers().contains(draggingSource) ) {
                                Receiver rx = (Receiver)draggingSource;
-                               Rectangle rxBounds = fromFrame.getBoundsOf(rx);
+                               Rectangle rxBounds = fromDeviceFrame.getBoundsOf(rx);
                                if( rxBounds != null ) {
                                        int r = (rxBounds.height - 5) / 2;
                                        rxBounds.translate(r+4, r+4);
@@ -4,7 +4,9 @@ import java.awt.datatransfer.DataFlavor;
 import java.awt.event.ComponentAdapter;
 import java.awt.event.ComponentEvent;
 import java.beans.PropertyVetoException;
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 import javax.sound.midi.MidiUnavailableException;
@@ -13,6 +15,8 @@ import javax.swing.JInternalFrame;
 import javax.swing.JLayeredPane;
 import javax.swing.JOptionPane;
 import javax.swing.TransferHandler;
+import javax.swing.event.TreeModelEvent;
+import javax.swing.event.TreeModelListener;
 import javax.swing.event.TreeSelectionEvent;
 import javax.swing.event.TreeSelectionListener;
 import javax.swing.tree.TreePath;
@@ -20,11 +24,17 @@ import javax.swing.tree.TreePath;
 /**
  * 開いているMIDIデバイスを置くためのデスクトップビュー
  */
-public class MidiOpenedDevicesView extends JDesktopPane implements TreeSelectionListener {
+public class MidiDeviceDesktopPane extends JDesktopPane implements TreeSelectionListener {
        /**
         * MIDIデバイスモデルからフレームを割り出すためのマップ
         */
-       private Map<MidiDeviceModel, MidiDeviceFrame> modelToFrame = new HashMap<>();
+       private Map<MidiDeviceModel, MidiDeviceFrame> frameMap = new HashMap<>();
+       /**
+        * MIDIデバイスモデルに対応するMIDIデバイスフレームを返すマップを返します。
+        */
+       public Map<MidiDeviceModel, MidiDeviceFrame> getFrameMap() {
+               return frameMap;
+       }
        /**
         * ツリー上で選択状態が変わったとき、このデスクトップ上のフレームの選択状態に反映します。
         */
@@ -37,7 +47,8 @@ public class MidiOpenedDevicesView extends JDesktopPane implements TreeSelection
                                MidiDeviceModel deviceModel = (MidiDeviceModel)lastSelected;
                                if( deviceModel.getMidiDevice().isOpen() ) {
                                        // 開いているMIDIデバイスがツリー上で選択されたら、対応するフレームを選択
-                                       MidiDeviceFrame deviceFrame = modelToFrame.get(deviceModel);
+                                       MidiDeviceFrame deviceFrame = frameMap.get(deviceModel);
+                                       if( deviceFrame == null ) return;
                                        deviceFrame.toFront();
                                        try {
                                                deviceFrame.setSelected(true);
@@ -58,11 +69,13 @@ public class MidiOpenedDevicesView extends JDesktopPane implements TreeSelection
                }
        }
 
-       public MidiOpenedDevicesView(MidiDeviceTreeView deviceTreeView,
+       public MidiDeviceDesktopPane(MidiDeviceTreeView deviceTreeView,
                        MidiDeviceInfoPane deviceInfoPane, MidiDeviceDialog dialog)
        {
                MidiCablePane cablePane = new MidiCablePane(this);
                add(cablePane, JLayeredPane.PALETTE_LAYER);
+               //
+               // リサイズ時、表示時にMIDIケーブルを再描画
                addComponentListener(new ComponentAdapter() {
                        @Override
                        public void componentResized(ComponentEvent e) {
@@ -72,41 +85,78 @@ public class MidiOpenedDevicesView extends JDesktopPane implements TreeSelection
                        @Override
                        public void componentShown(ComponentEvent e) { cablePane.repaint(); }
                });
+               // デバイスツリーが変更されたときの更新処理を予約
+               MidiDeviceTreeModel deviceTreeModel = deviceTreeView.getModel();
+               List<MidiDeviceModel> deviceModelList = deviceTreeModel.getDeviceModelList();
+               TreeModelListener treeModelListener = new TreeModelListener() {
+                       @Override
+                       public void treeNodesChanged(TreeModelEvent e) { }
+                       @Override
+                       public void treeNodesInserted(TreeModelEvent e) { }
+                       @Override
+                       public void treeNodesRemoved(TreeModelEvent e) { }
+                       @Override
+                       public void treeStructureChanged(TreeModelEvent e) {
+                               //
+                               // 削除されたデバイスモデルに対するデバイスフレームをマップから外す
+                               List<MidiDeviceModel> removingDeviceModels = new ArrayList<>();
+                               for( MidiDeviceModel m : frameMap.keySet() ) {
+                                       if( ! deviceModelList.contains(m) ) removingDeviceModels.add(m);
+                               }
+                               for( MidiDeviceModel m : removingDeviceModels ) {
+                                       MidiDeviceFrame frame = frameMap.remove(m);
+                                       if( frame != null ) remove(frame);
+                               }
+                               removingDeviceModels.clear();
+                               //
+                               // 新しいデバイスモデルに対するデバイスフレームを生成してマップに登録
+                               for( MidiDeviceModel deviceModel : deviceModelList ) {
+                                       if( frameMap.containsKey(deviceModel) ) continue;
+                                       MidiDeviceFrame frame = new MidiDeviceFrame(deviceModel, cablePane);
+                                       frameMap.put(deviceModel, frame);
+                                       //
+                                       // トランスミッタリストモデルが変化したときにMIDIケーブルを再描画
+                                       TransmitterListModel txListModel = deviceModel.getTransmitterListModel();
+                                       if( txListModel != null ) txListModel.addListDataListener(cablePane.midiConnecterListDataListener);
+                                       //
+                                       // レシーバリストモデルが変化したときにMIDIケーブルを再描画
+                                       ReceiverListModel rxListModel = deviceModel.getReceiverListModel();
+                                       if( rxListModel != null ) rxListModel.addListDataListener(cablePane.midiConnecterListDataListener);
+                                       //
+                                       // デバイスフレームが開閉したときの動作
+                                       frame.addInternalFrameListener(cablePane.midiDeviceFrameListener);
+                                       frame.addInternalFrameListener(deviceTreeView.midiDeviceFrameListener);
+                                       frame.addInternalFrameListener(deviceInfoPane.midiDeviceFrameListener);
+                                       //
+                                       // 移動または変形時の動作
+                                       frame.addComponentListener(cablePane.midiDeviceFrameComponentListener);
+                                       //
+                                       // サイズを設定したフレームをデスクトップに追加
+                                       frame.setSize(250, deviceModel.getInOutType() == MidiDeviceInOutType.MIDI_IN_OUT ? 90 : 70);
+                                       add(frame);
+                                       //
+                                       // デバイスが開いていたら表示
+                                       if( deviceModel.getMidiDevice().isOpen() ) {
+                                               frame.setVisible(true);
+                                       }
+                               }
+                       }
+               };
+               deviceTreeModel.addTreeModelListener(treeModelListener);
+               treeModelListener.treeStructureChanged(null);
+               //
+               // 表示したデバイスフレームを整列
                int toX = 10;
                int toY = 10;
-               MidiDeviceModelList deviceModels = deviceTreeView.getModel().getDeviceModelList();
-               for( MidiDeviceModel deviceModel : deviceModels ) {
-                       MidiDeviceFrame frame = new MidiDeviceFrame(deviceModel, cablePane);
-                       modelToFrame.put(deviceModel, frame);
-                       //
-                       // トランスミッタリストモデルが変化したときにMIDIケーブルを再描画
-                       TransmitterListModel txListModel = deviceModel.getTransmitterListModel();
-                       if( txListModel != null ) txListModel.addListDataListener(cablePane.midiConnecterListDataListener);
-                       //
-                       // レシーバリストモデルが変化したときにMIDIケーブルを再描画
-                       ReceiverListModel rxListModel = deviceModel.getReceiverListModel();
-                       if( rxListModel != null ) rxListModel.addListDataListener(cablePane.midiConnecterListDataListener);
-                       //
-                       // デバイスフレームが開閉したときの動作
-                       frame.addInternalFrameListener(cablePane.midiDeviceFrameListener);
-                       frame.addInternalFrameListener(deviceTreeView.midiDeviceFrameListener);
-                       frame.addInternalFrameListener(deviceInfoPane.midiDeviceFrameListener);
-                       //
-                       // 移動または変形時の動作
-                       frame.addComponentListener(cablePane.midiDeviceFrameComponentListener);
-                       //
-                       // ダイアログが閉じたときの動作
-                       dialog.addWindowListener(frame.windowListener);
-                       //
-                       frame.setSize(250, deviceModel.getMidiDeviceInOutType() == MidiDeviceInOutType.MIDI_IN_OUT ? 90 : 70);
-                       add(frame);
-                       if( deviceModel.getMidiDevice().isOpen() ) {
-                               frame.setLocation(toX, toY);
-                               frame.setVisible(true);
-                               toX = (toX == 10 ? 270 : 10);
-                               toY += 50;
-                       }
+               for( MidiDeviceModel deviceModel : deviceModelList ) {
+                       if( ! deviceModel.getMidiDevice().isOpen() ) continue;
+                       frameMap.get(deviceModel).setLocation(toX, toY);
+                       toX = (toX == 10 ? 270 : 10);
+                       toY += 50;
                }
+               deviceTreeView.expandAll();
+               //
+               // ドロップ設定
                setTransferHandler(new TransferHandler() {
                        @Override
                        public boolean canImport(TransferSupport support) {
@@ -135,11 +185,12 @@ public class MidiOpenedDevicesView extends JDesktopPane implements TreeSelection
                                MidiDeviceModel deviceModel = null;
                                try {
                                        deviceModel = (MidiDeviceModel)support.getTransferable().getTransferData(flavor);
+                                       MidiDeviceFrame deviceFrame = frameMap.get(deviceModel);
+                                       if( deviceFrame == null ) return false;
                                        deviceModel.open();
                                        if( ! deviceModel.getMidiDevice().isOpen() ) {
                                                throw new MidiUnavailableException("開いたはずのMIDIデバイスが、開かれた状態になっていません。");
                                        }
-                                       MidiDeviceFrame deviceFrame = modelToFrame.get(deviceModel);
                                        if( ! deviceFrame.isVisible() ) {
                                                deviceFrame.setLocation(support.getDropLocation().getDropPoint());
                                                deviceFrame.setVisible(true);
index 085ef24..efecea3 100644 (file)
@@ -20,6 +20,7 @@ import camidion.chordhelper.ButtonIcon;
  */
 public class MidiDeviceDialog extends JDialog {
        public static final Icon MIDI_CONNECTER_ICON = new ButtonIcon(ButtonIcon.MIDI_CONNECTOR_ICON);
+       public static final String MSGS = "Microsoft GS Wavetable Synth";
        /**
         * MIDIデバイスダイアログを開くアクション
         */
@@ -36,16 +37,15 @@ public class MidiDeviceDialog extends JDialog {
        };
        /**
         * MIDIデバイスダイアログを構築します。
-        * @param deviceModelList デバイスモデル(MIDIコネクタリストモデル)のリスト
+        * @param deviceModelManager デバイスモデルマネージャ
         */
-       public MidiDeviceDialog(final MidiDeviceModelList deviceModelList) {
+       public MidiDeviceDialog(final MidiDeviceModelManager deviceModelManager) {
                setTitle(openAction.getValue(Action.NAME).toString());
-               setBounds( 300, 300, 800, 500 );
-               MidiDeviceTreeModel deviceTreeModel = new MidiDeviceTreeModel(deviceModelList);
-               MidiDeviceTreeView deviceTreeView = new MidiDeviceTreeView(deviceTreeModel);
+               setBounds( 300, 300, 820, 540 );
+               MidiDeviceTreeView deviceTreeView = new MidiDeviceTreeView(deviceModelManager.getTreeModel());
                final MidiDeviceInfoPane deviceInfoPane = new MidiDeviceInfoPane();
                deviceTreeView.addTreeSelectionListener(deviceInfoPane);
-               MidiOpenedDevicesView desktopPane = new MidiOpenedDevicesView(deviceTreeView, deviceInfoPane, this);
+               MidiDeviceDesktopPane desktopPane = new MidiDeviceDesktopPane(deviceTreeView, deviceInfoPane, this);
                deviceTreeView.addTreeSelectionListener(desktopPane);
                deviceTreeView.setSelectionRow(0);
                add(new JSplitPane(
@@ -54,24 +54,38 @@ public class MidiDeviceDialog extends JDialog {
                                JSplitPane.VERTICAL_SPLIT,
                                new JScrollPane(deviceTreeView),
                                new JPanel() {{
-                                       add(new JScrollPane(deviceInfoPane));
-                                       add(new JButton("Reset time on MIDI devices") {{
-                                               addActionListener(new ActionListener() {
-                                                       @Override
-                                                       public void actionPerformed(ActionEvent e) {
-                                                               deviceModelList.resetMicrosecondPosition();
-                                                       }
-                                               });
+                                       add(new JPanel() {{
+                                               add(new JButton("Detect USB MIDI devices", new ButtonIcon(ButtonIcon.REPEAT_ICON)) {{
+                                                       setToolTipText("Update view for USB MIDI device newly plugged or removed");
+                                                       addActionListener(new ActionListener() {
+                                                               @Override
+                                                               public void actionPerformed(ActionEvent e) {
+                                                                       deviceModelManager.updateMidiDeviceList();
+                                                                       deviceTreeView.expandAll();
+                                                               }
+                                                       });
+                                               }});
+                                               add(new JButton("Reset Tx timestamp", new ButtonIcon(ButtonIcon.TOP_ICON)) {{
+                                                       setToolTipText("Reset timestamp on transmittable MIDI devices");
+                                                       addActionListener(new ActionListener() {
+                                                               @Override
+                                                               public void actionPerformed(ActionEvent e) {
+                                                                       deviceModelManager.resetMicrosecondPosition();
+                                                               }
+                                                       });
+                                               }});
+                                               setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
                                        }});
+                                       add(new JScrollPane(deviceInfoPane));
                                        setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
                                }}
                        ){{
-                               setDividerLocation(260);
+                               setDividerLocation(230);
                        }},
                        desktopPane
                ){{
                        setOneTouchExpandable(true);
-                       setDividerLocation(250);
+                       setDividerLocation(260);
                }});
        }
 }
index a62ee71..1381fba 100644 (file)
@@ -4,11 +4,7 @@ import java.awt.BorderLayout;
 import java.awt.Rectangle;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
-import java.awt.event.WindowAdapter;
-import java.awt.event.WindowEvent;
-import java.awt.event.WindowListener;
 
-import javax.sound.midi.MidiDevice;
 import javax.sound.midi.Receiver;
 import javax.sound.midi.Transmitter;
 import javax.swing.JInternalFrame;
@@ -21,12 +17,12 @@ import javax.swing.Timer;
  * MIDIデバイスフレームビュー
  */
 public class MidiDeviceFrame extends JInternalFrame {
+       private static final String LABEL_NO_VALUE = "--:--";
        private MidiDeviceModel deviceModel;
        private TransmitterListView transmitterListView;
        private ReceiverListView receiverListView;
        private JScrollPane scrollPane;
        private JPanel trxPanel, txPanel, rxPanel;
-       private Timer timer;
        /**
         * このデバイスフレームに表示内容を提供しているMIDIデバイスモデルを取得します。
         * @return MIDIデバイスモデル
@@ -43,38 +39,30 @@ public class MidiDeviceFrame extends JInternalFrame {
         */
        public ReceiverListView getMidiReceiverListView() { return receiverListView; }
        /**
-        * ダイアログウィンドウがアクティブなときだけタイムスタンプ更新を有効にするためのリスナー
-        */
-       public final WindowListener windowListener = new WindowAdapter() {
-               @Override
-               public void windowClosing(WindowEvent e) { timer.stop(); }
-               @Override
-               public void windowActivated(WindowEvent e) { timer.start(); }
-       };
-       /**
         * MIDIデバイスモデルからフレームビューを構築します。
         */
        public MidiDeviceFrame(MidiDeviceModel deviceModel, MidiCablePane cablePane) {
                super( null, true, true, false, false );
                this.deviceModel = deviceModel;
-               setTitle("[" + deviceModel.getMidiDeviceInOutType().getShortName() + "] " + deviceModel);
+               setTitle("[" + deviceModel.getInOutType().getShortName() + "] " + deviceModel);
                setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
                setLayout(new BorderLayout());
-               add(new JLabel("--:--") {{
-                       timer = new Timer(50, new ActionListener() {
+               add(new JLabel(LABEL_NO_VALUE) {
+                       Timer timer = new Timer(50, new ActionListener() {
                                private long sec = -2;
-                               private MidiDevice device = getMidiDeviceModel().getMidiDevice();
                                @Override
                                public void actionPerformed(ActionEvent event) {
-                                       long usec = device.getMicrosecondPosition();
+                                       if( ! isVisible() ) return;
+                                       long usec = deviceModel.getMidiDevice().getMicrosecondPosition();
                                        long sec = (usec == -1 ? -1 : usec/1000000);
                                        if( sec == this.sec ) return;
                                        this.sec = sec;
-                                       setText(sec == -1?"--:--":String.format("%02d:%02d",sec/60,sec%60));
+                                       setText(sec == -1?LABEL_NO_VALUE:String.format("%02d:%02d",sec/60,sec%60));
                                        cablePane.repaint();
                                }
                        });
-               }}, BorderLayout.SOUTH);
+                       { timer.start(); }
+               }, BorderLayout.SOUTH);
                add(scrollPane = new JScrollPane(trxPanel = new JPanel() {{
                        setLayout(new BorderLayout());
                        ReceiverListModel rxListModel = getMidiDeviceModel().getReceiverListModel();
index 71160d6..0432485 100644 (file)
@@ -20,8 +20,8 @@ public class MidiDeviceModel {
         * このリストのMIDIデバイスの入出力タイプを返します。
         * @return このリストのMIDIデバイスの入出力タイプ
         */
-       public MidiDeviceInOutType getMidiDeviceInOutType() { return ioType; }
-       private MidiDeviceInOutType ioType;
+       public MidiDeviceInOutType getInOutType() { return ioType; }
+       private MidiDeviceInOutType ioType = MidiDeviceInOutType.MIDI_NONE;
        /**
         * 対象MIDIデバイスを返します。
         * @return 対象MIDIデバイス
@@ -32,7 +32,9 @@ public class MidiDeviceModel {
         * 対象MIDIデバイスの名前を返します。
         */
        @Override
-       public String toString() { return device.getDeviceInfo().toString(); }
+       public String toString() {
+               return device.getDeviceInfo().toString();
+       }
        /**
         * {@link Transmitter} のリストモデルを返します。サポートしていない場合はnullを返します。
         * @return リストモデルまたはnull
@@ -48,18 +50,20 @@ public class MidiDeviceModel {
        /**
         * このMIDIデバイスモデルを収容しているリストを返します。
         */
-       public MidiDeviceModelList getDeviceModelList() { return deviceModelList; }
-       protected MidiDeviceModelList deviceModelList;
+       public MidiDeviceModelManager getDeviceModelManager() { return deviceModelManager; }
+       protected MidiDeviceModelManager deviceModelManager;
        /**
         * MIDIデバイスモデルを構築します。
         *
         * @param device 対象MIDIデバイス
-        * @param deviceModelList このMIDIデバイスモデルを収容しているリスト(接続相手となりうるMIDIデバイス)
+        * @param deviceModelManager このMIDIデバイスモデルのマネージャー(接続相手となりうるMIDIデバイス)
         */
-       public MidiDeviceModel(MidiDevice device, MidiDeviceModelList deviceModelList) {
+       public MidiDeviceModel(MidiDevice device, MidiDeviceModelManager deviceModelManager) {
+               this.deviceModelManager = deviceModelManager;
                this.device = device;
-               this.deviceModelList = deviceModelList;
-               if( device.getMaxTransmitters() != 0 ) txListModel = new TransmitterListModel(this);
+               if( device.getMaxTransmitters() != 0 ) {
+                       txListModel = new TransmitterListModel(this);
+               }
                if( device.getMaxReceivers() != 0 ) {
                        rxListModel = new ReceiverListModel(this);
                        ioType = txListModel != null ? MidiDeviceInOutType.MIDI_IN_OUT :MidiDeviceInOutType.MIDI_OUT;
@@ -67,10 +71,10 @@ public class MidiDeviceModel {
                else {
                        ioType = txListModel != null ? MidiDeviceInOutType.MIDI_IN :MidiDeviceInOutType.MIDI_NONE;
                }
-               treePath = new TreePath(new Object[] {deviceModelList, ioType ,this});
+               treePath = new TreePath(new Object[] {deviceModelManager, ioType ,this});
        }
        /**
-        * ã\81\93ã\81®MIDIã\83\87ã\83\90ã\82¤ã\82¹ã\83¢ã\83\87ã\83«ã\82\92é\96\8bã\81\8dã\81¾ã\81\99ã\80\82
+        * このMIDIデバイスを開きます。
         * MIDIデバイスを {@link MidiDevice#open()} で開き、
         * レシーバをサポートしている場合は {@link MidiDevice#getReceiver()} でレシーバを1個開きます。
         *
diff --git a/src/camidion/chordhelper/mididevice/MidiDeviceModelList.java b/src/camidion/chordhelper/mididevice/MidiDeviceModelList.java
deleted file mode 100644 (file)
index 6fcbbd9..0000000
+++ /dev/null
@@ -1,133 +0,0 @@
-package camidion.chordhelper.mididevice;
-
-import java.util.List;
-import java.util.Vector;
-
-import javax.sound.midi.MidiDevice;
-import javax.sound.midi.MidiSystem;
-import javax.sound.midi.MidiUnavailableException;
-import javax.sound.midi.Sequencer;
-import javax.sound.midi.Synthesizer;
-import javax.sound.midi.Transmitter;
-
-import camidion.chordhelper.ChordHelperApplet;
-
-/**
- * 仮想MIDIデバイスを含めたすべてのMIDIデバイスモデル {@link MidiDeviceModel} を収容するリスト
- */
-public class MidiDeviceModelList extends Vector<MidiDeviceModel> {
-
-       public String toString() { return "MIDI devices"; }
-
-       private MidiSequencerModel sequencerModel;
-       public MidiSequencerModel getSequencerModel() { return sequencerModel; }
-
-       public MidiDeviceModelList(List<VirtualMidiDevice> guiVirtualDeviceList) {
-               //
-               // GUI仮想MIDIデバイス
-               MidiDeviceModel guiModels[] = new MidiDeviceModel[guiVirtualDeviceList.size()];
-               for( int i=0; i<guiVirtualDeviceList.size(); i++ ) {
-                       addElement(guiModels[i] = new MidiDeviceModel(guiVirtualDeviceList.get(i), this));
-               }
-               // シーケンサの取得
-               Sequencer sequencer;
-               try {
-                       sequencer = MidiSystem.getSequencer(false);
-                       addElement(sequencerModel = new MidiSequencerModel(sequencer, this));
-               } catch( MidiUnavailableException e ) {
-                       System.out.println(ChordHelperApplet.VersionInfo.NAME +" : MIDI sequencer unavailable");
-                       e.printStackTrace();
-               }
-               // その他のリアルMIDIデバイスの取得
-               MidiDeviceModel synthModel = null;
-               MidiDeviceModel firstMidiInModel = null;
-               MidiDeviceModel firstMidiOutModel = null;
-               MidiDevice.Info[] deviceInfos = MidiSystem.getMidiDeviceInfo();
-               for( MidiDevice.Info info : deviceInfos ) {
-                       MidiDevice device;
-                       try {
-                               device = MidiSystem.getMidiDevice(info);
-                       } catch( MidiUnavailableException e ) {
-                               e.printStackTrace(); continue;
-                       }
-                       // シーケンサはすでに取得済みなのでスキップ
-                       if( device instanceof Sequencer ) continue;
-                       //
-                       // Java内蔵シンセサイザ
-                       if( device instanceof Synthesizer ) {
-                               try {
-                                       addElement(synthModel = new MidiDeviceModel(MidiSystem.getSynthesizer(), this));
-                               } catch( MidiUnavailableException e ) {
-                                       System.out.println(ChordHelperApplet.VersionInfo.NAME +
-                                                       " : Java internal MIDI synthesizer unavailable");
-                                       e.printStackTrace();
-                               }
-                               continue;
-                       }
-                       // その他のMIDIデバイス
-                       MidiDeviceModel m;
-                       addElement(m = new MidiDeviceModel(device, this));
-                       if( firstMidiOutModel == null && m.getReceiverListModel() != null ) firstMidiOutModel = m;
-                       if( firstMidiInModel == null && m.getTransmitterListModel() != null ) firstMidiInModel = m;
-               }
-               // MIDIデバイスを開く。
-               //   NOTE: 必ず MIDI OUT Rx デバイスを先に開くこと。
-               //
-               //   そうすれば、後から開いた MIDI IN Tx デバイスからの
-               //   タイムスタンプのほうが「若く」なる。これにより、
-               //   先に開かれ「少し歳を食った」Rx デバイスは
-               //   「信号が遅れてやってきた」と認識するので、
-               //   遅れを取り戻そうとして即座に音を出してくれる。
-               //
-               //   開く順序が逆になると「進みすぎるから遅らせよう」として
-               //   無用なレイテンシーが発生する原因になる。
-               try {
-                       MidiDeviceModel openModels[] = {
-                               synthModel,
-                               firstMidiOutModel,
-                               sequencerModel,
-                               firstMidiInModel,
-                       };
-                       for( MidiDeviceModel m : openModels ) if( m != null ) m.open();
-                       for( MidiDeviceModel m : guiModels ) m.open();
-                       //
-                       // 初期接続
-                       TransmitterListModel txListModel;
-                       for( MidiDeviceModel mtx : guiModels ) {
-                               if( (txListModel = mtx.getTransmitterListModel() ) != null) {
-                                       for( MidiDeviceModel m : guiModels ) txListModel.connectToFirstReceiverOfDevice(m);
-                                       txListModel.connectToFirstReceiverOfDevice(sequencerModel);
-                                       txListModel.connectToFirstReceiverOfDevice(synthModel);
-                                       txListModel.connectToFirstReceiverOfDevice(firstMidiOutModel);
-                               }
-                       }
-                       if( firstMidiInModel != null && (txListModel = firstMidiInModel.getTransmitterListModel()) != null) {
-                               for( MidiDeviceModel m : guiModels ) txListModel.connectToFirstReceiverOfDevice(m);
-                               txListModel.connectToFirstReceiverOfDevice(sequencerModel);
-                               txListModel.connectToFirstReceiverOfDevice(synthModel);
-                               txListModel.connectToFirstReceiverOfDevice(firstMidiOutModel);
-                       }
-                       if( sequencerModel != null && (txListModel = sequencerModel.getTransmitterListModel()) != null) {
-                               for( MidiDeviceModel m : guiModels ) txListModel.connectToFirstReceiverOfDevice(m);
-                               txListModel.connectToFirstReceiverOfDevice(synthModel);
-                               txListModel.connectToFirstReceiverOfDevice(firstMidiOutModel);
-                       }
-               } catch( MidiUnavailableException ex ) {
-                       ex.printStackTrace();
-               }
-       }
-       /**
-        * {@link Transmitter}を持つすべてのデバイス(例:MIDIキーボードなど)について、
-        * {@link MidiDeviceModel#resetMicrosecondPosition()}でマイクロ秒位置をリセットします。
-        */
-       public void resetMicrosecondPosition() {
-               for(MidiDeviceModel m : this) {
-                       TransmitterListModel txListModel = m.getTransmitterListModel();
-                       if( txListModel != null ) txListModel.resetMicrosecondPosition();
-               }
-       }
-       /**
-        * すべてのMIDIデバイスを閉じます。
-        */
-       public void closeAllDevices() { for(MidiDeviceModel m : this) m.getMidiDevice().close(); }
-}
diff --git a/src/camidion/chordhelper/mididevice/MidiDeviceModelManager.java b/src/camidion/chordhelper/mididevice/MidiDeviceModelManager.java
new file mode 100644 (file)
index 0000000..0245156
--- /dev/null
@@ -0,0 +1,210 @@
+package camidion.chordhelper.mididevice;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.Vector;
+
+import javax.sound.midi.MidiDevice;
+import javax.sound.midi.MidiSystem;
+import javax.sound.midi.MidiUnavailableException;
+import javax.sound.midi.Receiver;
+import javax.sound.midi.Sequencer;
+import javax.sound.midi.Synthesizer;
+import javax.sound.midi.Transmitter;
+
+import camidion.chordhelper.ChordHelperApplet;
+
+/**
+ * 仮想MIDIデバイスを含めたすべてのMIDIデバイスモデル {@link MidiDeviceModel} を管理するオブジェクト
+ */
+public class MidiDeviceModelManager {
+
+       private List<MidiDeviceModel> deviceModelList = new Vector<>();
+       public List<MidiDeviceModel> getDeviceModelList() { return deviceModelList; }
+
+       private MidiSequencerModel sequencerModel;
+       public MidiSequencerModel getSequencerModel() { return sequencerModel; }
+
+       private MidiDeviceTreeModel treeModel;
+       public MidiDeviceTreeModel getTreeModel() { return treeModel; }
+       public void setTreeModel(MidiDeviceTreeModel treeModel) { this.treeModel = treeModel; }
+
+       public MidiDeviceModelManager(VirtualMidiDevice guiVirtualDevice) {
+               List<MidiDevice.Info> deviceInfos = Arrays.asList(MidiSystem.getMidiDeviceInfo());
+               //
+               // GUI仮想MIDIデバイス
+               MidiDeviceModel guiModel = new MidiDeviceModel(guiVirtualDevice, this);
+               deviceModelList.add(guiModel);
+               //
+               // シーケンサ
+               Sequencer sequencer = null;
+               try {
+                       sequencer = MidiSystem.getSequencer(false);
+                       deviceModelList.add(sequencerModel = new MidiSequencerModel(sequencer, this));
+               } catch( MidiUnavailableException e ) {
+                       System.out.println(ChordHelperApplet.VersionInfo.NAME +" : MIDI sequencer unavailable");
+                       e.printStackTrace();
+               }
+               // その他のリアルMIDIデバイス
+               MidiDeviceModel synthModel = null;
+               MidiDeviceModel firstMidiInModel = null;
+               MidiDeviceModel firstMidiOutModel = null;
+               for( MidiDevice.Info info : deviceInfos ) {
+                       MidiDevice device;
+                       try {
+                               device = MidiSystem.getMidiDevice(info);
+                       } catch( MidiUnavailableException e ) {
+                               e.printStackTrace();
+                               continue;
+                       }
+                       // シーケンサはすでに取得済みなのでスキップ
+                       if( device instanceof Sequencer ) continue;
+                       //
+                       // Java内蔵シンセサイザ
+                       if( device instanceof Synthesizer ) {
+                               try {
+                                       device = MidiSystem.getSynthesizer();
+                                       deviceModelList.add(synthModel = new MidiDeviceModel(device, this));
+                               } catch( MidiUnavailableException e ) {
+                                       System.out.println(ChordHelperApplet.VersionInfo.NAME +
+                                                       " : Java internal MIDI synthesizer unavailable");
+                                       e.printStackTrace();
+                               }
+                               continue;
+                       }
+                       // その他のMIDIデバイス
+                       MidiDeviceModel m;
+                       deviceModelList.add(m = new MidiDeviceModel(device, this));
+                       //
+                       // 最初の MIDI OUT(Windowsの場合は通常、内蔵音源 Microsoft GS Wavetable SW Synth)
+                       if( firstMidiOutModel == null && m.getReceiverListModel() != null ) firstMidiOutModel = m;
+                       //
+                       // 最初の MIDI IN(USB MIDI インターフェースにつながったMIDIキーボードなど)
+                       if( firstMidiInModel == null && m.getTransmitterListModel() != null ) firstMidiInModel = m;
+               }
+               // MIDIデバイスを開く。
+               //   NOTE: 必ず MIDI OUT Rx デバイスを先に開くこと。
+               //
+               //   そうすれば、後から開いた MIDI IN Tx デバイスからのタイムスタンプのほうが「若く」なるので、
+               //   相手の MIDI OUT Rx デバイスは「信号が遅れてやってきた」と認識、遅れを取り戻そうとして
+               //   即座に音を出してくれる。
+               //
+               //   開く順序が逆になると「進みすぎるから遅らせよう」として無用なレイテンシーが発生する原因になる。
+               try {
+                       // デバイスを開く
+                       MidiDeviceModel modelsToOpen[] = {synthModel, firstMidiOutModel, sequencerModel, firstMidiInModel};
+                       for( MidiDeviceModel m : modelsToOpen ) if( m != null ) m.open();
+                       guiModel.open();
+                       //
+                       // 初期接続
+                       // GUI → GUI、各音源、シーケンサ
+                       TransmitterListModel txListModel;
+                       if( (txListModel = guiModel.getTransmitterListModel() ) != null) {
+                               txListModel.connectToFirstReceiverOfDevices(guiModel,sequencerModel,synthModel,firstMidiOutModel);
+                       }
+                       // MIDI IN → GUI、各音源、シーケンサ
+                       if( firstMidiInModel != null && (txListModel = firstMidiInModel.getTransmitterListModel()) != null) {
+                               txListModel.connectToFirstReceiverOfDevices(guiModel,sequencerModel,synthModel,firstMidiOutModel);
+                       }
+                       // シーケンサ → GUI、各音源
+                       if( sequencerModel != null && (txListModel = sequencerModel.getTransmitterListModel()) != null) {
+                               txListModel.connectToFirstReceiverOfDevices(guiModel,synthModel,firstMidiOutModel);
+                       }
+               } catch( MidiUnavailableException ex ) {
+                       ex.printStackTrace();
+               }
+               treeModel = new MidiDeviceTreeModel(deviceModelList);
+       }
+       /**
+        * MIDIデバイスリストを最新の状態に更新します。USB-MIDIデバイスの着脱後に使います。
+        */
+       public void updateMidiDeviceList() {
+               //
+               // USBから抜いたMIDIデバイスを相手のMIDIデバイスに接続したまま
+               // MidiSystem.getMidiDeviceInfo()を呼ぶと、Java VM が落ちることがある。
+               //
+               // それを回避するため、接続を一旦全部閉じる。
+               //
+               // 各Receiverごとに相手のTransmitterを閉じ、デバイスモデル同士の接続を覚えておく。
+               Map<MidiDeviceModel, Set<MidiDeviceModel>> rxToTxConnections = new HashMap<>();
+               for(MidiDeviceModel m : deviceModelList) {
+                       ReceiverListModel rxListModel = m.getReceiverListModel();
+                       if( rxListModel == null ) continue;
+                       Set<MidiDeviceModel> txDeviceModels = rxListModel.closePeerTransmitters();
+                       if( txDeviceModels.isEmpty() ) continue;
+                       rxToTxConnections.put(m, txDeviceModels);
+               }
+               // 最新のMIDIデバイス情報を取得
+               MidiDevice.Info[] infoArray = MidiSystem.getMidiDeviceInfo();
+               //
+               // 追加されたデバイスの情報
+               List<MidiDevice.Info> additionalDeviceInfos = new Vector<>(Arrays.asList(infoArray));
+               //
+               // 取り外されたデバイスのモデル
+               List<MidiDeviceModel> removingDeviceModels = new Vector<>();
+               //
+               // 既存デバイスをスキャン
+               for(MidiDeviceModel m : deviceModelList) {
+                       MidiDevice device = m.getMidiDevice();
+                       if( device instanceof VirtualMidiDevice ) continue;
+                       if( ! additionalDeviceInfos.remove(device.getDeviceInfo()) ) {
+                               // 最新のMIDIデバイス情報リストから除去しようとして、すでに存在していなかった場合、
+                               // そのデバイスはすでに取り外されている
+                               m.close();
+                               removingDeviceModels.add(m);
+                       }
+               }
+               // 取り外されたデバイスのモデルを除去
+               deviceModelList.removeAll(removingDeviceModels);
+               //
+               // 追加されたデバイスをモデル化してデバイスモデルリストに追加
+               for( MidiDevice.Info info : additionalDeviceInfos ) {
+                       try {
+                               MidiDevice device = MidiSystem.getMidiDevice(info);
+                               MidiDeviceModel m = new MidiDeviceModel(device, this);
+                               deviceModelList.add(m);
+                       } catch( MidiUnavailableException e ) {
+                               e.printStackTrace();
+                       }
+               }
+               // デバイスモデル同士の接続を復元
+               Set<MidiDeviceModel> rxDeviceModels = rxToTxConnections.keySet();
+               for( MidiDeviceModel rxm : rxDeviceModels ) {
+                       if( ! deviceModelList.contains(rxm) ) continue;
+                       List<Receiver> rxList = rxm.getReceiverListModel().getTransceivers();
+                       for( Receiver rx : rxList ) {
+                               Set<MidiDeviceModel> txDeviceModels = rxToTxConnections.get(rxm);
+                               for( MidiDeviceModel txm : txDeviceModels ) {
+                                       try {
+                                               txm.getTransmitterListModel().openTransmitter().setReceiver(rx);
+                                       } catch( MidiUnavailableException e ) {
+                                               e.printStackTrace();
+                                       }
+                               }
+                       }
+               }
+               // デバイスツリーの更新を通知
+               treeModel.setDeviceModelList(deviceModelList);
+       }
+       /**
+        * {@link Transmitter}を持つすべてのデバイス(例:MIDIキーボードなど)について、
+        * {@link MidiDeviceModel#resetMicrosecondPosition()}でマイクロ秒位置をリセットします。
+        */
+       public void resetMicrosecondPosition() {
+               for(MidiDeviceModel m : deviceModelList) {
+                       TransmitterListModel txListModel = m.getTransmitterListModel();
+                       if( txListModel != null ) txListModel.resetMicrosecondPosition();
+               }
+       }
+       /**
+        * すべてのMIDIデバイスを閉じます。
+        */
+       public void close() {
+               for(MidiDeviceModel m : deviceModelList) m.getMidiDevice().close();
+               deviceModelList.clear();
+               treeModel = null;
+       }
+}
index 621cbf0..bd316f3 100644 (file)
@@ -1,5 +1,10 @@
 package camidion.chordhelper.mididevice;
 
+import java.util.ArrayList;
+import java.util.EnumMap;
+import java.util.List;
+import java.util.Map;
+
 import javax.swing.event.EventListenerList;
 import javax.swing.event.TreeModelEvent;
 import javax.swing.event.TreeModelListener;
@@ -7,63 +12,53 @@ import javax.swing.tree.TreeModel;
 import javax.swing.tree.TreePath;
 
 /**
- * {@link MidiDeviceModelList}に収容されたMIDIデバイスを
- * {@link MidiDeviceInOutType}で分類して参照できるようにするツリーモデル
+ * MIDIデバイスを{@link MidiDeviceInOutType}で分類して参照できるようにするツリーモデル
  */
 public class MidiDeviceTreeModel implements TreeModel {
 
-       private MidiDeviceModelList trxListModelList;
+       private List<MidiDeviceModel> deviceModelList;
+       public List<MidiDeviceModel> getDeviceModelList() { return deviceModelList; }
+       public void setDeviceModelList(List<MidiDeviceModel> deviceModelList) {
+               group.clear();
+               this.deviceModelList = deviceModelList;
+               createGroup();
+               fireTreeStructureChanged(this, null, null, null);
+       }
 
-       public MidiDeviceModelList getDeviceModelList() { return trxListModelList; }
+       private Map<MidiDeviceInOutType, List<MidiDeviceModel>> group = new EnumMap<>(MidiDeviceInOutType.class);
+       private void createGroup() {
+               for(MidiDeviceInOutType ioType : MidiDeviceInOutType.values()) {
+                       if( ioType != MidiDeviceInOutType.MIDI_NONE ) group.put(ioType, new ArrayList<>());
+               }
+               for( MidiDeviceModel m : deviceModelList ) group.get(m.getInOutType()).add(m);
+       }
+       public Map<MidiDeviceInOutType, List<MidiDeviceModel>> getGroup() { return group; }
 
-       public MidiDeviceTreeModel(MidiDeviceModelList trxListModelList) {
-               this.trxListModelList = trxListModelList;
+       public MidiDeviceTreeModel(List<MidiDeviceModel> deviceModelList) {
+               this.deviceModelList = deviceModelList;
+               createGroup();
        }
 
        @Override
-       public Object getRoot() { return trxListModelList; }
+       public String toString() { return "MIDI devices"; }
        @Override
-       public Object getChild(Object parent, int index) {
-               if( parent == getRoot() ) return MidiDeviceInOutType.values()[index + 1];
-               if( parent instanceof MidiDeviceInOutType ) {
-                       MidiDeviceInOutType ioType = (MidiDeviceInOutType)parent;
-                       for( MidiDeviceModel deviceModel : trxListModelList )
-                               if( deviceModel.getMidiDeviceInOutType() == ioType ) {
-                                       if( index == 0 ) return deviceModel;
-                                       index--;
-                               }
-               }
-               return null;
-       }
+       public Object getRoot() { return this; }
        @Override
        public int getChildCount(Object parent) {
                if( parent == getRoot() ) return MidiDeviceInOutType.values().length - 1;
-               int childCount = 0;
-               if( parent instanceof MidiDeviceInOutType ) {
-                       MidiDeviceInOutType ioType = (MidiDeviceInOutType)parent;
-                       for( MidiDeviceModel deviceModel : trxListModelList )
-                               if( deviceModel.getMidiDeviceInOutType() == ioType ) childCount++;
-               }
-               return childCount;
+               if( parent instanceof MidiDeviceInOutType ) return group.get(parent).size();
+               return 0;
+       }
+       @Override
+       public Object getChild(Object parent, int index) {
+               if( parent == getRoot() ) return MidiDeviceInOutType.values()[index + 1];
+               if( parent instanceof MidiDeviceInOutType ) return group.get(parent).get(index);
+               return null;
        }
        @Override
        public int getIndexOfChild(Object parent, Object child) {
-               if( parent == getRoot() ) {
-                       if( child instanceof MidiDeviceInOutType ) {
-                               MidiDeviceInOutType ioType = (MidiDeviceInOutType)child;
-                               return ioType.ordinal() - 1;
-                       }
-               }
-               if( parent instanceof MidiDeviceInOutType ) {
-                       MidiDeviceInOutType ioType = (MidiDeviceInOutType)parent;
-                       int index = 0;
-                       for( MidiDeviceModel deviceModel : trxListModelList ) {
-                               if( deviceModel.getMidiDeviceInOutType() == ioType ) {
-                                       if( deviceModel == child ) return index;
-                                       index++;
-                               }
-                       }
-               }
+               if( parent == getRoot() ) return ((MidiDeviceInOutType)child).ordinal() - 1;
+               if( parent instanceof MidiDeviceInOutType ) return group.get(parent).indexOf(child);
                return -1;
        }
        @Override
@@ -80,16 +75,14 @@ public class MidiDeviceTreeModel implements TreeModel {
        public void removeTreeModelListener(TreeModelListener listener) {
                listenerList.remove(TreeModelListener.class, listener);
        }
-       public void fireTreeNodesChanged(
-               Object source, Object[] path, int[] childIndices, Object[] children
-       ) {
+       protected void fireTreeStructureChanged(Object source, Object[] path, int[] childIndices, Object[] children) {
                Object[] listeners = listenerList.getListenerList();
                for (int i = listeners.length-2; i>=0; i-=2) {
                        if (listeners[i]==TreeModelListener.class) {
-                               ((TreeModelListener)listeners[i+1]).treeNodesChanged(
+                               ((TreeModelListener)listeners[i+1]).treeStructureChanged(
                                        new TreeModelEvent(source,path,childIndices,children)
                                );
                        }
                }
        }
-}
\ No newline at end of file
+}
index 2099151..6efbe90 100644 (file)
@@ -36,6 +36,12 @@ public class MidiDeviceTreeView extends JTree {
        @Override
        public MidiDeviceTreeModel getModel() { return (MidiDeviceTreeModel) super.getModel(); }
        /**
+        * ツリーノードを開き、ルートを選択した状態にします。
+        */
+       public void expandAll() {
+               for( int row = 0; row < getRowCount() ; row++ ) expandRow(row);
+       }
+       /**
         * MIDIデバイスツリービューを構築します。
         * @param model このビューにデータを提供するモデル
         */
@@ -63,8 +69,6 @@ public class MidiDeviceTreeView extends JTree {
                                return this;
                        }
                });
-               // ツリーノードを開き、ルートを選択した状態にする
-               for( int row = 0; row < getRowCount() ; row++ ) expandRow(row);
                //
                // ツリーノードのToolTipを有効化
                ToolTipManager.sharedInstance().registerComponent(this);
index f944066..fae059e 100644 (file)
@@ -35,7 +35,7 @@ public class MidiSequencerModel extends MidiDeviceModel implements BoundedRangeM
         * @param sequencer シーケンサーMIDIデバイス
         * @param deviceModelList 親のMIDIデバイスモデルリスト
         */
-       public MidiSequencerModel(Sequencer sequencer, MidiDeviceModelList deviceModelList) {
+       public MidiSequencerModel(Sequencer sequencer, MidiDeviceModelManager deviceModelList) {
                super(sequencer, deviceModelList);
        }
        /**
@@ -124,7 +124,7 @@ public class MidiSequencerModel extends MidiDeviceModel implements BoundedRangeM
                timeRangeUpdater.start();
                SequenceTrackListTableModel sequenceTableModel = getSequenceTrackListTableModel();
                if( sequenceTableModel != null && sequenceTableModel.hasRecordChannel() ) {
-                       deviceModelList.resetMicrosecondPosition();
+                       deviceModelManager.resetMicrosecondPosition();
                        System.gc();
                        sequencer.startRecording();
                }
index 2b65802..6b9b544 100644 (file)
@@ -1,6 +1,8 @@
 package camidion.chordhelper.mididevice;
 
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 import javax.sound.midi.MidiDevice;
 import javax.sound.midi.MidiUnavailableException;
@@ -24,18 +26,22 @@ public class ReceiverListModel extends AbstractTransceiverListModel<Receiver> {
                if( device.getReceivers().isEmpty() ) device.getReceiver();
        }
        /**
-        * このリストモデルの{@link Receiver}に接続された他デバイスの{@link Transmitter}を全て閉じます。
+        * このリストモデルの{@link Receiver}に接続された{@link Transmitter}を全て閉じます。
+        *
+        * @return 閉じた{@link Transmitter}の{@link MidiDeviceModel}の集合
         */
-       public void closePeerTransmitters() {
+       public Set<MidiDeviceModel> closePeerTransmitters() {
                List<Receiver> rxList = deviceModel.getMidiDevice().getReceivers();
-               MidiDeviceModelList deviceModelList = deviceModel.getDeviceModelList();
+               List<MidiDeviceModel> deviceModelList = deviceModel.getDeviceModelManager().getDeviceModelList();
+               Set<MidiDeviceModel> peedDeviceModels = new HashSet<>();
                for( Receiver rx : rxList ) {
-                       for( MidiDeviceModel m : deviceModelList ) {
-                               if( m == deviceModel ) continue;
-                               TransmitterListModel txListModel = m.getTransmitterListModel();
-                               if( txListModel == null ) continue;
-                               txListModel.closePeerTransmitterOf(rx);
+                       for( MidiDeviceModel peedDeviceModel : deviceModelList ) {
+                               if( peedDeviceModel == deviceModel ) continue;
+                               TransmitterListModel txListModel = peedDeviceModel.getTransmitterListModel();
+                               if( txListModel == null || txListModel.closeTransmittersConnectedTo(rx).isEmpty() ) continue;
+                               peedDeviceModels.add(peedDeviceModel);
                        }
                }
+               return peedDeviceModels;
        }
 }
index 5836db1..7642fb4 100644 (file)
@@ -50,13 +50,14 @@ public class TransmitterListModel extends AbstractTransceiverListModel<Transmitt
         * 相手のMIDIデバイスが持つ最初の{@link Receiver}を、
         * このリストモデルの新規{@link Transmitter}に接続します。
         *
-        * @param anotherDeviceModel 接続相手のMIDIデバイス
+        * @param anotherDeviceModels 接続相手のMIDIデバイス(複数指定可)
         * @throws MidiUnavailableException リソースの制約のためにトランスミッタを使用できない場合にスローされる
         */
-       public void connectToFirstReceiverOfDevice(MidiDeviceModel anotherDeviceModel) throws MidiUnavailableException {
-               List<Receiver> rxList = anotherDeviceModel.getMidiDevice().getReceivers();
-               if( rxList.isEmpty() ) return;
-               deviceModel.getTransmitterListModel().openTransmitter().setReceiver(rxList.get(0));
+       public void connectToFirstReceiverOfDevices(MidiDeviceModel... anotherDeviceModels) throws MidiUnavailableException {
+               for( MidiDeviceModel anotherDeviceModel : anotherDeviceModels ) {
+                       List<Receiver> rxList = anotherDeviceModel.getMidiDevice().getReceivers();
+                       if( ! rxList.isEmpty() ) deviceModel.getTransmitterListModel().openTransmitter().setReceiver(rxList.get(0));
+               }
        }
        /**
         * 指定の{@link Transmitter}を閉じ、要素数が1個減ったことをこのモデルを参照しているビューへ通知します。
@@ -72,15 +73,18 @@ public class TransmitterListModel extends AbstractTransceiverListModel<Transmitt
         * このリストモデルにある{@link Transmitter}のうち、
         * 引数で指定された{@link Receiver}へデータを送信しているものを全て閉じます。
         * 閉じるとリストから自動的に削除されるので、表示の更新も行います。
+        * @return 閉じた{@link Transmitter}のリスト
         */
-       public void closePeerTransmitterOf(Receiver rx) {
-               List<Transmitter> closingTxList = new Vector<Transmitter>();
+       public List<Transmitter> closeTransmittersConnectedTo(Receiver rx) {
+               List<Transmitter> closeTxList = new Vector<Transmitter>();
                List<Transmitter> txList = getTransceivers();
-               for( Transmitter tx : txList ) if( tx.getReceiver() == rx ) closingTxList.add(tx);
-               if( closingTxList.isEmpty() ) return;
-               int length = getSize();
-               for( Transmitter tx : closingTxList ) tx.close();
-               fireIntervalRemoved(this, 0, length);
+               for( Transmitter tx : txList ) if( tx.getReceiver() == rx ) closeTxList.add(tx);
+               if( ! closeTxList.isEmpty() ) {
+                       int length = getSize();
+                       for( Transmitter tx : closeTxList ) tx.close();
+                       fireIntervalRemoved(this, 0, length);
+               }
+               return closeTxList;
        }
        /**
         * マイクロ秒位置をリセットします。
@@ -97,9 +101,7 @@ public class TransmitterListModel extends AbstractTransceiverListModel<Transmitt
         */
        public void resetMicrosecondPosition() {
                MidiDevice device = deviceModel.getMidiDevice();
-               //
-               // シーケンサはこのメソッドでのリセット対象外
-               if( device instanceof Sequencer ) return;
+               if( device instanceof Sequencer || ! device.isOpen() ) return;
                //
                // デバイスを閉じる前に接続状態を把握
                List<Receiver> peerRxList = new Vector<Receiver>();
@@ -109,7 +111,7 @@ public class TransmitterListModel extends AbstractTransceiverListModel<Transmitt
                        if( rx != null ) peerRxList.add(rx);
                }
                List<Transmitter> peerTxList = new Vector<Transmitter>();
-               MidiDeviceModelList deviceModelList = deviceModel.getDeviceModelList();
+               List<MidiDeviceModel> deviceModelList = deviceModel.getDeviceModelManager().getDeviceModelList();
                List<Receiver> rxList = device.getReceivers();
                for( Receiver rx : rxList ) {
                        for( MidiDeviceModel m : deviceModelList ) {