OSDN Git Service

MIDIデバイスダイアログ修正
authorAkiyoshi Kamide <kamide@yk.rim.or.jp>
Fri, 17 Jun 2016 16:53:56 +0000 (01:53 +0900)
committerAkiyoshi Kamide <kamide@yk.rim.or.jp>
Fri, 17 Jun 2016 16:53:56 +0000 (01:53 +0900)
・コネクタリストを送信・受信で別々のコンポーネントに分離

15 files changed:
src/camidion/chordhelper/ChordHelperApplet.java
src/camidion/chordhelper/mididevice/MidiCablePane.java
src/camidion/chordhelper/mididevice/MidiDeviceDialog.java
src/camidion/chordhelper/mididevice/MidiDeviceFrame.java
src/camidion/chordhelper/mididevice/MidiDeviceInfoPane.java
src/camidion/chordhelper/mididevice/MidiDeviceModel.java [new file with mode: 0644]
src/camidion/chordhelper/mididevice/MidiDeviceModelList.java [moved from src/camidion/chordhelper/mididevice/MidiTransceiverListModelList.java with 70% similarity]
src/camidion/chordhelper/mididevice/MidiDeviceTreeModel.java
src/camidion/chordhelper/mididevice/MidiDeviceTreeView.java
src/camidion/chordhelper/mididevice/MidiOpenedDevicesView.java
src/camidion/chordhelper/mididevice/MidiReceiverListView.java [new file with mode: 0644]
src/camidion/chordhelper/mididevice/MidiSequencerModel.java
src/camidion/chordhelper/mididevice/MidiTransceiverListModel.java [deleted file]
src/camidion/chordhelper/mididevice/MidiTransceiverListView.java [deleted file]
src/camidion/chordhelper/mididevice/MidiTransmitterListView.java [new file with mode: 0644]

index d27db2e..a6d8fbc 100644 (file)
@@ -58,8 +58,8 @@ 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.MidiSequencerModel;
-import camidion.chordhelper.mididevice.MidiTransceiverListModelList;
 import camidion.chordhelper.mididevice.SequencerMeasureView;
 import camidion.chordhelper.mididevice.SequencerTimeView;
 import camidion.chordhelper.mididevice.VirtualMidiDevice;
@@ -285,7 +285,7 @@ public class ChordHelperApplet extends JApplet {
         */
        public static class VersionInfo {
                public static final String      NAME = "MIDI Chord Helper";
-               public static final String      VERSION = "Ver.20160612.1";
+               public static final String      VERSION = "Ver.20160617.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/";
@@ -403,7 +403,7 @@ public class ChordHelperApplet extends JApplet {
        private JLabel songTitleLabel = new JLabel();
        private AnoGakkiPane anoGakkiPane;
        private JToggleButton anoGakkiToggleButton;
-       private MidiTransceiverListModelList deviceModelList;
+       private MidiDeviceModelList deviceModelList;
 
        public void init() {
                //
@@ -468,7 +468,7 @@ public class ChordHelperApplet extends JApplet {
                VirtualMidiDevice guiMidiDevice = keyboardPanel.keyboardCenterPanel.keyboard.midiDevice;
                //
                // MIDIデバイス一覧を構築
-               deviceModelList = new MidiTransceiverListModelList(Arrays.asList(guiMidiDevice));
+               deviceModelList = new MidiDeviceModelList(Arrays.asList(guiMidiDevice));
                (midiDeviceDialog = new MidiDeviceDialog(deviceModelList)).setIconImage(iconImage);
                //
                // MIDIデバイス一覧のシーケンサと連携するプレイリストを構築
index d9d6536..6f8ecf9 100644 (file)
@@ -34,7 +34,7 @@ import javax.swing.event.ListDataListener;
 public class MidiCablePane extends JComponent implements DragSourceMotionListener {
        private Point draggingPoint;
        /**
-        * {@link MidiTransceiverListModel} の {@link Transmitter}
+        * {@link MidiDeviceModel} の {@link Transmitter}
         * をドラッグしている最中に再描画するためのリスナー
         */
        @Override
@@ -70,14 +70,14 @@ public class MidiCablePane extends JComponent implements DragSourceMotionListene
                        JInternalFrame frame = e.getInternalFrame();
                        if( ! (frame instanceof MidiDeviceFrame) ) return;
                        MidiDeviceFrame f = (MidiDeviceFrame)frame;
-                       MidiTransceiverListModel m = f.getMidiTransceiverListView().getModel();
+                       MidiDeviceModel m = f.getMidiDeviceModel();
                        List<Receiver> rxList = m.getMidiDevice().getReceivers();
                        for( Receiver rx : rxList ) colorMap.remove(rx);
                        repaint();
                }
        };
        /**
-        * {@link MidiTransceiverListModel} における {@link Transmitter}
+        * {@link MidiDeviceModel} における {@link Transmitter}
         * の増減や状態変更があった場合にケーブルを再描画するためのリスナー
         */
        public final ListDataListener midiConnecterListDataListener = new ListDataListener() {
@@ -132,13 +132,13 @@ public class MidiCablePane extends JComponent implements DragSourceMotionListene
                for( JInternalFrame frame : frames ) {
                        if( ! (frame instanceof MidiDeviceFrame) ) continue;
                        MidiDeviceFrame fromFrame = (MidiDeviceFrame)frame;
-                       MidiTransceiverListView fromView = fromFrame.getMidiTransceiverListView();
-                       MidiDevice fromDevice = fromView.getModel().getMidiDevice();
+                       MidiDevice fromDevice = fromFrame.getMidiDeviceModel().getMidiDevice();
                        if( draggingPoint != null ) {
                                // Receiverからドラッグされた線を描画
+                               MidiReceiverListView rxView = fromFrame.getMidiReceiverListView();
                                List<Receiver> rxList = fromDevice.getReceivers();
                                for( Receiver rx : rxList ) {
-                                       if( ! rx.equals(fromView.getDraggingTransceiver()) ) continue;
+                                       if( ! rx.equals(rxView.getDraggingReceiver()) ) continue;
                                        Rectangle rxBounds = fromFrame.getBoundsOf(rx);
                                        if( rxBounds == null ) continue;
                                        int r = (rxBounds.height - 5) / 2;
@@ -148,6 +148,7 @@ public class MidiCablePane extends JComponent implements DragSourceMotionListene
                                        break;
                                }
                        }
+                       MidiTransmitterListView txView = fromFrame.getMidiTransmitterListView();
                        List<Transmitter> txList = fromDevice.getTransmitters();
                        for( Transmitter tx : txList ) {
                                // Transmitterの場所を特定
@@ -155,9 +156,9 @@ public class MidiCablePane extends JComponent implements DragSourceMotionListene
                                if( txBounds == null ) continue;
                                int r = (txBounds.height - 5) / 2;
                                txBounds.translate(r+4, r+4);
-                               AutoCloseable draggingTrx = fromView.getDraggingTransceiver();
+                               Transmitter draggingTx = txView.getDraggingTransmitter();
                                Receiver rx = tx.getReceiver();
-                               if( draggingPoint != null && tx.equals(draggingTrx) ) {
+                               if( draggingPoint != null && tx.equals(draggingTx) ) {
                                        // Transmitterからドラッグされた線を描画
                                        g2.setColor(rx == null ? ADDING_CABLE_COLOR : colorOf(rx));
                                        g2.drawLine(txBounds.x, txBounds.y, draggingPoint.x, draggingPoint.y);
@@ -169,7 +170,7 @@ public class MidiCablePane extends JComponent implements DragSourceMotionListene
                                        if( rxBounds == null ) continue;
                                        r = (rxBounds.height - 5) / 2;
                                        rxBounds.translate(r+4, r+4);
-                                       g2.setColor(draggingTrx == tx ? REMOVING_CABLE_COLOR : colorOf(rx));
+                                       g2.setColor(draggingTx == tx ? REMOVING_CABLE_COLOR : colorOf(rx));
                                        g2.drawLine(txBounds.x, txBounds.y, rxBounds.x, rxBounds.y);
                                        break;
                                }
index b44a51e..818326c 100644 (file)
@@ -1,11 +1,15 @@
 package camidion.chordhelper.mididevice;
 
+import java.awt.datatransfer.DataFlavor;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
 
+import javax.sound.midi.Receiver;
+import javax.sound.midi.Transmitter;
 import javax.swing.AbstractAction;
 import javax.swing.Action;
 import javax.swing.BoxLayout;
+import javax.swing.Icon;
 import javax.swing.JButton;
 import javax.swing.JDialog;
 import javax.swing.JPanel;
@@ -18,6 +22,9 @@ import camidion.chordhelper.ButtonIcon;
  * MIDIデバイスダイアログ (View)
  */
 public class MidiDeviceDialog extends JDialog {
+       public static final DataFlavor receiverFlavor = new DataFlavor(Receiver.class, "Receiver");
+       public static final DataFlavor transmitterFlavor = new DataFlavor(Transmitter.class, "Transmitter");
+       public static final Icon MIDI_CONNECTER_ICON = new ButtonIcon(ButtonIcon.MIDI_CONNECTOR_ICON);
        /**
         * MIDIデバイスダイアログを開くアクション
         */
@@ -25,23 +32,25 @@ public class MidiDeviceDialog extends JDialog {
                {
                        putValue(NAME, "MIDI device connection");
                        putValue(SHORT_DESCRIPTION, "MIDIデバイス間の接続を編集");
-                       putValue(LARGE_ICON_KEY, new ButtonIcon(ButtonIcon.MIDI_CONNECTOR_ICON));
+                       putValue(LARGE_ICON_KEY, MIDI_CONNECTER_ICON);
                }
                @Override
-               public void actionPerformed(ActionEvent event) { setVisible(true); }
+               public void actionPerformed(ActionEvent event) {
+                       setVisible(true);
+               }
        };
        /**
         * MIDIデバイスダイアログを構築します。
         * @param deviceModelList デバイスモデル(MIDIコネクタリストモデル)のリスト
         */
-       public MidiDeviceDialog(final MidiTransceiverListModelList deviceModelList) {
+       public MidiDeviceDialog(final MidiDeviceModelList deviceModelList) {
                setTitle(openAction.getValue(Action.NAME).toString());
                setBounds( 300, 300, 800, 500 );
                MidiDeviceTreeModel deviceTreeModel = new MidiDeviceTreeModel(deviceModelList);
                MidiDeviceTreeView deviceTreeView = new MidiDeviceTreeView(deviceTreeModel);
                final MidiDeviceInfoPane deviceInfoPane = new MidiDeviceInfoPane();
-               MidiOpenedDevicesView desktopPane = new MidiOpenedDevicesView(deviceTreeView, deviceInfoPane, this);
                deviceTreeView.addTreeSelectionListener(deviceInfoPane);
+               MidiOpenedDevicesView desktopPane = new MidiOpenedDevicesView(deviceTreeView, deviceInfoPane, this);
                deviceTreeView.addTreeSelectionListener(desktopPane);
                deviceTreeView.setSelectionRow(0);
                add(new JSplitPane(
index 75cfcdf..4fde422 100644 (file)
@@ -1,5 +1,6 @@
 package camidion.chordhelper.mididevice;
 
+import java.awt.BorderLayout;
 import java.awt.Rectangle;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
@@ -10,9 +11,10 @@ import java.awt.event.WindowListener;
 import javax.sound.midi.MidiDevice;
 import javax.sound.midi.Receiver;
 import javax.sound.midi.Transmitter;
-import javax.swing.BoxLayout;
 import javax.swing.JInternalFrame;
 import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JPanel;
 import javax.swing.JScrollPane;
 import javax.swing.Timer;
 
@@ -20,14 +22,27 @@ import javax.swing.Timer;
  * MIDIデバイスフレームビュー
  */
 public class MidiDeviceFrame extends JInternalFrame {
-       private MidiTransceiverListView transceiverListView;
+       private MidiDeviceModel deviceModel;
+       private MidiTransmitterListView transmitterListView;
+       private MidiReceiverListView receiverListView;
        private JScrollPane scrollPane;
+       private JPanel trxPanel, txPanel, rxPanel;
        private Timer timer;
        /**
-        * ã\81\93ã\81®ã\83\87ã\83\90ã\82¤ã\82¹ã\83\95ã\83¬ã\83¼ã\83 ã\81«è²¼ã\82\8aä»\98ã\81\91ã\82\89ã\82\8cã\81\9fä»®æ\83³MIDI端å­\90ã\83ªã\82¹ã\83\88ã\83\93ã\83¥ã\83¼を取得します。
-        * @return 仮想MIDI端子リストビュー
+        * ã\81\93ã\81®ã\83\87ã\83\90ã\82¤ã\82¹ã\83\95ã\83¬ã\83¼ã\83 ã\81«è¡¨ç¤ºå\86\85容ã\82\92æ\8f\90ä¾\9bã\81\97ã\81¦ã\81\84ã\82\8bMIDIã\83\87ã\83\90ã\82¤ã\82¹ã\83¢ã\83\87ã\83«を取得します。
+        * @return MIDIデバイスモデル
         */
-       public MidiTransceiverListView getMidiTransceiverListView() { return transceiverListView; }
+       public MidiDeviceModel getMidiDeviceModel() { return deviceModel; }
+       /**
+        * このデバイスフレームに貼り付けられたMIDIトランスミッタリストビューを取得します。
+        * @return MIDIトランスミッタリストビュー
+        */
+       public MidiTransmitterListView getMidiTransmitterListView() { return transmitterListView; }
+       /**
+        * このデバイスフレームに貼り付けられたMIDIトランシーバリストビューを取得します。
+        * @return MIDIトランシーバリストビュー
+        */
+       public MidiReceiverListView getMidiReceiverListView() { return receiverListView; }
        /**
         * ダイアログウィンドウがアクティブなときだけタイムスタンプ更新を有効にするためのリスナー
         */
@@ -38,43 +53,81 @@ public class MidiDeviceFrame extends JInternalFrame {
                public void windowActivated(WindowEvent e) { timer.start(); }
        };
        /**
-        * MIDIã\83\87ã\83\90ã\82¤ã\82¹ã\81®Transmitter/Receiverã\83ªã\82¹ã\83\88ã\83\93ã\83¥ã\83¼からフレームビューを構築します。
+        * MIDIã\83\87ã\83\90ã\82¤ã\82¹ã\83¢ã\83\87ã\83«からフレームビューを構築します。
         */
-       public MidiDeviceFrame(MidiTransceiverListView transceiverListView) {
+       public MidiDeviceFrame(MidiDeviceModel deviceModel, final MidiCablePane cablePane) {
                super( null, true, true, false, false );
-               this.transceiverListView = transceiverListView;
-               MidiTransceiverListModel model = transceiverListView.getModel();
-               setTitle("[" + model.getMidiDeviceInOutType().getShortName() + "] " + model);
+               this.deviceModel = deviceModel;
+               setTitle("[" + deviceModel.getMidiDeviceInOutType().getShortName() + "] " + deviceModel);
                setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
+               setLayout(new BorderLayout());
                add(new JLabel("--:--") {{
                        timer = new Timer(50, new ActionListener() {
                                private long sec = -2;
-                               private MidiDevice device = getMidiTransceiverListView().getModel().getMidiDevice();
+                               private MidiDevice device = getMidiDeviceModel().getMidiDevice();
                                @Override
-                               public void actionPerformed(ActionEvent e) {
+                               public void actionPerformed(ActionEvent event) {
                                        long usec = device.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));
+                                       cablePane.repaint();
                                }
                        });
-               }});
-               add(scrollPane = new JScrollPane(transceiverListView));
-               setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS));
+               }}, BorderLayout.SOUTH);
+               add(scrollPane = new JScrollPane(trxPanel = new JPanel() {{
+                       setLayout(new BorderLayout());
+                       MidiDeviceModel.ReceiverListModel rxListModel = getMidiDeviceModel().getReceiverList();
+                       if( rxListModel != null ) {
+                               receiverListView = new MidiReceiverListView(rxListModel, cablePane);
+                               add(rxPanel = new JPanel() {{
+                                       setLayout(new BorderLayout());
+                                       add(new JLabel("Rx") {{ setVerticalAlignment(TOP); }}, BorderLayout.WEST);
+                                       add(receiverListView);
+                               }}, BorderLayout.NORTH);
+                       }
+                       MidiDeviceModel.TransmitterListModel txListModel = getMidiDeviceModel().getTransmitterList();
+                       if( txListModel != null ) {
+                               transmitterListView = new MidiTransmitterListView(txListModel, cablePane);
+                               add(txPanel = new JPanel() {{
+                                       setLayout(new BorderLayout());
+                                       add(new JLabel("Tx") {{ setVerticalAlignment(TOP); }}, BorderLayout.WEST);
+                                       add(transmitterListView);
+                               }}, rxListModel == null ? BorderLayout.NORTH : BorderLayout.SOUTH);
+                       }
+               }}));
        }
        /**
-        * å¼\95æ\95°ã\81§æ\8c\87å®\9aã\81\95ã\82\8cã\81\9f{@link Transmitter}ã\81¾ã\81\9fã\81¯{@link Receiver}ã\81®ã\82»ã\83«ç¯\84å\9b²ã\82\92示ã\81\99ã\80\81
+        * 引数で指定された{@link Transmitter}のセル範囲を示す、
         * デスクトップの座標系内の境界の矩形を返します。対応するセルがない場合はnullを返します。
-        * @param transceiver {@link Transmitter}または{@link Receiver}
         * @return セル範囲を示す境界の矩形、またはnull
         */
-       public Rectangle getBoundsOf(AutoCloseable transceiver) {
-               Rectangle rect = transceiverListView.getCellBounds(transceiver);
-               if( rect != null ) rect.translate(
-                       getRootPane().getX() + getContentPane().getX() + scrollPane.getX() + getX(),
-                       getRootPane().getY() + getContentPane().getY() + scrollPane.getY() + getY()
-               );
+       public Rectangle getBoundsOf(Transmitter tx) {
+               if( transmitterListView == null ) return null;
+               Rectangle rect = transmitterListView.getCellBounds(tx);
+               translate(rect, txPanel, transmitterListView);
                return rect;
        }
+       /**
+        * 引数で指定された{@link Receiver}のセル範囲を示す、
+        * デスクトップの座標系内の境界の矩形を返します。対応するセルがない場合はnullを返します。
+        * @return セル範囲を示す境界の矩形、またはnull
+        */
+       public Rectangle getBoundsOf(Receiver rx) {
+               if( receiverListView == null ) return null;
+               Rectangle rect = receiverListView.getCellBounds(rx);
+               translate(rect, rxPanel, receiverListView);
+               return rect;
+       }
+       private void translate(Rectangle rect, JPanel panel, JList<? extends AutoCloseable> list) {
+               if( rect == null ) return;
+               int x = getX() + getRootPane().getX() + getContentPane().getX() +
+                               scrollPane.getX() + trxPanel.getX() +
+                               panel.getX() + list.getX();
+               int y = getY() + getRootPane().getY() + getContentPane().getY() +
+                               scrollPane.getY() + trxPanel.getY() +
+                               panel.getY() + list.getY();
+               rect.translate(x,y);
+       }
 }
index fd1b1c3..450ab33 100644 (file)
@@ -18,8 +18,8 @@ import javax.swing.tree.TreePath;
 public class MidiDeviceInfoPane extends JEditorPane implements TreeSelectionListener {
        private void setTreeNodeText(Object node) {
                String html = "<html><head></head><body>";
-               if( node instanceof MidiTransceiverListModel ) {
-                       MidiTransceiverListModel deviceModel = (MidiTransceiverListModel)node;
+               if( node instanceof MidiDeviceModel ) {
+                       MidiDeviceModel deviceModel = (MidiDeviceModel)node;
                        MidiDevice device = deviceModel.getMidiDevice();
                        MidiDevice.Info info = device.getDeviceInfo();
                        html += "<b>"+deviceModel+"</b><br/>"
@@ -53,13 +53,13 @@ public class MidiDeviceInfoPane extends JEditorPane implements TreeSelectionList
                public void internalFrameActivated(InternalFrameEvent e) {
                        JInternalFrame frame = e.getInternalFrame();
                        if( ! (frame instanceof MidiDeviceFrame ) ) return;
-                       setTreeNodeText(((MidiDeviceFrame)frame).getMidiTransceiverListView().getModel());
+                       setTreeNodeText(((MidiDeviceFrame)frame).getMidiDeviceModel());
                }
                @Override
                public void internalFrameClosing(InternalFrameEvent e) {
                        JInternalFrame frame = e.getInternalFrame();
                        if( ! (frame instanceof MidiDeviceFrame ) ) return;
-                       MidiTransceiverListModel m = ((MidiDeviceFrame)frame).getMidiTransceiverListView().getModel();
+                       MidiDeviceModel m = ((MidiDeviceFrame)frame).getMidiDeviceModel();
                        m.closeReceiver();
                        MidiDevice device = m.getMidiDevice();
                        device.close();
diff --git a/src/camidion/chordhelper/mididevice/MidiDeviceModel.java b/src/camidion/chordhelper/mididevice/MidiDeviceModel.java
new file mode 100644 (file)
index 0000000..ba07a58
--- /dev/null
@@ -0,0 +1,263 @@
+package camidion.chordhelper.mididevice;
+
+import java.util.List;
+import java.util.Vector;
+
+import javax.sound.midi.MidiDevice;
+import javax.sound.midi.MidiUnavailableException;
+import javax.sound.midi.Receiver;
+import javax.sound.midi.Sequencer;
+import javax.sound.midi.Transmitter;
+import javax.swing.AbstractListModel;
+import javax.swing.tree.TreePath;
+
+/**
+ * 1個の{@link MidiDevice}を表すモデル
+ */
+public class MidiDeviceModel {
+
+       /**
+        * {@link Receiver} のリストを表す {@link javax.swing.ListModel}
+        */
+       public class ReceiverListModel extends AbstractListModel<Receiver> {
+               @Override
+               public Receiver getElementAt(int index) { return device.getReceivers().get(index); }
+               @Override
+               public int getSize() { return device.getReceivers().size(); }
+               /**
+                * 指定の要素がこのリストモデルで最初に見つかった位置を返します。
+                *
+                * @param element 探したい要素
+                * @return 位置のインデックス(先頭が 0、見つからないとき -1)
+                */
+               public int indexOf(Receiver element) { return device.getReceivers().indexOf(element); }
+       }
+
+       /**
+        * 実体のない新規{@link Transmitter}を表すインターフェース
+        */
+       public interface NewTransmitter extends Transmitter {};
+       /**
+        * {@link Transmitter} のリストを表す {@link javax.swing.ListModel}
+        */
+       public class TransmitterListModel extends AbstractListModel<Transmitter> {
+               private NewTransmitter newTransmitter = new NewTransmitter() {
+                       @Override
+                       public void setReceiver(Receiver receiver) { }
+                       @Override
+                       public Receiver getReceiver() { return null; }
+                       @Override
+                       public void close() { }
+               };
+               @Override
+               public Transmitter getElementAt(int index) {
+                       List<Transmitter> txList = device.getTransmitters();
+                       int length = txList.size();
+                       if( index == length ) return newTransmitter;
+                       if( index > length || index < 0 ) return null;
+                       return txList.get(index);
+               }
+               @Override
+               public int getSize() { return device.getTransmitters().size() + 1; }
+               /**
+                * 指定の要素がこのリストモデルで最初に見つかった位置(先頭が 0)を返します。
+                * 見つからなかった場合は -1 を返します。
+                *
+                * @param element 探したい要素
+                * @return 位置のインデックス
+                */
+               public int indexOf(Transmitter element) {
+                       List<Transmitter> txList = device.getTransmitters();
+                       return element.equals(newTransmitter) ? txList.size() : txList.indexOf(element);
+               }
+               /**
+                * レシーバに未接続の最初の{@link Transmitter}を開いて返します。
+                * ない場合は {@link MidiDevice#getTransmitter} で新たに取得して返します。
+                *
+                * @return 新しく開かれた未接続の{@link Transmitter}
+                */
+               public Transmitter openTransmitter() {
+                       List<Transmitter> txList = device.getTransmitters();
+                       for( Transmitter tx : txList ) if( tx.getReceiver() == null ) return tx;
+                       Transmitter tx;
+                       try {
+                               tx = device.getTransmitter();
+                       } catch( MidiUnavailableException e ) {
+                               e.printStackTrace();
+                               return null;
+                       }
+                       fireIntervalAdded(this, 0, getSize());
+                       return tx;
+               }
+               /**
+                * このリストモデルで開いている指定の{@link Transmitter}があれば、
+                * それを閉じて表示を更新します。
+                * ない場合は無視されます。
+                * @param txToClose このリストモデルで開いている{@link Transmitter}
+                */
+               public void closeTransmitter(Transmitter txToClose) {
+                       if( ! device.getTransmitters().contains(txToClose) ) return;
+                       txToClose.close();
+                       fireIntervalRemoved(this, 0, getSize());
+               }
+       }
+
+       /**
+        * このリストのMIDIデバイスの入出力タイプを返します。
+        * @return このリストのMIDIデバイスの入出力タイプ
+        */
+       public MidiDeviceInOutType getMidiDeviceInOutType() { return ioType; }
+       private MidiDeviceInOutType ioType;
+
+       /**
+        * {@link javax.swing.JTree}で使用するツリー表示のパスを返します。
+        * @return ツリーパス
+        */
+       public TreePath getTreePath() { return treePath; }
+       private TreePath treePath;
+
+       /**
+        * 対象MIDIデバイスを返します。
+        * @return 対象MIDIデバイス
+        */
+       public MidiDevice getMidiDevice() { return device; }
+       protected MidiDevice device;
+       /**
+        * 対象MIDIデバイスの名前を返します。
+        */
+       @Override
+       public String toString() { return device.getDeviceInfo().toString(); }
+       /**
+        * このデバイスモデルが {@link Transmitter} をサポートしているか調べます。
+        * @return {@link Transmitter} をサポートしていたら true
+        */
+       public boolean txSupported() { return device.getMaxTransmitters() != 0; }
+       /**
+        * このデバイスモデルが {@link Receiver} をサポートしているか調べます。
+        * @return {@link Receiver} をサポートしていたら true
+        */
+       public boolean rxSupported() { return device.getMaxReceivers() != 0; }
+
+       /**
+        * {@link Transmitter} のリストモデルを返します。サポートしていない場合はnullを返します。
+        * @return リストモデルまたはnull
+        */
+       public TransmitterListModel getTransmitterList() { return transmitterList; }
+       private TransmitterListModel transmitterList;
+       /**
+        * {@link Receiver} のリストモデルを返します。サポートしていない場合はnullを返します。
+        * @return リストモデルまたはnull
+        */
+       public ReceiverListModel getReceiverList() { return receiverList; }
+       private ReceiverListModel receiverList;
+
+       protected MidiDeviceModelList deviceModelList;
+       /**
+        * MIDIデバイスモデルを構築します。
+        *
+        * @param device 対象MIDIデバイス
+        * @param deviceModelList 接続相手となりうるMIDIデバイスのリスト
+        */
+       public MidiDeviceModel(MidiDevice device, MidiDeviceModelList deviceModelList) {
+               this.device = device;
+               this.deviceModelList = deviceModelList;
+               if( txSupported() ) {
+                       transmitterList = new TransmitterListModel();
+               }
+               if( rxSupported() ) {
+                       ioType = txSupported() ? MidiDeviceInOutType.MIDI_IN_OUT :MidiDeviceInOutType.MIDI_OUT;
+                       receiverList = new ReceiverListModel();
+               }
+               else {
+                       ioType = txSupported() ? MidiDeviceInOutType.MIDI_IN :MidiDeviceInOutType.MIDI_NONE;
+               }
+               treePath = new TreePath(new Object[] {deviceModelList, ioType ,this});
+       }
+       /**
+        * 未接続の{@link Transmitter}を、引数で指定されたリストモデルの最初の{@link Receiver}に接続します。
+        * @param anotherDeviceModel 接続可能な{@link Receiver}を持つリストモデル
+        */
+       public void connectToReceiverOf(MidiDeviceModel anotherDeviceModel) {
+               if( ! txSupported() || anotherDeviceModel == null || ! anotherDeviceModel.rxSupported() ) return;
+               List<Receiver> rxList = anotherDeviceModel.device.getReceivers();
+               if( rxList.isEmpty() ) return;
+               transmitterList.openTransmitter().setReceiver(rxList.get(0));
+       }
+       /**
+        * {@link Receiver}を1個だけ開きます。サポートしていない場合は無視されます。
+        *
+        * @throws MidiUnavailableException デバイスを開くことができない場合
+        */
+       public void openReceiver() throws MidiUnavailableException {
+               if( rxSupported() && device.getReceivers().isEmpty() ) device.getReceiver();
+       }
+       /**
+        * {@link MidiDevice}を閉じる前に、{@link Receiver}を閉じます。
+        *
+        * 閉じようとしている{@link Receiver}を他デバイスの{@link Transmitter}が使用していた場合は、
+        * その{@link Transmitter}も閉じます。
+        */
+       public void closeReceiver() {
+               List<Receiver> rxList = device.getReceivers();
+               for( Receiver rx : rxList ) {
+                       for( MidiDeviceModel m : deviceModelList ) {
+                               if( m == this || ! m.txSupported() ) continue;
+                               for( int i=0; i<m.transmitterList.getSize(); i++ ) {
+                                       Transmitter tx = m.transmitterList.getElementAt(i);
+                                       if( tx.getReceiver() == rx ) m.transmitterList.closeTransmitter(tx);
+                               }
+                       }
+                       rx.close();
+               }
+       }
+       /**
+        * マイクロ秒位置をリセットします。
+        * <p>マイクロ秒位置はMIDIデバイスを開いてからの時間で表されます。
+        * このメソッドではMIDIデバイスをいったん閉じて再び開くことによって
+        * 時間位置をリセットします。
+        * 接続相手のデバイスがあった場合、元通りに接続を復元します。
+        * </p>
+        * <p>MIDIデバイスからリアルタイムレコーディングを開始するときは、
+        * 必ずマイクロ秒位置をリセットする必要があります。
+        * (リセットされていないマイクロ秒位置がそのままシーケンサに記録されると、
+        * 大幅に後ろのほうにずれて記録されてしまいます)
+        * </p>
+        */
+       public void resetMicrosecondPosition() {
+               if( ! txSupported() || device instanceof Sequencer ) return;
+               //
+               // デバイスを閉じる前に接続相手の情報を保存
+               List<Transmitter> myTxList = device.getTransmitters();
+               List<Receiver> peerRxList = new Vector<Receiver>();
+               Receiver rx;
+               for( Transmitter tx : myTxList ) if( (rx = tx.getReceiver()) != null ) peerRxList.add(rx);
+               List<Transmitter> peerTxList = null;
+               if( rxSupported() ) {
+                       rx = device.getReceivers().get(0);
+                       peerTxList = new Vector<Transmitter>();
+                       for( MidiDeviceModel m : deviceModelList ) {
+                               if( m == this || ! m.txSupported() ) continue;
+                               for( int i=0; i<m.transmitterList.getSize(); i++ ) {
+                                       Transmitter tx = m.transmitterList.getElementAt(i);
+                                       if( tx.getReceiver() == rx ) peerTxList.add(tx);
+                               }
+                       }
+               }
+               // いったん閉じて開く(ここでマイクロ秒位置がリセットされる)
+               device.close();
+               try {
+                       device.open();
+               } catch( MidiUnavailableException e ) {
+                       e.printStackTrace();
+               }
+               // 元通りに接続し直す
+               for( Receiver peerRx : peerRxList ) {
+                       Transmitter tx = transmitterList.openTransmitter();
+                       if( tx != null ) tx.setReceiver(peerRx);
+               }
+               if( peerTxList != null ) {
+                       rx = device.getReceivers().get(0);
+                       for( Transmitter peerTx : peerTxList ) peerTx.setReceiver(rx);
+               }
+       }
+}
\ No newline at end of file
@@ -12,21 +12,21 @@ import javax.sound.midi.Synthesizer;
 import camidion.chordhelper.ChordHelperApplet;
 
 /**
- * 仮想MIDIデバイスを含めたすべてのMIDIデバイスモデル {@link MidiTransceiverListModel} を収容するリスト
+ * 仮想MIDIデバイスを含めたすべてのMIDIデバイスモデル {@link MidiDeviceModel} を収容するリスト
  */
-public class MidiTransceiverListModelList extends Vector<MidiTransceiverListModel> {
+public class MidiDeviceModelList extends Vector<MidiDeviceModel> {
 
        public String toString() { return "MIDI devices"; }
 
        private MidiSequencerModel sequencerModel;
        public MidiSequencerModel getSequencerModel() { return sequencerModel; }
 
-       public MidiTransceiverListModelList(List<VirtualMidiDevice> guiVirtualDeviceList) {
+       public MidiDeviceModelList(List<VirtualMidiDevice> guiVirtualDeviceList) {
                //
                // GUI仮想MIDIデバイス
-               MidiTransceiverListModel guiModels[] = new MidiTransceiverListModel[guiVirtualDeviceList.size()];
+               MidiDeviceModel guiModels[] = new MidiDeviceModel[guiVirtualDeviceList.size()];
                for( int i=0; i<guiVirtualDeviceList.size(); i++ ) {
-                       addElement(guiModels[i] = new MidiTransceiverListModel(guiVirtualDeviceList.get(i), this));
+                       addElement(guiModels[i] = new MidiDeviceModel(guiVirtualDeviceList.get(i), this));
                }
                // シーケンサの取得
                Sequencer sequencer;
@@ -38,9 +38,9 @@ public class MidiTransceiverListModelList extends Vector<MidiTransceiverListMode
                        e.printStackTrace();
                }
                // その他のリアルMIDIデバイスの取得
-               MidiTransceiverListModel synthModel = null;
-               MidiTransceiverListModel firstMidiInModel = null;
-               MidiTransceiverListModel firstMidiOutModel = null;
+               MidiDeviceModel synthModel = null;
+               MidiDeviceModel firstMidiInModel = null;
+               MidiDeviceModel firstMidiOutModel = null;
                MidiDevice.Info[] deviceInfos = MidiSystem.getMidiDeviceInfo();
                for( MidiDevice.Info info : deviceInfos ) {
                        MidiDevice device;
@@ -55,7 +55,7 @@ public class MidiTransceiverListModelList extends Vector<MidiTransceiverListMode
                        // Java内蔵シンセサイザ
                        if( device instanceof Synthesizer ) {
                                try {
-                                       addElement(synthModel = new MidiTransceiverListModel(MidiSystem.getSynthesizer(), this));
+                                       addElement(synthModel = new MidiDeviceModel(MidiSystem.getSynthesizer(), this));
                                } catch( MidiUnavailableException e ) {
                                        System.out.println(ChordHelperApplet.VersionInfo.NAME +
                                                        " : Java internal MIDI synthesizer unavailable");
@@ -64,8 +64,8 @@ public class MidiTransceiverListModelList extends Vector<MidiTransceiverListMode
                                continue;
                        }
                        // その他のMIDIデバイス
-                       MidiTransceiverListModel m;
-                       addElement(m = new MidiTransceiverListModel(device, this));
+                       MidiDeviceModel m;
+                       addElement(m = new MidiDeviceModel(device, this));
                        if( m.rxSupported() && firstMidiOutModel == null ) firstMidiOutModel = m;
                        if( m.txSupported() && firstMidiInModel == null ) firstMidiInModel = m;
                }
@@ -81,17 +81,17 @@ public class MidiTransceiverListModelList extends Vector<MidiTransceiverListMode
                //   開く順序が逆になると「進みすぎるから遅らせよう」として
                //   無用なレイテンシーが発生する原因になる。
                try {
-                       MidiTransceiverListModel openModels[] = {
+                       MidiDeviceModel openModels[] = {
                                synthModel,
                                firstMidiOutModel,
                                sequencerModel,
                                firstMidiInModel,
                        };
-                       for( MidiTransceiverListModel m : openModels ) if( m != null ) {
+                       for( MidiDeviceModel m : openModels ) if( m != null ) {
                                m.getMidiDevice().open();
                                m.openReceiver();
                        }
-                       for( MidiTransceiverListModel m : guiModels ) {
+                       for( MidiDeviceModel m : guiModels ) {
                                m.getMidiDevice().open();
                                m.openReceiver();
                        }
@@ -100,35 +100,35 @@ public class MidiTransceiverListModelList extends Vector<MidiTransceiverListMode
                }
                // 初期接続
                //
-               for( MidiTransceiverListModel mtx : guiModels ) {
-                       for( MidiTransceiverListModel mrx : guiModels ) mtx.connectToReceiverOf(mrx);
+               for( MidiDeviceModel mtx : guiModels ) {
+                       for( MidiDeviceModel mrx : guiModels ) mtx.connectToReceiverOf(mrx);
                        mtx.connectToReceiverOf(sequencerModel);
                        mtx.connectToReceiverOf(synthModel);
                        mtx.connectToReceiverOf(firstMidiOutModel);
                }
                if( firstMidiInModel != null ) {
-                       for( MidiTransceiverListModel m : guiModels ) firstMidiInModel.connectToReceiverOf(m);
+                       for( MidiDeviceModel m : guiModels ) firstMidiInModel.connectToReceiverOf(m);
                        firstMidiInModel.connectToReceiverOf(sequencerModel);
                        firstMidiInModel.connectToReceiverOf(synthModel);
                        firstMidiInModel.connectToReceiverOf(firstMidiOutModel);
                }
                if( sequencerModel != null ) {
-                       for( MidiTransceiverListModel m : guiModels ) sequencerModel.connectToReceiverOf(m);
+                       for( MidiDeviceModel m : guiModels ) sequencerModel.connectToReceiverOf(m);
                        sequencerModel.connectToReceiverOf(synthModel);
                        sequencerModel.connectToReceiverOf(firstMidiOutModel);
                }
        }
        /**
-        * すべてのデバイスについて、{@link MidiTransceiverListModel#resetMicrosecondPosition()}
+        * すべてのデバイスについて、{@link MidiDeviceModel#resetMicrosecondPosition()}
         * でマイクロ秒位置をリセットします。
         */
        public void resetMicrosecondPosition() {
-               for(MidiTransceiverListModel m : this) m.resetMicrosecondPosition();
+               for(MidiDeviceModel m : this) m.resetMicrosecondPosition();
        }
        /**
         * すべてのMIDIデバイスを閉じます。
         */
        public void closeAllDevices() {
-               for(MidiTransceiverListModel m : this) m.device.close();
+               for(MidiDeviceModel m : this) m.device.close();
        }
 }
index bc338b8..621cbf0 100644 (file)
@@ -7,16 +7,16 @@ import javax.swing.tree.TreeModel;
 import javax.swing.tree.TreePath;
 
 /**
- * {@link MidiTransceiverListModelList}に収容されたMIDIデバイスを
+ * {@link MidiDeviceModelList}に収容されたMIDIデバイスを
  * {@link MidiDeviceInOutType}で分類して参照できるようにするツリーモデル
  */
 public class MidiDeviceTreeModel implements TreeModel {
 
-       private MidiTransceiverListModelList trxListModelList;
+       private MidiDeviceModelList trxListModelList;
 
-       public MidiTransceiverListModelList getTransceiverListModelList() { return trxListModelList; }
+       public MidiDeviceModelList getDeviceModelList() { return trxListModelList; }
 
-       public MidiDeviceTreeModel(MidiTransceiverListModelList trxListModelList) {
+       public MidiDeviceTreeModel(MidiDeviceModelList trxListModelList) {
                this.trxListModelList = trxListModelList;
        }
 
@@ -27,7 +27,7 @@ public class MidiDeviceTreeModel implements TreeModel {
                if( parent == getRoot() ) return MidiDeviceInOutType.values()[index + 1];
                if( parent instanceof MidiDeviceInOutType ) {
                        MidiDeviceInOutType ioType = (MidiDeviceInOutType)parent;
-                       for( MidiTransceiverListModel deviceModel : trxListModelList )
+                       for( MidiDeviceModel deviceModel : trxListModelList )
                                if( deviceModel.getMidiDeviceInOutType() == ioType ) {
                                        if( index == 0 ) return deviceModel;
                                        index--;
@@ -41,7 +41,7 @@ public class MidiDeviceTreeModel implements TreeModel {
                int childCount = 0;
                if( parent instanceof MidiDeviceInOutType ) {
                        MidiDeviceInOutType ioType = (MidiDeviceInOutType)parent;
-                       for( MidiTransceiverListModel deviceModel : trxListModelList )
+                       for( MidiDeviceModel deviceModel : trxListModelList )
                                if( deviceModel.getMidiDeviceInOutType() == ioType ) childCount++;
                }
                return childCount;
@@ -57,7 +57,7 @@ public class MidiDeviceTreeModel implements TreeModel {
                if( parent instanceof MidiDeviceInOutType ) {
                        MidiDeviceInOutType ioType = (MidiDeviceInOutType)parent;
                        int index = 0;
-                       for( MidiTransceiverListModel deviceModel : trxListModelList ) {
+                       for( MidiDeviceModel deviceModel : trxListModelList ) {
                                if( deviceModel.getMidiDeviceInOutType() == ioType ) {
                                        if( deviceModel == child ) return index;
                                        index++;
@@ -67,7 +67,7 @@ public class MidiDeviceTreeModel implements TreeModel {
                return -1;
        }
        @Override
-       public boolean isLeaf(Object node) { return node instanceof MidiTransceiverListModel; }
+       public boolean isLeaf(Object node) { return node instanceof MidiDeviceModel; }
        @Override
        public void valueForPathChanged(TreePath path, Object newValue) {}
        //
index e8bd6a0..9bcda1a 100644 (file)
@@ -10,11 +10,9 @@ import java.awt.dnd.DragGestureListener;
 import java.awt.dnd.DragSource;
 import java.awt.dnd.DragSourceAdapter;
 import java.awt.dnd.DragSourceDropEvent;
-import java.awt.dnd.DragSourceListener;
 
 import javax.swing.JInternalFrame;
 import javax.swing.JTree;
-import javax.swing.ListModel;
 import javax.swing.ToolTipManager;
 import javax.swing.event.InternalFrameAdapter;
 import javax.swing.event.InternalFrameEvent;
@@ -26,14 +24,14 @@ import javax.swing.tree.DefaultTreeCellRenderer;
  */
 public class MidiDeviceTreeView extends JTree {
 
-       public static final DataFlavor LIST_MODEL_FLAVOR  = new DataFlavor(ListModel.class,"ListModel");
+       public static final DataFlavor DEVICE_MODEL_FLAVOR  = new DataFlavor(MidiDeviceModel.class,"MidiDeviceModel");
 
-       public class DraggingObject implements Transferable {
-               private DataFlavor flavors[] = {LIST_MODEL_FLAVOR};
-               private MidiTransceiverListModel trxListModel;
+       public class DraggingDevice implements Transferable {
+               private DataFlavor flavors[] = {DEVICE_MODEL_FLAVOR};
+               private MidiDeviceModel midiDeviceModel;
                @Override
                public Object getTransferData(DataFlavor flavor) {
-                       return flavor.getRepresentationClass().isInstance(trxListModel) ? trxListModel : null;
+                       return flavor.getRepresentationClass().isInstance(midiDeviceModel) ? midiDeviceModel : null;
                }
                @Override
                public DataFlavor[] getTransferDataFlavors() { return flavors; }
@@ -42,16 +40,7 @@ public class MidiDeviceTreeView extends JTree {
                        return flavors[0].equals(flavor);
                }
        };
-       private DraggingObject draggingObject = new DraggingObject();
-       /**
-        * このツリーからドラッグしたMIDIデバイスのドロップが成功したときに再描画するためのリスナー
-        */
-       private DragSourceListener dragSourceListener = new DragSourceAdapter() {
-               @Override
-               public void dragDropEnd(DragSourceDropEvent dsde) {
-                       if( dsde.getDropSuccess() ) repaint();
-               }
-       };
+       private DraggingDevice draggingDevice = new DraggingDevice();
        /**
         *      {@link MidiDeviceFrame} が閉じられたり、選択されたりしたときに再描画するリスナー
         */
@@ -60,7 +49,7 @@ public class MidiDeviceTreeView extends JTree {
                public void internalFrameActivated(InternalFrameEvent e) {
                        JInternalFrame frame = e.getInternalFrame();
                        if( ! (frame instanceof MidiDeviceFrame ) ) return;
-                       setSelectionPath(((MidiDeviceFrame)frame).getMidiTransceiverListView().getModel().getTreePath());
+                       setSelectionPath(((MidiDeviceFrame)frame).getMidiDeviceModel().getTreePath());
                }
                @Override
                public void internalFrameClosing(InternalFrameEvent e) { repaint(); }
@@ -80,9 +69,14 @@ public class MidiDeviceTreeView extends JTree {
                                        if( (dge.getDragAction() & DnDConstants.ACTION_COPY_OR_MOVE) == 0 ) return;
                                        Point origin = dge.getDragOrigin();
                                        Object leaf = getPathForLocation(origin.x, origin.y).getLastPathComponent();
-                                       if( ! (leaf instanceof MidiTransceiverListModel) ) return;
-                                       draggingObject.trxListModel = (MidiTransceiverListModel)leaf;
-                                       dge.startDrag(DragSource.DefaultMoveDrop, draggingObject, dragSourceListener);
+                                       if( ! (leaf instanceof MidiDeviceModel) ) return;
+                                       draggingDevice.midiDeviceModel = (MidiDeviceModel)leaf;
+                                       dge.startDrag(DragSource.DefaultMoveDrop, draggingDevice, new DragSourceAdapter() {
+                                               @Override
+                                               public void dragDropEnd(DragSourceDropEvent dsde) {
+                                                       if( dsde.getDropSuccess() ) repaint();
+                                               }
+                                       });
                                }
                        }
                );
@@ -94,13 +88,13 @@ public class MidiDeviceTreeView extends JTree {
                                super.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus);
                                setToolTipText(value.toString());
                                if(leaf) {
-                                       MidiTransceiverListModel deviceModel = (MidiTransceiverListModel)value;
+                                       MidiDeviceModel deviceModel = (MidiDeviceModel)value;
                                        if( deviceModel.getMidiDevice().isOpen() ) {
-                                               setDisabledIcon(MidiTransceiverListView.MIDI_CONNECTER_ICON);
+                                               setDisabledIcon(MidiDeviceDialog.MIDI_CONNECTER_ICON);
                                                setEnabled(false);
                                                setToolTipText(getToolTipText()+"はすでに開いています");
                                        } else {
-                                               setIcon(MidiTransceiverListView.MIDI_CONNECTER_ICON);
+                                               setIcon(MidiDeviceDialog.MIDI_CONNECTER_ICON);
                                                setEnabled(true);
                                                setToolTipText("ドラッグ&ドロップで"+getToolTipText()+"が開きます");
                                        }
index 060727e..d35a2db 100644 (file)
@@ -27,7 +27,7 @@ import javax.swing.tree.TreePath;
  */
 public class MidiOpenedDevicesView extends JDesktopPane implements TreeSelectionListener {
 
-       private Map<MidiTransceiverListModel, MidiDeviceFrame> modelToFrame = new HashMap<>();
+       private Map<MidiDeviceModel, MidiDeviceFrame> modelToFrame = new HashMap<>();
 
        /**
         * ツリー上で選択状態が変わったとき、このデスクトップ上のフレームの選択状態に反映します。
@@ -37,8 +37,8 @@ public class MidiOpenedDevicesView extends JDesktopPane implements TreeSelection
                TreePath treePath = tse.getNewLeadSelectionPath();
                if( treePath != null ) {
                        Object lastSelected = treePath.getLastPathComponent();
-                       if( lastSelected instanceof MidiTransceiverListModel ) {
-                               MidiTransceiverListModel deviceModel = (MidiTransceiverListModel)lastSelected;
+                       if( lastSelected instanceof MidiDeviceModel ) {
+                               MidiDeviceModel deviceModel = (MidiDeviceModel)lastSelected;
                                if( deviceModel.getMidiDevice().isOpen() ) {
                                        // 開いているMIDIデバイスがツリー上で選択されたら、対応するフレームを選択
                                        MidiDeviceFrame deviceFrame = modelToFrame.get(deviceModel);
@@ -67,7 +67,7 @@ public class MidiOpenedDevicesView extends JDesktopPane implements TreeSelection
        private DropTargetListener dropTargetListener = new DropTargetAdapter() {
                @Override
                public void dragEnter(DropTargetDragEvent dtde) {
-                       if( dtde.getTransferable().isDataFlavorSupported(MidiDeviceTreeView.LIST_MODEL_FLAVOR) ) {
+                       if( dtde.getTransferable().isDataFlavorSupported(MidiDeviceTreeView.DEVICE_MODEL_FLAVOR) ) {
                                dtde.acceptDrag(DnDConstants.ACTION_COPY_OR_MOVE);
                        }
                }
@@ -81,16 +81,16 @@ public class MidiOpenedDevicesView extends JDesktopPane implements TreeSelection
                                        return;
                                }
                                Transferable t = dtde.getTransferable();
-                               if( ! t.isDataFlavorSupported(MidiDeviceTreeView.LIST_MODEL_FLAVOR) ) {
+                               if( ! t.isDataFlavorSupported(MidiDeviceTreeView.DEVICE_MODEL_FLAVOR) ) {
                                        dtde.dropComplete(false);
                                        return;
                                }
-                               Object data = t.getTransferData(MidiDeviceTreeView.LIST_MODEL_FLAVOR);
-                               if( ! (data instanceof MidiTransceiverListModel) ) {
+                               Object source = t.getTransferData(MidiDeviceTreeView.DEVICE_MODEL_FLAVOR);
+                               if( ! (source instanceof MidiDeviceModel) ) {
                                        dtde.dropComplete(false);
                                        return;
                                }
-                               MidiTransceiverListModel deviceModel = (MidiTransceiverListModel)data;
+                               MidiDeviceModel deviceModel = (MidiDeviceModel)source;
                                try {
                                        deviceModel.getMidiDevice().open();
                                        deviceModel.openReceiver();
@@ -119,7 +119,6 @@ public class MidiOpenedDevicesView extends JDesktopPane implements TreeSelection
                                        JOptionPane.showMessageDialog(null, message, title, JOptionPane.ERROR_MESSAGE);
                                        return;
                                }
-                               dtde.dropComplete(true);
                                //
                                // デバイスが正常に開かれたことを確認できたら
                                // ドロップした場所へフレームを配置して可視化する。
@@ -127,6 +126,7 @@ public class MidiOpenedDevicesView extends JDesktopPane implements TreeSelection
                                MidiDeviceFrame deviceFrame = modelToFrame.get(deviceModel);
                                deviceFrame.setLocation(dtde.getLocation());
                                deviceFrame.setVisible(true);
+                               dtde.dropComplete(true);
                        }
                        catch (Exception ex) {
                                ex.printStackTrace();
@@ -135,28 +135,36 @@ public class MidiOpenedDevicesView extends JDesktopPane implements TreeSelection
                }
        };
 
-       private MidiCablePane cablePane = new MidiCablePane(this);
+       MidiCablePane cablePane = new MidiCablePane(this);
 
-       public MidiOpenedDevicesView(
-                       MidiDeviceTreeView deviceTreeView,
-                       MidiDeviceInfoPane deviceInfoPane,
-                       MidiDeviceDialog dialog
-       ) {
+       public MidiOpenedDevicesView(MidiDeviceTreeView deviceTreeView,
+                       MidiDeviceInfoPane deviceInfoPane, MidiDeviceDialog dialog)
+       {
                add(cablePane, JLayeredPane.PALETTE_LAYER);
                addComponentListener(new ComponentAdapter() {
                        @Override
-                       public void componentResized(ComponentEvent e) { cablePane.setSize(getSize()); }
+                       public void componentResized(ComponentEvent e) {
+                               cablePane.setSize(getSize());
+                               cablePane.repaint();
+                       }
+                       @Override
+                       public void componentShown(ComponentEvent e) { cablePane.repaint(); }
                });
                int toX = 10;
                int toY = 10;
-               MidiTransceiverListModelList deviceModels = deviceTreeView.getModel().getTransceiverListModelList();
-               for( MidiTransceiverListModel deviceModel : deviceModels ) {
-                       MidiDeviceFrame frame = new MidiDeviceFrame(new MidiTransceiverListView(deviceModel, cablePane));
+               MidiDeviceModelList deviceModels = deviceTreeView.getModel().getDeviceModelList();
+               for( MidiDeviceModel deviceModel : deviceModels ) {
+                       MidiDeviceFrame frame = new MidiDeviceFrame(deviceModel, cablePane);
                        modelToFrame.put(deviceModel, frame);
                        //
-                       // MIDIデバイスモデルのMIDIコネクタリストが変化したときにMIDIケーブルを再描画
-                       deviceModel.addListDataListener(cablePane.midiConnecterListDataListener);
-                       //
+                       // トランスミッタリストモデルが変化したときにMIDIケーブルを再描画
+                       if( deviceModel.txSupported() ) {
+                               deviceModel.getTransmitterList().addListDataListener(cablePane.midiConnecterListDataListener);
+                       }
+                       // レシーバリストモデルが変化したときにMIDIケーブルを再描画
+                       if( deviceModel.rxSupported() ) {
+                               deviceModel.getReceiverList().addListDataListener(cablePane.midiConnecterListDataListener);
+                       }
                        // デバイスフレームが開閉したときの動作
                        frame.addInternalFrameListener(cablePane.midiDeviceFrameListener);
                        frame.addInternalFrameListener(deviceTreeView.midiDeviceFrameListener);
@@ -168,7 +176,7 @@ public class MidiOpenedDevicesView extends JDesktopPane implements TreeSelection
                        // ダイアログが閉じたときの動作
                        dialog.addWindowListener(frame.windowListener);
                        //
-                       frame.setSize(250, 90);
+                       frame.setSize(250, deviceModel.getMidiDeviceInOutType() == MidiDeviceInOutType.MIDI_IN_OUT ? 90 : 70);
                        add(frame);
                        if( deviceModel.getMidiDevice().isOpen() ) {
                                frame.setLocation(toX, toY);
diff --git a/src/camidion/chordhelper/mididevice/MidiReceiverListView.java b/src/camidion/chordhelper/mididevice/MidiReceiverListView.java
new file mode 100644 (file)
index 0000000..e75d2a8
--- /dev/null
@@ -0,0 +1,165 @@
+package camidion.chordhelper.mididevice;
+
+import java.awt.Component;
+import java.awt.Rectangle;
+import java.awt.datatransfer.DataFlavor;
+import java.awt.datatransfer.Transferable;
+import java.awt.dnd.DnDConstants;
+import java.awt.dnd.DragGestureEvent;
+import java.awt.dnd.DragGestureListener;
+import java.awt.dnd.DragSource;
+import java.awt.dnd.DragSourceAdapter;
+import java.awt.dnd.DragSourceDropEvent;
+import java.awt.dnd.DropTarget;
+import java.awt.dnd.DropTargetAdapter;
+import java.awt.dnd.DropTargetDragEvent;
+import java.awt.dnd.DropTargetDropEvent;
+import java.awt.dnd.DropTargetListener;
+import java.util.Arrays;
+import java.util.List;
+
+import javax.sound.midi.Receiver;
+import javax.sound.midi.Transmitter;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.ListCellRenderer;
+import javax.swing.ListSelectionModel;
+
+/**
+ * MIDIレシーバ({@link Receiver})のリストビューです。
+ * レシーバをこのビューからドラッグし、
+ * {@link MidiTransmitterListView} のトランスミッタにドロップして接続できます。
+ */
+public class MidiReceiverListView extends JList<Receiver> {
+       /**
+        * レシーバを描画するクラス
+        */
+       private static class CellRenderer extends JLabel implements ListCellRenderer<Receiver> {
+               public Component getListCellRendererComponent(JList<? extends Receiver> list,
+                               Receiver value, int index, boolean isSelected, boolean cellHasFocus)
+               {
+                       setEnabled(list.isEnabled());
+                       setFont(list.getFont());
+                       setOpaque(true);
+                       setIcon(MidiDeviceDialog.MIDI_CONNECTER_ICON);
+                       setToolTipText("ドラッグ&ドロップしてTxに接続");
+                       if (isSelected) {
+                               setBackground(list.getSelectionBackground());
+                               setForeground(list.getSelectionForeground());
+                       } else {
+                               setBackground(list.getBackground());
+                               setForeground(list.getForeground());
+                       }
+                       return this;
+               }
+       }
+       /**
+        * ドラッグ対象レシーバを表すクラス
+        */
+       private static class DraggingReceiver implements Transferable {
+               private static final List<DataFlavor> flavors = Arrays.asList(MidiDeviceDialog.receiverFlavor);
+               private Receiver rx;
+               @Override
+               public Object getTransferData(DataFlavor flavor) {
+                       return flavor.getRepresentationClass().isInstance(rx) ? rx : null;
+               }
+               @Override
+               public DataFlavor[] getTransferDataFlavors() { return (DataFlavor[]) flavors.toArray(); }
+               @Override
+               public boolean isDataFlavorSupported(DataFlavor flavor) { return flavors.contains(flavor); }
+       };
+       private static DraggingReceiver draggingReceiver = new DraggingReceiver();
+
+       /**
+        * 現在ドラッグされている{@link Receiver}を返します。
+        * ドラッグ中でなければnullを返します。
+        */
+       public Receiver getDraggingReceiver() { return draggingReceiver.rx; }
+
+       private MidiCablePane cablePane;
+       /**
+        * 仮想MIDI端子リストビューを生成します。
+        * @param model このビューから参照されるデータモデル
+        * @param cablePane MIDIケーブル描画面
+        */
+       public MidiReceiverListView(MidiDeviceModel.ReceiverListModel model, MidiCablePane cablePane) {
+               super(model);
+               this.cablePane = cablePane;
+               setCellRenderer(new CellRenderer());
+               setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+               setLayoutOrientation(JList.HORIZONTAL_WRAP);
+               setVisibleRowCount(0);
+               //
+               // レシーバのドラッグを受け付ける
+               DragSource dragSource = new DragSource();
+               DragGestureListener dgl = new DragGestureListener() {
+                       @Override
+                       public void dragGestureRecognized(DragGestureEvent event) {
+                               if( (event.getDragAction() & DnDConstants.ACTION_COPY_OR_MOVE) == 0 ) return;
+                               draggingReceiver.rx = getModel().getElementAt(locationToIndex(event.getDragOrigin()));
+                               event.startDrag(DragSource.DefaultLinkDrop, draggingReceiver, new DragSourceAdapter() {
+                                       @Override
+                                       public void dragDropEnd(DragSourceDropEvent event) {
+                                               draggingReceiver.rx = null;
+                                               MidiReceiverListView.this.cablePane.dragDropEnd();
+                                       }
+                               });
+                       }
+               };
+               dragSource.createDefaultDragGestureRecognizer(this, DnDConstants.ACTION_COPY_OR_MOVE, dgl);
+               dragSource.addDragSourceMotionListener(cablePane);
+               //
+               // トランスミッタのドロップを受け付ける
+               DropTargetListener dtl = new DropTargetAdapter() {
+                       @Override
+                       public void dragEnter(DropTargetDragEvent event) {
+                               if( event.isDataFlavorSupported(MidiDeviceDialog.transmitterFlavor) ) {
+                                       event.acceptDrag(DnDConstants.ACTION_COPY_OR_MOVE);
+                               }
+                       }
+                       @Override
+                       public void drop(DropTargetDropEvent event) {
+                               event.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
+                               try {
+                                       int maskedBits = event.getDropAction() & DnDConstants.ACTION_COPY_OR_MOVE;
+                                       if( maskedBits == 0 ) {
+                                               event.dropComplete(false);
+                                               return;
+                                       }
+                                       Transferable t = event.getTransferable();
+                                       if( ! t.isDataFlavorSupported(MidiDeviceDialog.transmitterFlavor) ) {
+                                               event.dropComplete(false);
+                                               return;
+                                       }
+                                       Object source = t.getTransferData(MidiDeviceDialog.transmitterFlavor);
+                                       if( ! (source instanceof Transmitter) ) {
+                                               event.dropComplete(false);
+                                               return;
+                                       }
+                                       Receiver destRx = getModel().getElementAt(locationToIndex(event.getLocation()));
+                                       ((Transmitter)source).setReceiver(destRx);
+                                       event.dropComplete(true);
+                                       return;
+                               }
+                               catch (Exception ex) {
+                                       ex.printStackTrace();
+                               }
+                               event.dropComplete(false);
+                       }
+               };
+               new DropTarget( this, DnDConstants.ACTION_COPY_OR_MOVE, dtl, true );
+       }
+       @Override
+       public MidiDeviceModel.ReceiverListModel getModel() {
+               return (MidiDeviceModel.ReceiverListModel) super.getModel();
+       }
+       /**
+        * 引数で指定された{@link Receiver}のセル範囲を示す、
+        * リストの座標系内の境界の矩形を返します。対応するセルがない場合はnullを返します。
+        * @return セル範囲を示す境界の矩形、またはnull
+        */
+       public Rectangle getCellBounds(Receiver rx) {
+               int index = getModel().indexOf(rx);
+               return getCellBounds(index,index);
+       }
+}
index 8fa2cf2..f944066 100644 (file)
@@ -17,6 +17,7 @@ import javax.swing.DefaultComboBoxModel;
 import javax.swing.Icon;
 import javax.swing.event.ChangeEvent;
 import javax.swing.event.ChangeListener;
+import javax.swing.event.EventListenerList;
 import javax.swing.event.ListDataEvent;
 import javax.swing.event.ListDataListener;
 
@@ -28,13 +29,13 @@ import camidion.chordhelper.midieditor.SequencerSpeedSlider;
 /**
  * MIDIシーケンサモデル
  */
-public class MidiSequencerModel extends MidiTransceiverListModel implements BoundedRangeModel {
+public class MidiSequencerModel extends MidiDeviceModel implements BoundedRangeModel {
        /**
         * MIDIシーケンサモデルを構築します。
         * @param sequencer シーケンサーMIDIデバイス
         * @param deviceModelList 親のMIDIデバイスモデルリスト
         */
-       public MidiSequencerModel(Sequencer sequencer, MidiTransceiverListModelList deviceModelList) {
+       public MidiSequencerModel(Sequencer sequencer, MidiDeviceModelList deviceModelList) {
                super(sequencer, deviceModelList);
        }
        /**
@@ -199,6 +200,7 @@ public class MidiSequencerModel extends MidiTransceiverListModel implements Boun
                setValueIsAdjusting(valueIsAdjusting);
                fireStateChanged();
        }
+       protected EventListenerList listenerList = new EventListenerList();
        /**
         * {@inheritDoc}
         * <p>このシーケンサーの再生時間位置または再生対象ファイルが変更されたときに
diff --git a/src/camidion/chordhelper/mididevice/MidiTransceiverListModel.java b/src/camidion/chordhelper/mididevice/MidiTransceiverListModel.java
deleted file mode 100644 (file)
index 6783565..0000000
+++ /dev/null
@@ -1,237 +0,0 @@
-package camidion.chordhelper.mididevice;
-
-import java.util.List;
-import java.util.Vector;
-
-import javax.sound.midi.MidiDevice;
-import javax.sound.midi.MidiUnavailableException;
-import javax.sound.midi.Receiver;
-import javax.sound.midi.Sequencer;
-import javax.sound.midi.Transmitter;
-import javax.swing.AbstractListModel;
-import javax.swing.tree.TreePath;
-
-/**
- * 1個の{@link MidiDevice}で開かれている{@link Transmitter}/{@link Receiver}のリストモデル
- */
-public class MidiTransceiverListModel extends AbstractListModel<AutoCloseable> {
-       protected MidiDevice device;
-       protected MidiTransceiverListModelList deviceModelList;
-       private MidiDeviceInOutType ioType;
-       private TreePath treePath;
-       /**
-        * 実体のない新規{@link Transmitter}を表すインターフェース
-        */
-       public interface NewTransmitter extends Transmitter {};
-       private NewTransmitter newTransmitter;
-       /**
-        * 指定のMIDIデバイスに属する {@link Transmitter}/{@link Receiver} のリストモデルを構築します。
-        *
-        * @param device 対象MIDIデバイス
-        * @param deviceModelList 接続相手となりうるMIDIデバイスのリスト
-        */
-       public MidiTransceiverListModel(MidiDevice device, MidiTransceiverListModelList deviceModelList) {
-               this.device = device;
-               this.deviceModelList = deviceModelList;
-               if( txSupported() ) {
-                       newTransmitter = new NewTransmitter() {
-                               @Override
-                               public void setReceiver(Receiver receiver) { }
-                               @Override
-                               public Receiver getReceiver() { return null; }
-                               @Override
-                               public void close() { }
-                       };
-               }
-               ioType = rxSupported() ?
-                       (txSupported() ? MidiDeviceInOutType.MIDI_IN_OUT : MidiDeviceInOutType.MIDI_OUT) :
-                       (txSupported() ? MidiDeviceInOutType.MIDI_IN     : MidiDeviceInOutType.MIDI_NONE);
-               treePath = new TreePath(new Object[] {deviceModelList, ioType ,this});
-       }
-       /**
-        * 対象MIDIデバイスを返します。
-        * @return 対象MIDIデバイス
-        */
-       public MidiDevice getMidiDevice() { return device; }
-       /**
-        * {@link javax.swing.JTree}で使用するツリー表示のパスを返します。
-        * @return ツリーパス
-        */
-       public TreePath getTreePath() { return treePath; }
-       /**
-        * 対象MIDIデバイスの名前を返します。
-        */
-       @Override
-       public String toString() { return device.getDeviceInfo().toString(); }
-       @Override
-       public AutoCloseable getElementAt(int index) {
-               List<Receiver> rxList = device.getReceivers();
-               int rxSize = rxList.size();
-               if( index < rxSize ) return rxList.get(index);
-               index -= rxSize;
-               List<Transmitter> txList = device.getTransmitters();
-               int txSize = txList.size();
-               if( index < txSize ) return txList.get(index);
-               if( index == txSize ) return newTransmitter;
-               return null;
-       }
-       @Override
-       public int getSize() {
-               int txSize = txSupported() ? device.getTransmitters().size() + 1 : 0;
-               return device.getReceivers().size() + txSize;
-       }
-       /**
-        * 指定の要素がこのリストモデルで最初に見つかった位置を返します。
-        *
-        * @param element 探したい要素
-        * @return 位置のインデックス(先頭が 0、見つからないとき -1)
-        */
-       public int indexOf(AutoCloseable element) {
-               int index;
-               List<Receiver> rxList = device.getReceivers();
-               if( (index = rxList.indexOf(element)) >= 0 ) return index;
-               List<Transmitter> txList = device.getTransmitters();
-               if( (index = txList.indexOf(element)) >= 0 ) return rxList.size() + index;
-               if( element.equals(newTransmitter) ) return rxList.size() + txList.size();
-               return -1;
-       }
-       /**
-        * このリストが {@link Transmitter} をサポートしているか調べます。
-        * @return {@link Transmitter} をサポートしていたら true
-        */
-       public boolean txSupported() { return device.getMaxTransmitters() != 0; }
-       /**
-        * このリストが {@link Receiver} をサポートしているか調べます。
-        * @return {@link Receiver} をサポートしていたら true
-        */
-       public boolean rxSupported() { return device.getMaxReceivers() != 0; }
-       /**
-        * このリストのMIDIデバイスの入出力タイプを返します。
-        * @return このリストのMIDIデバイスの入出力タイプ
-        */
-       public MidiDeviceInOutType getMidiDeviceInOutType() { return ioType; }
-       /**
-        * 未接続の{@link Transmitter}を、引数で指定されたリストモデルの最初の{@link Receiver}に接続します。
-        * @param anotherModel 接続可能な{@link Receiver}を持つリストモデル
-        */
-       public void connectToReceiverOf(MidiTransceiverListModel anotherModel) {
-               if( ! txSupported() || anotherModel == null || ! anotherModel.rxSupported() ) return;
-               List<Receiver> rxList = anotherModel.device.getReceivers();
-               if( rxList.isEmpty() ) return;
-               openTransmitter().setReceiver(rxList.get(0));
-       }
-       /**
-        * レシーバに未接続の最初の{@link Transmitter}を開いて返します。
-        * ない場合は {@link MidiDevice#getTransmitter} で新たに取得して返します。
-        *
-        * @return 新しく開かれた未接続の{@link Transmitter}
-        */
-       public Transmitter openTransmitter() {
-               if( ! txSupported() ) return null;
-               List<Transmitter> txList = device.getTransmitters();
-               for( Transmitter tx : txList ) if( tx.getReceiver() == null ) return tx;
-               Transmitter tx;
-               try {
-                       tx = device.getTransmitter();
-               } catch( MidiUnavailableException e ) {
-                       e.printStackTrace();
-                       return null;
-               }
-               fireIntervalAdded(this,0,getSize());
-               return tx;
-       }
-       /**
-        * このリストモデルで開いている指定の{@link Transmitter}があれば、
-        * それを閉じて表示を更新します。
-        * ない場合は無視されます。
-        * @param txToClose このリストモデルで開いている{@link Transmitter}
-        */
-       public void closeTransmitter(Transmitter txToClose) {
-               if( ! device.getTransmitters().contains(txToClose) ) return;
-               txToClose.close();
-               fireIntervalRemoved(this,0,getSize());
-       }
-       /**
-        * {@link Receiver}を1個だけ開きます。サポートしていない場合は無視されます。
-        *
-        * @throws MidiUnavailableException デバイスを開くことができない場合
-        */
-       public void openReceiver() throws MidiUnavailableException {
-               if( rxSupported() && device.getReceivers().isEmpty() ) device.getReceiver();
-       }
-       /**
-        * {@link MidiDevice}を閉じる前に、{@link Receiver}を閉じます。
-        *
-        * 閉じようとしている{@link Receiver}を他デバイスの{@link Transmitter}が使用していた場合は、
-        * その{@link Transmitter}も閉じます。
-        */
-       public void closeReceiver() {
-               List<Receiver> rxList = device.getReceivers();
-               for( Receiver rx : rxList ) {
-                       for( MidiTransceiverListModel m : deviceModelList ) {
-                               if( m == this || ! m.txSupported() ) continue;
-                               for( int i=0; i<m.getSize(); i++ ) {
-                                       AutoCloseable ac = m.getElementAt(i);
-                                       if( ! (ac instanceof Transmitter) ) continue;
-                                       Transmitter tx = ((Transmitter)ac);
-                                       if( tx.getReceiver() == rx ) m.closeTransmitter(tx);
-                               }
-                       }
-                       rx.close();
-               }
-       }
-       /**
-        * マイクロ秒位置をリセットします。
-        * <p>マイクロ秒位置はMIDIデバイスを開いてからの時間で表されます。
-        * このメソッドではMIDIデバイスをいったん閉じて再び開くことによって
-        * 時間位置をリセットします。
-        * 接続相手のデバイスがあった場合、元通りに接続を復元します。
-        * </p>
-        * <p>MIDIデバイスからリアルタイムレコーディングを開始するときは、
-        * 必ずマイクロ秒位置をリセットする必要があります。
-        * (リセットされていないマイクロ秒位置がそのままシーケンサに記録されると、
-        * 大幅に後ろのほうにずれて記録されてしまいます)
-        * </p>
-        */
-       public void resetMicrosecondPosition() {
-               if( ! txSupported() || device instanceof Sequencer ) return;
-               //
-               // デバイスを閉じる前に接続相手の情報を保存
-               List<Transmitter> myTxList = device.getTransmitters();
-               List<Receiver> peerRxList = new Vector<Receiver>();
-               Receiver rx;
-               for( Transmitter tx : myTxList ) {
-                       if( (rx = tx.getReceiver()) != null ) peerRxList.add(rx);
-               }
-               List<Transmitter> peerTxList = null;
-               if( rxSupported() ) {
-                       rx = device.getReceivers().get(0);
-                       peerTxList = new Vector<Transmitter>();
-                       for( MidiTransceiverListModel m : deviceModelList ) {
-                               if( m == this || ! m.txSupported() ) continue;
-                               for( int i=0; i<m.getSize(); i++ ) {
-                                       Object obj = m.getElementAt(i);
-                                       if( ! (obj instanceof Transmitter) ) continue;
-                                       Transmitter tx = ((Transmitter)obj);
-                                       if( tx.getReceiver() == rx ) peerTxList.add(tx);
-                               }
-                       }
-               }
-               // いったん閉じて開く(ここでマイクロ秒位置がリセットされる)
-               device.close();
-               try {
-                       device.open();
-               } catch( MidiUnavailableException e ) {
-                       e.printStackTrace();
-               }
-               // 元通りに接続し直す
-               for( Receiver peerRx : peerRxList ) {
-                       Transmitter tx = openTransmitter();
-                       if( tx != null ) tx.setReceiver(peerRx);
-               }
-               if( peerTxList != null ) {
-                       rx = device.getReceivers().get(0);
-                       for( Transmitter peerTx : peerTxList ) peerTx.setReceiver(rx);
-               }
-       }
-}
\ No newline at end of file
diff --git a/src/camidion/chordhelper/mididevice/MidiTransceiverListView.java b/src/camidion/chordhelper/mididevice/MidiTransceiverListView.java
deleted file mode 100644 (file)
index 7027bc5..0000000
+++ /dev/null
@@ -1,210 +0,0 @@
-package camidion.chordhelper.mididevice;
-
-import java.awt.Component;
-import java.awt.Rectangle;
-import java.awt.datatransfer.DataFlavor;
-import java.awt.datatransfer.Transferable;
-import java.awt.dnd.DnDConstants;
-import java.awt.dnd.DragGestureEvent;
-import java.awt.dnd.DragGestureListener;
-import java.awt.dnd.DragSource;
-import java.awt.dnd.DragSourceAdapter;
-import java.awt.dnd.DragSourceDropEvent;
-import java.awt.dnd.DragSourceListener;
-import java.awt.dnd.DropTarget;
-import java.awt.dnd.DropTargetAdapter;
-import java.awt.dnd.DropTargetDragEvent;
-import java.awt.dnd.DropTargetDropEvent;
-import java.awt.dnd.DropTargetListener;
-import java.util.Arrays;
-import java.util.List;
-
-import javax.sound.midi.Receiver;
-import javax.sound.midi.Transmitter;
-import javax.swing.Icon;
-import javax.swing.JLabel;
-import javax.swing.JList;
-import javax.swing.ListCellRenderer;
-import javax.swing.ListSelectionModel;
-
-import camidion.chordhelper.ButtonIcon;
-import camidion.chordhelper.mididevice.MidiTransceiverListModel.NewTransmitter;
-
-/**
- * MIDI端子({@link Transmitter}と{@link Receiver})のリストビューです。
- * リストの要素をドラッグ&ドロップすることで、
- * 2つのMIDI端子を仮想的なMIDIケーブルで接続することができます。
- */
-public class MidiTransceiverListView extends JList<AutoCloseable> {
-
-       public static final Icon MIDI_CONNECTER_ICON = new ButtonIcon(ButtonIcon.MIDI_CONNECTOR_ICON);
-       /**
-        * リストに登録されている仮想MIDI端子の描画ツール
-        */
-       private static class CellRenderer extends JLabel implements ListCellRenderer<AutoCloseable> {
-               public Component getListCellRendererComponent(
-                               JList<? extends AutoCloseable> list, AutoCloseable value, int index,
-                               boolean isSelected, boolean cellHasFocus)
-               {
-                       setEnabled(list.isEnabled());
-                       setFont(list.getFont());
-                       setOpaque(true);
-                       setIcon(MIDI_CONNECTER_ICON);
-                       if( value == null ) {
-                               setText(null);
-                               setToolTipText(null);
-                       } else if( value instanceof Receiver ) {
-                               setText("Rx");
-                               setToolTipText("Rxをドラッグ&ドロップしてTxに接続");
-                       } else if( value instanceof Transmitter ) {
-                               setText("Tx");
-                               if( value instanceof NewTransmitter ) {
-                                       setToolTipText("Txをドラッグ&ドロップしてRxに接続");
-                               } else {
-                                       setToolTipText("Txをドラッグ&ドロップして切断、またはRx切替");
-                               }
-                       }
-                       else {
-                               setText(value.toString());
-                               setToolTipText(value.toString());
-                       }
-                       if (isSelected) {
-                               setBackground(list.getSelectionBackground());
-                               setForeground(list.getSelectionForeground());
-                       } else {
-                               setBackground(list.getBackground());
-                               setForeground(list.getForeground());
-                       }
-                       return this;
-               }
-       }
-
-       private static final DataFlavor transmitterFlavor = new DataFlavor(Transmitter.class, "Transmitter");
-       private static final DataFlavor receiverFlavor = new DataFlavor(Receiver.class, "Receiver");
-       /**
-        * ドラッグ対象を表すクラス
-        */
-       private static class DraggingObject implements Transferable {
-               private static final List<DataFlavor> flavors = Arrays.asList(transmitterFlavor, receiverFlavor);
-               private AutoCloseable trx;
-               @Override
-               public Object getTransferData(DataFlavor flavor) {
-                       return flavor.getRepresentationClass().isInstance(trx) ? trx : null;
-               }
-               @Override
-               public DataFlavor[] getTransferDataFlavors() { return (DataFlavor[]) flavors.toArray(); }
-               @Override
-               public boolean isDataFlavorSupported(DataFlavor flavor) { return flavors.contains(flavor); }
-       };
-       private static DraggingObject draggingObject = new DraggingObject();
-
-       /**
-        * 現在ドラッグされている{@link Transmitter}または{@link Receiver}を返します。
-        * ドラッグ中でなければnullを返します。
-        */
-       public AutoCloseable getDraggingTransceiver() { return draggingObject.trx; }
-
-       private MidiCablePane cablePane;
-
-       private DragSourceListener dragSourceListener = new DragSourceAdapter() {
-               @Override
-               public void dragDropEnd(DragSourceDropEvent dsde) {
-                       if( draggingObject.trx instanceof Transmitter && ! dsde.getDropSuccess() ) {
-                               getModel().closeTransmitter((Transmitter)draggingObject.trx);
-                       }
-                       draggingObject.trx = null;
-                       cablePane.dragDropEnd();
-               }
-       };
-       /**
-        * 仮想MIDI端子リストビューを生成します。
-        * @param model このビューから参照されるデータモデル
-        * @param cablePane MIDIケーブル描画面
-        */
-       public MidiTransceiverListView(MidiTransceiverListModel model, MidiCablePane cablePane) {
-               super(model);
-               this.cablePane = cablePane;
-               setCellRenderer(new CellRenderer());
-               setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
-               setLayoutOrientation(JList.HORIZONTAL_WRAP);
-               setVisibleRowCount(0);
-               DragSource dragSource = new DragSource();
-               DragGestureListener dgl = new DragGestureListener() {
-                       @Override
-                       public void dragGestureRecognized(DragGestureEvent dge) {
-                               if( (dge.getDragAction() & DnDConstants.ACTION_COPY_OR_MOVE) == 0 ) return;
-                               MidiTransceiverListModel m = getModel();
-                               AutoCloseable source = m.getElementAt(locationToIndex(dge.getDragOrigin()));
-                               if( source instanceof MidiTransceiverListModel.NewTransmitter ) {
-                                       draggingObject.trx = m.openTransmitter();
-                               } else if( source instanceof Transmitter || source instanceof Receiver ) {
-                                       draggingObject.trx = source;
-                               } else {
-                                       return;
-                               }
-                               dge.startDrag(DragSource.DefaultLinkDrop, draggingObject, dragSourceListener);
-                       }
-               };
-               dragSource.createDefaultDragGestureRecognizer(this, DnDConstants.ACTION_COPY_OR_MOVE, dgl);
-               dragSource.addDragSourceMotionListener(cablePane);
-               DropTargetListener dtl = new DropTargetAdapter() {
-                       @Override
-                       public void dragEnter(DropTargetDragEvent event) {
-                               if( event.isDataFlavorSupported(transmitterFlavor) || event.isDataFlavorSupported(receiverFlavor) ) {
-                                       event.acceptDrag(DnDConstants.ACTION_COPY_OR_MOVE);
-                               }
-                       }
-                       @Override
-                       public void drop(DropTargetDropEvent event) {
-                               event.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
-                               try {
-                                       int maskedBits = event.getDropAction() & DnDConstants.ACTION_COPY_OR_MOVE;
-                                       if( maskedBits == 0 ) {
-                                               event.dropComplete(false);
-                                               return;
-                                       }
-                                       MidiTransceiverListModel m = getModel();
-                                       AutoCloseable destination = m.getElementAt(locationToIndex(event.getLocation()));
-                                       Transferable t = event.getTransferable();
-                                       if( t.isDataFlavorSupported(transmitterFlavor) || t.isDataFlavorSupported(receiverFlavor) ) {
-                                               Object source;
-                                               if( (source = t.getTransferData(transmitterFlavor)) != null ) {
-                                                       if( destination instanceof Receiver ) {
-                                                               ((Transmitter)source).setReceiver((Receiver)destination);
-                                                               event.dropComplete(true);
-                                                               return;
-                                                       }
-                                               } else if( (source = t.getTransferData(receiverFlavor)) != null ) {
-                                                       if( destination instanceof Transmitter ) {
-                                                               Transmitter tx = (Transmitter)destination;
-                                                               if( tx instanceof MidiTransceiverListModel.NewTransmitter ) {
-                                                                       tx = m.openTransmitter();
-                                                               }
-                                                               tx.setReceiver((Receiver)source);
-                                                               event.dropComplete(true);
-                                                               return;
-                                                       }
-                                               }
-                                       }
-                               }
-                               catch (Exception ex) {
-                                       ex.printStackTrace();
-                               }
-                               event.dropComplete(false);
-                       }
-               };
-               new DropTarget( this, DnDConstants.ACTION_COPY_OR_MOVE, dtl, true );
-       }
-       @Override
-       public MidiTransceiverListModel getModel() { return (MidiTransceiverListModel)super.getModel(); }
-       /**
-        * 引数で指定された{@link Transmitter}または{@link Receiver}のセル範囲を示す、
-        * リストの座標系内の境界の矩形を返します。対応するセルがない場合はnullを返します。
-        * @param transceiver {@link Transmitter}または{@link Receiver}
-        * @return セル範囲を示す境界の矩形、またはnull
-        */
-       public Rectangle getCellBounds(AutoCloseable transceiver) {
-               int index = getModel().indexOf(transceiver);
-               return getCellBounds(index,index);
-       }
-}
diff --git a/src/camidion/chordhelper/mididevice/MidiTransmitterListView.java b/src/camidion/chordhelper/mididevice/MidiTransmitterListView.java
new file mode 100644 (file)
index 0000000..7423d2b
--- /dev/null
@@ -0,0 +1,181 @@
+package camidion.chordhelper.mididevice;
+
+import java.awt.Component;
+import java.awt.Rectangle;
+import java.awt.datatransfer.DataFlavor;
+import java.awt.datatransfer.Transferable;
+import java.awt.dnd.DnDConstants;
+import java.awt.dnd.DragGestureEvent;
+import java.awt.dnd.DragGestureListener;
+import java.awt.dnd.DragSource;
+import java.awt.dnd.DragSourceAdapter;
+import java.awt.dnd.DragSourceDropEvent;
+import java.awt.dnd.DropTarget;
+import java.awt.dnd.DropTargetAdapter;
+import java.awt.dnd.DropTargetDragEvent;
+import java.awt.dnd.DropTargetDropEvent;
+import java.awt.dnd.DropTargetListener;
+import java.util.Arrays;
+import java.util.List;
+
+import javax.sound.midi.Receiver;
+import javax.sound.midi.Transmitter;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.ListCellRenderer;
+import javax.swing.ListSelectionModel;
+
+import camidion.chordhelper.mididevice.MidiDeviceModel.NewTransmitter;
+
+/**
+ * MIDIトランスミッタ({@link Transmitter})のリストビューです。
+ * トランスミッタをこのビューからドラッグし、
+ * {@link MidiReceiverListView} のレシーバにドロップして接続できます。
+ */
+public class MidiTransmitterListView extends JList<Transmitter> {
+       /**
+        * トランスミッタを描画するクラス
+        */
+       private static class CellRenderer extends JLabel implements ListCellRenderer<Transmitter> {
+               public Component getListCellRendererComponent(JList<? extends Transmitter> list,
+                               Transmitter value, int index, boolean isSelected, boolean cellHasFocus)
+               {
+                       setEnabled(list.isEnabled());
+                       setFont(list.getFont());
+                       setOpaque(true);
+                       setIcon(MidiDeviceDialog.MIDI_CONNECTER_ICON);
+                       if( value == null ) {
+                               setToolTipText(null);
+                       } else {
+                               if( value instanceof NewTransmitter ) {
+                                       setToolTipText("ドラッグ&ドロップしてRxに接続");
+                               } else {
+                                       setToolTipText("ドラッグ&ドロップして切断、またはRx切替");
+                               }
+                       }
+                       if (isSelected) {
+                               setBackground(list.getSelectionBackground());
+                               setForeground(list.getSelectionForeground());
+                       } else {
+                               setBackground(list.getBackground());
+                               setForeground(list.getForeground());
+                       }
+                       return this;
+               }
+       }
+       /**
+        * ドラッグ対象トランスミッタを表すクラス
+        */
+       private static class DraggingTransmitter implements Transferable {
+               private static final List<DataFlavor> flavors = Arrays.asList(MidiDeviceDialog.transmitterFlavor);
+               private Transmitter tx;
+               @Override
+               public Object getTransferData(DataFlavor flavor) {
+                       return flavor.getRepresentationClass().isInstance(tx) ? tx : null;
+               }
+               @Override
+               public DataFlavor[] getTransferDataFlavors() { return (DataFlavor[]) flavors.toArray(); }
+               @Override
+               public boolean isDataFlavorSupported(DataFlavor flavor) { return flavors.contains(flavor); }
+       };
+       private static DraggingTransmitter draggingTransmitter = new DraggingTransmitter();
+
+       /**
+        * 現在ドラッグされている{@link Transmitter}を返します。
+        * ドラッグ中でなければnullを返します。
+        */
+       public Transmitter getDraggingTransmitter() { return draggingTransmitter.tx; }
+
+       private MidiCablePane cablePane;
+
+       /**
+        * 仮想MIDI端子リストビューを生成します。
+        * @param model このビューから参照されるデータモデル
+        * @param cablePane MIDIケーブル描画面
+        */
+       public MidiTransmitterListView(MidiDeviceModel.TransmitterListModel model, MidiCablePane cablePane) {
+               super(model);
+               this.cablePane = cablePane;
+               setCellRenderer(new CellRenderer());
+               setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+               setLayoutOrientation(JList.HORIZONTAL_WRAP);
+               setVisibleRowCount(0);
+               //
+               // トランスミッタのドラッグを受け付ける
+               DragSource dragSource = new DragSource();
+               DragGestureListener dgl = new DragGestureListener() {
+                       @Override
+                       public void dragGestureRecognized(DragGestureEvent event) {
+                               if( (event.getDragAction() & DnDConstants.ACTION_COPY_OR_MOVE) == 0 ) return;
+                               draggingTransmitter.tx = getModel().getElementAt(locationToIndex(event.getDragOrigin()));
+                               if( draggingTransmitter.tx instanceof NewTransmitter ) {
+                                       draggingTransmitter.tx = getModel().openTransmitter();
+                               }
+                               event.startDrag(DragSource.DefaultLinkDrop, draggingTransmitter, new DragSourceAdapter() {
+                                       @Override
+                                       public void dragDropEnd(DragSourceDropEvent event) {
+                                               if( ! event.getDropSuccess() ) getModel().closeTransmitter((Transmitter)draggingTransmitter.tx);
+                                               draggingTransmitter.tx = null;
+                                               MidiTransmitterListView.this.cablePane.dragDropEnd();
+                                       }
+                               });
+                       }
+               };
+               dragSource.createDefaultDragGestureRecognizer(this, DnDConstants.ACTION_COPY_OR_MOVE, dgl);
+               dragSource.addDragSourceMotionListener(cablePane);
+               //
+               // レシーバのドロップを受け付ける
+               DropTargetListener dtl = new DropTargetAdapter() {
+                       @Override
+                       public void dragEnter(DropTargetDragEvent event) {
+                               if( event.isDataFlavorSupported(MidiDeviceDialog.receiverFlavor) ) {
+                                       event.acceptDrag(DnDConstants.ACTION_COPY_OR_MOVE);
+                               }
+                       }
+                       @Override
+                       public void drop(DropTargetDropEvent event) {
+                               event.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
+                               try {
+                                       int maskedBits = event.getDropAction() & DnDConstants.ACTION_COPY_OR_MOVE;
+                                       if( maskedBits == 0 ) {
+                                               event.dropComplete(false);
+                                               return;
+                                       }
+                                       Transferable t = event.getTransferable();
+                                       if( ! t.isDataFlavorSupported(MidiDeviceDialog.receiverFlavor) ) {
+                                               event.dropComplete(false);
+                                               return;
+                                       }
+                                       Object source = t.getTransferData(MidiDeviceDialog.receiverFlavor);
+                                       if( ! (source instanceof Receiver) ) {
+                                               event.dropComplete(false);
+                                               return;
+                                       }
+                                       Transmitter destTx = getModel().getElementAt(locationToIndex(event.getLocation()));
+                                       if( destTx instanceof NewTransmitter ) destTx = getModel().openTransmitter();
+                                       destTx.setReceiver((Receiver)source);
+                                       event.dropComplete(true);
+                                       return;
+                               }
+                               catch (Exception ex) {
+                                       ex.printStackTrace();
+                               }
+                               event.dropComplete(false);
+                       }
+               };
+               new DropTarget( this, DnDConstants.ACTION_COPY_OR_MOVE, dtl, true );
+       }
+       @Override
+       public MidiDeviceModel.TransmitterListModel getModel() {
+               return (MidiDeviceModel.TransmitterListModel) super.getModel();
+       }
+       /**
+        * 引数で指定された{@link Transmitter}のセル範囲を示す、
+        * リストの座標系内の境界の矩形を返します。対応するセルがない場合はnullを返します。
+        * @return セル範囲を示す境界の矩形、またはnull
+        */
+       public Rectangle getCellBounds(Transmitter tx) {
+               int index = getModel().indexOf(tx);
+               return getCellBounds(index,index);
+       }
+}