OSDN Git Service

MIDI接続ドラッグ&ドロップの改良
authorAkiyoshi Kamide <kamide@yk.rim.or.jp>
Wed, 4 May 2016 15:13:10 +0000 (00:13 +0900)
committerAkiyoshi Kamide <kamide@yk.rim.or.jp>
Wed, 4 May 2016 15:13:10 +0000 (00:13 +0900)
・ドラッグ&ドロップ中からケーブルを表示するようにした
・Rx以外にドロップしても接続されないようにした
・リファクタリング

src/camidion/chordhelper/ChordHelperApplet.java
src/camidion/chordhelper/mididevice/MidiCablePane.java
src/camidion/chordhelper/mididevice/MidiConnecterListModel.java
src/camidion/chordhelper/mididevice/MidiConnecterListView.java
src/camidion/chordhelper/mididevice/MidiDesktopPane.java [deleted file]
src/camidion/chordhelper/mididevice/MidiDeviceDialog.java
src/camidion/chordhelper/mididevice/MidiDeviceFrame.java
src/camidion/chordhelper/mididevice/MidiDeviceModelList.java
src/camidion/chordhelper/mididevice/MidiDeviceTree.java [deleted file]
src/camidion/chordhelper/mididevice/MidiDeviceTreeView.java [new file with mode: 0644]
src/camidion/chordhelper/mididevice/MidiOpenedDevicesView.java [new file with mode: 0644]

index 7c565ae..b602c1b 100644 (file)
@@ -284,7 +284,7 @@ public class ChordHelperApplet extends JApplet {
         */
        public static class VersionInfo {
                public static final String      NAME = "MIDI Chord Helper";
-               public static final String      VERSION = "Ver.20160502.1";
+               public static final String      VERSION = "Ver.20160504.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/";
index 53582f7..bec5fa4 100644 (file)
@@ -4,8 +4,12 @@ import java.awt.BasicStroke;
 import java.awt.Color;
 import java.awt.Graphics;
 import java.awt.Graphics2D;
+import java.awt.Point;
 import java.awt.Rectangle;
 import java.awt.Stroke;
+import java.awt.dnd.DragSourceDragEvent;
+import java.awt.dnd.DragSourceMotionListener;
+import java.awt.event.ComponentAdapter;
 import java.awt.event.ComponentEvent;
 import java.awt.event.ComponentListener;
 import java.util.Hashtable;
@@ -17,6 +21,7 @@ import javax.swing.JComponent;
 import javax.swing.JDesktopPane;
 import javax.swing.JInternalFrame;
 import javax.swing.JLayeredPane;
+import javax.swing.event.InternalFrameAdapter;
 import javax.swing.event.InternalFrameEvent;
 import javax.swing.event.InternalFrameListener;
 import javax.swing.event.ListDataEvent;
@@ -25,51 +30,74 @@ import javax.swing.event.ListDataListener;
 /**
  * MIDI ケーブル描画面
  */
-public class MidiCablePane extends JComponent
-       implements ListDataListener, ComponentListener, InternalFrameListener
-{
+public class MidiCablePane extends JComponent {
+       private Point draggingPoint;
+       /**
+        * {@link MidiConnecterListModel} の {@link Transmitter}
+        * をドラッグしている最中に再描画するためのリスナー
+        */
+       public final DragSourceMotionListener midiConnecterMotionListener = new DragSourceMotionListener() {
+               @Override
+               public void dragMouseMoved(DragSourceDragEvent dsde) {
+                       Point origin = MidiCablePane.this.getLocationOnScreen();
+                       draggingPoint = dsde.getLocation();
+                       draggingPoint.translate(-origin.x, -origin.y);
+                       repaint();
+               }
+       };
+       /**
+        * ドラッグ&ドロップの終了時にこのメソッドを呼び出します。
+        */
+       public void dragDropEnd() { draggingPoint = null; repaint(); }
+       /**
+        * {@link MidiDeviceFrame} の移動や変形を監視して再描画するためのリスナー
+        */
+       public final ComponentListener midiDeviceFrameComponentListener = new ComponentAdapter() {
+               @Override
+               public void componentMoved(ComponentEvent e) { repaint(); }
+               @Override
+               public void componentResized(ComponentEvent e) { repaint(); }
+       };
+       /**
+        * {@link MidiDeviceFrame} が閉じたタイミングで再描画するためのリスナー
+        */
+       public final InternalFrameListener midiDeviceFrameListener = new InternalFrameAdapter() {
+               @Override
+               public void internalFrameClosed(InternalFrameEvent e) { repaint(); }
+               @Override
+               public void internalFrameDeactivated(InternalFrameEvent e) { repaint(); }
+               @Override
+               public void internalFrameClosing(InternalFrameEvent e) {
+                       JInternalFrame frame = e.getInternalFrame();
+                       if( ! (frame instanceof MidiDeviceFrame) ) return;
+                       MidiConnecterListModel devModel = ((MidiDeviceFrame)frame).listView.getModel();
+                       if( ! devModel.rxSupported() ) return;
+                       colorMap.remove(devModel.getMidiDevice().getReceivers().get(0));
+                       repaint();
+               }
+       };
+       /**
+        * {@link MidiConnecterListModel} における {@link Transmitter}
+        * の増減や状態変更があった場合に再描画するためのリスナー
+        */
+       public final ListDataListener midiConnecterListDataListener = new ListDataListener() {
+               @Override
+               public void contentsChanged(ListDataEvent e) { repaint(); }
+               @Override
+               public void intervalAdded(ListDataEvent e) { repaint(); }
+               @Override
+               public void intervalRemoved(ListDataEvent e) { repaint(); }
+       };
+
        private JDesktopPane desktopPane;
-       //private JTree tree;
        public MidiCablePane(JDesktopPane desktopPane) {
                this.desktopPane = desktopPane;
                setOpaque(false);
                setVisible(true);
        }
-       //
-       // MidiDeviceFrame の開閉を検出
-       public void internalFrameActivated(InternalFrameEvent e) {}
-       public void internalFrameClosed(InternalFrameEvent e) { repaint(); }
-       public void internalFrameClosing(InternalFrameEvent e) {
-               JInternalFrame frame = e.getInternalFrame();
-               if( ! (frame instanceof MidiDeviceFrame) )
-                       return;
-               MidiDeviceFrame devFrame = (MidiDeviceFrame)frame;
-               MidiConnecterListModel devModel = devFrame.listView.getModel();
-               if( ! devModel.rxSupported() )
-                       return;
-               colorMap.remove(devModel.getMidiDevice().getReceivers().get(0));
-               repaint();
-       }
-       public void internalFrameDeactivated(InternalFrameEvent e) { repaint(); }
-       public void internalFrameDeiconified(InternalFrameEvent e) {}
-       public void internalFrameIconified(InternalFrameEvent e) {}
-       public void internalFrameOpened(InternalFrameEvent e) {}
-       //
-       // ウィンドウオペレーションの検出
-       public void componentHidden(ComponentEvent e) {}
-       public void componentMoved(ComponentEvent e) { repaint(); }
-       public void componentResized(ComponentEvent e) { repaint(); }
-       public void componentShown(ComponentEvent e) {}
-       //
-       // MidiConnecterListModel における Transmitter リストの更新を検出
-       public void contentsChanged(ListDataEvent e) { repaint(); }
-       public void intervalAdded(ListDataEvent e) { repaint(); }
-       public void intervalRemoved(ListDataEvent e) { repaint(); }
-       //
-       // ケーブル描画用
+
        private static final Stroke CABLE_STROKE = new BasicStroke(
-               3, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND
-       );
+                       3, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);
        private static final Color[] CABLE_COLORS = {
                new Color(255, 0, 0,144),
                new Color(0, 255, 0,144),
@@ -85,67 +113,66 @@ public class MidiCablePane extends JComponent
                super.paint(g);
                Graphics2D g2 = (Graphics2D)g;
                g2.setStroke(CABLE_STROKE);
-               JInternalFrame[] frames =
-                       desktopPane.getAllFramesInLayer(JLayeredPane.DEFAULT_LAYER);
+               JInternalFrame[] frames = desktopPane.getAllFramesInLayer(JLayeredPane.DEFAULT_LAYER);
                for( JInternalFrame frame : frames ) {
-                       if( ! (frame instanceof MidiDeviceFrame) )
-                               continue;
+                       if( ! (frame instanceof MidiDeviceFrame) ) continue;
                        MidiDeviceFrame txDeviceFrame = (MidiDeviceFrame)frame;
-                       List<Transmitter> txList = txDeviceFrame.listView.getModel().getMidiDevice().getTransmitters();
+                       MidiConnecterListView txView = txDeviceFrame.listView;
+                       Transmitter draggingTx = txView.getDraggingTransmitter();
+                       List<Transmitter> txList = txView.getModel().getMidiDevice().getTransmitters();
                        for( Transmitter tx : txList ) {
                                //
-                               // 送信端子から接続されている受信端子の存在を確認
-                               Receiver rx = tx.getReceiver();
-                               if( rx == null )
-                                       continue;
+                               // 送信端子の場所を特定
+                               Rectangle txRect = txView.getCellBounds(tx);
+                               if( txRect == null ) continue;
+                               txRect.translate(
+                                       txDeviceFrame.getRootPane().getX() +
+                                       txDeviceFrame.getContentPane().getX() + txDeviceFrame.getX(),
+                                       txDeviceFrame.getRootPane().getY() +
+                                       txDeviceFrame.getContentPane().getY() + txDeviceFrame.getY()
+                               );
+                               int d = txRect.height - 5;
+                               int r = d / 2;
+                               int fromX = txRect.x + r + 4;
+                               int fromY = txRect.y + r + 4;
                                //
-                               // 送信端子の矩形を特定
-                               Rectangle txRect = txDeviceFrame.getListCellBounds(tx);
-                               if( txRect == null )
+                               // ドラッグ中であれば、マウスカーソルのある所まで線を引く
+                               if( tx.equals(draggingTx) && draggingPoint != null ) {
+                                       g2.setColor(Color.BLACK);
+                                       g2.drawLine(fromX, fromY, draggingPoint.x, draggingPoint.y);
                                        continue;
-                               //
-                               // 受信端子のあるMIDIデバイスを探す
+                               }
+                               // 受信端子の場所を特定
+                               Receiver rx = tx.getReceiver();
+                               if( rx == null ) continue;
                                Rectangle rxRect = null;
                                for( JInternalFrame anotherFrame : frames ) {
-                                       if( ! (anotherFrame instanceof MidiDeviceFrame) )
-                                               continue;
-                                       //
-                                       // 受信端子の矩形を探す
+                                       if( ! (anotherFrame instanceof MidiDeviceFrame) ) continue;
                                        MidiDeviceFrame rxDeviceFrame = (MidiDeviceFrame)anotherFrame;
-                                       if((rxRect = rxDeviceFrame.getListCellBounds(rx)) == null)
-                                               continue;
-                                       rxRect.translate(rxDeviceFrame.getX(), rxDeviceFrame.getY());
+                                       if( (rxRect = rxDeviceFrame.listView.getCellBounds(rx)) == null ) continue;
+                                       rxRect.translate(
+                                               rxDeviceFrame.getRootPane().getX() +
+                                               rxDeviceFrame.getContentPane().getX() + rxDeviceFrame.getX(),
+                                               rxDeviceFrame.getRootPane().getY() +
+                                               rxDeviceFrame.getContentPane().getY() + rxDeviceFrame.getY()
+                                       );
                                        break;
                                }
-                               if( rxRect == null )
-                                       continue;
-                               txRect.translate(txDeviceFrame.getX(), txDeviceFrame.getY());
+                               if( rxRect == null ) continue;
                                //
-                               // 色を探す
+                               // 受信端子まで線を引く
                                Color color = colorMap.get(rx);
                                if( color == null ) {
                                        colorMap.put(rx, color=CABLE_COLORS[nextColorIndex++]);
-                                       if( nextColorIndex >= CABLE_COLORS.length )
-                                               nextColorIndex = 0;
+                                       if( nextColorIndex >= CABLE_COLORS.length ) nextColorIndex = 0;
                                }
                                g2.setColor(color);
-                               //
-                               // Tx 始点
-                               int fromX = txRect.x;
-                               int fromY = txRect.y + 2;
-                               int d = txRect.height - 5;
-                               g2.fillOval(fromX, fromY, d, d);
-                               //
-                               // Tx → Rx 線
-                               int r = d / 2;
-                               fromX += r;
-                               fromY += r;
                                d = rxRect.height - 5;
                                r = d / 2;
-                               int toX = rxRect.x + r;
-                               int toY = rxRect.y + r + 2;
+                               int toX = rxRect.x + r + 4;
+                               int toY = rxRect.y + r + 4;
                                g2.drawLine(fromX, fromY, toX, toY);
                        }
                }
        }
-}
\ No newline at end of file
+}
index 8b9c074..97c9a27 100644 (file)
@@ -29,11 +29,10 @@ public class MidiConnecterListModel extends AbstractListModel<AutoCloseable> {
                public void close() { }
        };
        /**
-        * 指定のMIDIデバイスに属する
-        *  {@link Transmitter}/{@link Receiver} のリストモデルを構築します。
+        * 指定のMIDIデバイスに属する {@link Transmitter}/{@link Receiver} のリストモデルを構築します。
         *
         * @param device 対象MIDIデバイス
-        * @param modelList ã\83ªã\82¹ã\83\88ã\83¢ã\83\87ã\83«ã\81®リスト
+        * @param modelList ã\83ªã\82¹ã\83\88ã\83¢ã\83\87ã\83«ã\82\92æ ¼ç´\8dã\81\97ã\81¦ã\81\84ã\82\8b親リスト
         */
        public MidiConnecterListModel(MidiDevice device, List<MidiConnecterListModel> modelList) {
                this.device = device;
index 9922bdf..d5165ab 100644 (file)
@@ -1,21 +1,21 @@
 package camidion.chordhelper.mididevice;
 
 import java.awt.Component;
+import java.awt.Point;
+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.DragSourceDragEvent;
+import java.awt.dnd.DragSourceAdapter;
 import java.awt.dnd.DragSourceDropEvent;
-import java.awt.dnd.DragSourceEvent;
 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.DropTargetEvent;
-import java.awt.dnd.DropTargetListener;
 
 import javax.sound.midi.Receiver;
 import javax.sound.midi.Transmitter;
@@ -34,23 +34,20 @@ import camidion.chordhelper.ButtonIcon;
  * 仮想MIDI端子リストです。
  * </p>
  */
-public class MidiConnecterListView extends JList<AutoCloseable>
-       implements DragGestureListener, DragSourceListener, Transferable, DropTargetListener
-{
-       public static final Icon MIDI_CONNECTER_ICON =
-               new ButtonIcon(ButtonIcon.MIDI_CONNECTOR_ICON);
-       private class CellRenderer extends JLabel implements ListCellRenderer<AutoCloseable> {
+public class MidiConnecterListView 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
-               ) {
+                               JList<? extends AutoCloseable> list, AutoCloseable value, int index,
+                               boolean isSelected, boolean cellHasFocus)
+               {
                        String text;
-                       if( value instanceof Receiver ) text = "Rx";
+                       if( value == null ) text = null;
+                       else if( value instanceof Receiver ) text = "Rx";
                        else if( value instanceof Transmitter ) text = "Tx";
-                       else if( value == null ) text = null;
                        else text = value.toString();
                        setText(text);
                        setIcon(MIDI_CONNECTER_ICON);
@@ -67,111 +64,124 @@ public class MidiConnecterListView extends JList<AutoCloseable>
                        return this;
                }
        }
+
+       private static final DataFlavor transmitterFlavor = new DataFlavor(Transmitter.class, "Transmitter");
+       /**
+        * ドラッグ対象を表すクラス
+        */
+       private static class DraggingObject implements Transferable {
+               private static final DataFlavor flavors[] = {transmitterFlavor};
+               private Transmitter tx;
+               @Override
+               public Object getTransferData(DataFlavor flavor) { return tx; }
+               @Override
+               public DataFlavor[] getTransferDataFlavors() { return flavors; }
+               @Override
+               public boolean isDataFlavorSupported(DataFlavor flavor) {
+                       return flavor.equals(transmitterFlavor);
+               }
+       };
+       private static DraggingObject draggingObject = new DraggingObject();
+
+       /**
+        * 現在ドラッグされているトランスミッタを返します。
+        * @return 現在ドラッグされているトランスミッタ(ドラッグ中でなければnull)
+        */
+       public Transmitter getDraggingTransmitter() { return draggingObject.tx; }
+
+       private MidiCablePane cablePane;
+       private DragSourceListener dragSourceListener = new DragSourceAdapter() {
+               @Override
+               public void dragDropEnd(DragSourceDropEvent dsde) {
+                       if( ! dsde.getDropSuccess() ) getModel().closeTransmitter(getDraggingTransmitter());
+                       draggingObject.tx = null;
+                       cablePane.dragDropEnd();
+               }
+       };
+
        /**
         * 仮想MIDI端子リストビューを生成します。
         * @param model このビューから参照されるデータモデル
+        * @param cablePane MIDIケーブル描画面
         */
-       public MidiConnecterListView(MidiConnecterListModel model) {
+       public MidiConnecterListView(MidiConnecterListModel model, MidiCablePane cablePane) {
                super(model);
+               this.cablePane = cablePane;
                setCellRenderer(new CellRenderer());
                setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
                setLayoutOrientation(JList.HORIZONTAL_WRAP);
                setVisibleRowCount(0);
-        (new DragSource()).createDefaultDragGestureRecognizer(
-               this, DnDConstants.ACTION_COPY_OR_MOVE, this
-        );
-               new DropTarget( this, DnDConstants.ACTION_COPY_OR_MOVE, this, true );
-       }
-
-       @Override
-       public void dragGestureRecognized(DragGestureEvent dge) {
-               if( (dge.getDragAction() & DnDConstants.ACTION_COPY_OR_MOVE) == 0 ) return;
-               AutoCloseable e = getModel().getElementAt(locationToIndex(dge.getDragOrigin()));
-               if( e instanceof Transmitter ) {
-                       if( e instanceof MidiConnecterListModel.NewTransmitter ) {
-                               transferringTx = getModel().getTransmitter();
+               DragSource dragSource = new DragSource();
+               dragSource.createDefaultDragGestureRecognizer(this,
+                       DnDConstants.ACTION_COPY_OR_MOVE,
+                       new DragGestureListener() {
+                               @Override
+                               public void dragGestureRecognized(DragGestureEvent dge) {
+                                       if( (dge.getDragAction() & DnDConstants.ACTION_COPY_OR_MOVE) == 0 ) return;
+                                       Point dragStartPoint = dge.getDragOrigin();
+                                       AutoCloseable transceiver = getModel().getElementAt(locationToIndex(dragStartPoint));
+                                       if( transceiver instanceof Transmitter ) {
+                                               if( transceiver instanceof MidiConnecterListModel.NewTransmitter ) {
+                                                       draggingObject.tx = getModel().getTransmitter();
+                                               }
+                                               else {
+                                                       draggingObject.tx = (Transmitter)transceiver;
+                                               }
+                                               dge.startDrag(DragSource.DefaultLinkDrop, draggingObject, dragSourceListener);
+                                       }
+                               }
                        }
-                       else {
-                               transferringTx = (Transmitter)e;
+               );
+               dragSource.addDragSourceMotionListener(cablePane.midiConnecterMotionListener);
+               DropTargetAdapter dta= new DropTargetAdapter() {
+                       @Override
+                       public void dragEnter(DropTargetDragEvent event) {
+                               if( event.isDataFlavorSupported(transmitterFlavor) )
+                                       event.acceptDrag(DnDConstants.ACTION_COPY_OR_MOVE);
                        }
-                       dge.startDrag(DragSource.DefaultLinkDrop, this, this);
-               }
-       }
-
-       @Override
-       public void dragEnter(DragSourceDragEvent dsde) {}
-       @Override
-       public void dragOver(DragSourceDragEvent dsde) {}
-       @Override
-       public void dropActionChanged(DragSourceDragEvent dsde) {}
-       @Override
-       public void dragExit(DragSourceEvent dse) {}
-       @Override
-       public void dragDropEnd(DragSourceDropEvent dsde) {
-               if( ! dsde.getDropSuccess() ) getModel().closeTransmitter(transferringTx);
-               transferringTx = null;
-       }
-
-       private Transmitter transferringTx = null;
-       private static final DataFlavor transmitterFlavor =
-               new DataFlavor(Transmitter.class, "Transmitter");
-       private static final DataFlavor transmitterFlavors[] = {transmitterFlavor};
-       @Override
-       public Object getTransferData(DataFlavor flavor) { return transferringTx; }
-       @Override
-       public DataFlavor[] getTransferDataFlavors() { return transmitterFlavors; }
-       @Override
-       public boolean isDataFlavorSupported(DataFlavor flavor) {
-               return flavor.equals(transmitterFlavor);
-       }
-
-       @Override
-       public void dragEnter(DropTargetDragEvent event) {
-               if( event.isDataFlavorSupported(transmitterFlavor) )
-                       event.acceptDrag(DnDConstants.ACTION_COPY_OR_MOVE);
-       }
-       @Override
-       public void dragExit(DropTargetEvent dte) {}
-       @Override
-       public void dragOver(DropTargetDragEvent dtde) {}
-       @Override
-       public void dropActionChanged(DropTargetDragEvent dtde) {}
-       @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 || ! getModel().rxSupported() ) {
-                               event.dropComplete(false);
-                               return;
+                       @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 || ! getModel().rxSupported() ) {
+                                               event.dropComplete(false);
+                                               return;
+                                       }
+                                       int index = locationToIndex(event.getLocation());
+                                       AutoCloseable destination = getModel().getElementAt(index);
+                                       if( ! (destination instanceof Receiver) ) {
+                                               event.dropComplete(false);
+                                               return;
+                                       }
+                                       Transferable draggedObject = event.getTransferable();
+                                       Transmitter sourceTx = (Transmitter)draggedObject.getTransferData(transmitterFlavor);
+                                       if( getModel().ConnectToReceiver(sourceTx, (Receiver)destination) == null ) {
+                                               event.dropComplete(false);
+                                               return;
+                                       }
+                                       event.dropComplete(true);
+                               }
+                               catch (Exception ex) {
+                                       ex.printStackTrace();
+                                       event.dropComplete(false);
+                               }
                        }
-                       AutoCloseable destination = getModel().getElementAt(locationToIndex(event.getLocation()));
-                       if( ! (destination instanceof Receiver) ) {
-                               event.dropComplete(false);
-                               return;
-                       }
-                       Transmitter sourceTx = (Transmitter)event.getTransferable().getTransferData(transmitterFlavor);
-                       if( getModel().ConnectToReceiver(sourceTx, (Receiver)destination) == null ) {
-                               event.dropComplete(false);
-                               return;
-                       }
-                       event.dropComplete(true);
-               }
-               catch (Exception ex) {
-                       ex.printStackTrace();
-                       event.dropComplete(false);
-               }
+               };
+               new DropTarget( this, DnDConstants.ACTION_COPY_OR_MOVE, dta, true );
        }
        @Override
-       public MidiConnecterListModel getModel() {
-               return (MidiConnecterListModel)super.getModel();
-       }
+       public MidiConnecterListModel getModel() { return (MidiConnecterListModel)super.getModel(); }
        /**
-        * 選択項目がトランスミッタの実体であれば、それを閉じます。
+        * 指定されたMIDI端子(Transmitter または Receiver)が仮想MIDI端子リスト上にあれば、
+        * そのセル範囲の矩形を返します。
+        *
+        * @param transceiver MIDI端子
+        * @return セル範囲の矩形(ない場合はnull)
         */
-       public void closeSelectedTransmitter() {
-               AutoCloseable ac = getSelectedValue();
-               if( ac instanceof Transmitter )
-                       getModel().closeTransmitter((Transmitter)ac);
+       public Rectangle getCellBounds(AutoCloseable transceiver) {
+               int index = getModel().indexOf(transceiver);
+               Rectangle rect = getCellBounds(index,index);
+               return rect == null ? null : rect;
        }
-}
\ No newline at end of file
+}
diff --git a/src/camidion/chordhelper/mididevice/MidiDesktopPane.java b/src/camidion/chordhelper/mididevice/MidiDesktopPane.java
deleted file mode 100644 (file)
index 8fa0059..0000000
+++ /dev/null
@@ -1,158 +0,0 @@
-package camidion.chordhelper.mididevice;
-
-import java.awt.Point;
-import java.awt.datatransfer.Transferable;
-import java.awt.dnd.DnDConstants;
-import java.awt.dnd.DropTarget;
-import java.awt.dnd.DropTargetDragEvent;
-import java.awt.dnd.DropTargetDropEvent;
-import java.awt.dnd.DropTargetEvent;
-import java.awt.dnd.DropTargetListener;
-import java.awt.event.ComponentAdapter;
-import java.awt.event.ComponentEvent;
-
-import javax.sound.midi.MidiUnavailableException;
-import javax.swing.JDesktopPane;
-import javax.swing.JInternalFrame;
-import javax.swing.JLayeredPane;
-import javax.swing.JOptionPane;
-import javax.swing.Timer;
-
-/**
- * 開いている MIDI デバイスを置くためのデスクトップビュー
- */
-public class MidiDesktopPane extends JDesktopPane implements DropTargetListener {
-       private MidiCablePane cablePane = new MidiCablePane(this);
-       public MidiDesktopPane(MidiDeviceTree deviceTree) {
-               add(cablePane, JLayeredPane.PALETTE_LAYER);
-               int i=0;
-               MidiDeviceTreeModel treeModel = (MidiDeviceTreeModel)deviceTree.getModel();
-               for( MidiConnecterListModel deviceModel : treeModel.deviceModelList ) {
-                       MidiDeviceFrame frame = new MidiDeviceFrame(deviceModel) {
-                               {
-                                       addInternalFrameListener(cablePane);
-                                       addComponentListener(cablePane);
-                               }
-                       };
-                       frame.addInternalFrameListener(deviceTree);
-                       deviceModel.addListDataListener(cablePane);
-                       add(frame);
-                       if( deviceModel.getMidiDevice().isOpen() ) {
-                               frame.setBounds( 10+(i%2)*260, 10+i*55, 250, 100 );
-                               frame.setVisible(true);
-                               i++;
-                       }
-               }
-               addComponentListener(new ComponentAdapter() {
-                       @Override
-                       public void componentResized(ComponentEvent e) {
-                               cablePane.setSize(getSize());
-                       }
-               });
-               new DropTarget( this, DnDConstants.ACTION_COPY_OR_MOVE, this, true );
-       }
-       @Override
-       public void dragEnter(DropTargetDragEvent dtde) {
-               Transferable trans = dtde.getTransferable();
-               if( trans.isDataFlavorSupported(MidiDeviceTree.TREE_MODEL_FLAVOR) ) {
-                       dtde.acceptDrag(DnDConstants.ACTION_COPY_OR_MOVE);
-               }
-       }
-       @Override
-       public void dragExit(DropTargetEvent dte) {}
-       @Override
-       public void dragOver(DropTargetDragEvent dtde) {}
-       @Override
-       public void dropActionChanged(DropTargetDragEvent dtde) {}
-       @Override
-       public void drop(DropTargetDropEvent dtde) {
-               dtde.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
-               try {
-                       int action = dtde.getDropAction() ;
-                       if( (action & DnDConstants.ACTION_COPY_OR_MOVE) != 0 ) {
-                               Transferable trans = dtde.getTransferable();
-                               Object data = trans.getTransferData(MidiDeviceTree.TREE_MODEL_FLAVOR);
-                               if( data instanceof MidiConnecterListModel ) {
-                                       MidiConnecterListModel deviceModel = (MidiConnecterListModel)data;
-                                       try {
-                                               deviceModel.openDevice();
-                                       } catch( MidiUnavailableException e ) {
-                                               //
-                                               // デバイスを開くのに失敗した場合
-                                               //
-                                               //   例えば、「Microsort MIDI マッパー」と
-                                               //   「Microsoft GS Wavetable SW Synth」を
-                                               //   同時に開こうとするとここに来る。
-                                               //
-                                               dtde.dropComplete(false);
-                                               String message = "MIDIデバイス "
-                                                               + deviceModel
-                                                               +" を開けません。\n"
-                                                               + "すでに開かれているデバイスが"
-                                                               + "このデバイスを連動して開いていないか確認してください。\n\n"
-                                                               + e.getMessage();
-                                               JOptionPane.showMessageDialog(
-                                                       null, message,
-                                                       "Cannot open MIDI device",
-                                                       JOptionPane.ERROR_MESSAGE
-                                               );
-                                               return;
-                                       }
-                                       if( deviceModel.getMidiDevice().isOpen() ) {
-                                               dtde.dropComplete(true);
-                                               //
-                                               // デバイスが正常に開かれたことを確認できたら
-                                               // ドロップした場所へフレームを配置して可視化する。
-                                               //
-                                               JInternalFrame frame = getFrameOf(deviceModel);
-                                               if( frame != null ) {
-                                                       Point loc = dtde.getLocation();
-                                                       loc.translate( -frame.getWidth()/2, 0 );
-                                                       frame.setLocation(loc);
-                                                       frame.setVisible(true);
-                                               }
-                                               return;
-                                       }
-                               }
-                       }
-               }
-               catch (Exception ex) {
-                       ex.printStackTrace();
-               }
-               dtde.dropComplete(false);
-       }
-       /**
-        * 指定されたMIDIデバイスモデルに対するMIDIデバイスフレームを返します。
-        *
-        * @param deviceModel MIDIデバイスモデル
-        * @return 対応するMIDIデバイスフレーム(ない場合 null)
-        */
-       public MidiDeviceFrame getFrameOf(MidiConnecterListModel deviceModel) {
-               JInternalFrame[] frames = getAllFramesInLayer(JLayeredPane.DEFAULT_LAYER);
-               for( JInternalFrame frame : frames ) {
-                       if( ! (frame instanceof MidiDeviceFrame) )
-                               continue;
-                       MidiDeviceFrame deviceFrame = (MidiDeviceFrame)frame;
-                       if( deviceFrame.listView.getModel() == deviceModel )
-                               return deviceFrame;
-               }
-               return null;
-       }
-       private boolean isTimerStarted;
-       /**
-        * タイムスタンプを更新するタイマーを開始または停止します。
-        * @param toStart trueで開始、falseで停止
-        */
-       public void setAllDeviceTimestampTimers(boolean toStart) {
-               if( isTimerStarted == toStart ) return;
-               isTimerStarted = toStart;
-               JInternalFrame[] frames = getAllFramesInLayer(JLayeredPane.DEFAULT_LAYER);
-               for( JInternalFrame frame : frames ) {
-                       if( ! (frame instanceof MidiDeviceFrame) )
-                               continue;
-                       MidiDeviceFrame deviceFrame = (MidiDeviceFrame)frame;
-                       Timer timer = deviceFrame.timer;
-                       if( toStart ) timer.start(); else timer.stop();
-               }
-       }
-}
\ No newline at end of file
index 1d1c5ba..112ea1d 100644 (file)
@@ -18,23 +18,56 @@ import javax.swing.event.TreeSelectionListener;
 /**
  * MIDIデバイスダイアログ (View)
  */
-public class MidiDeviceDialog extends JDialog
-       implements ActionListener, TreeSelectionListener
+public class MidiDeviceDialog extends JDialog implements ActionListener
 {
+       private MidiDeviceTreeView deviceTree;
        private JEditorPane deviceInfoPane;
-       private MidiDesktopPane desktopPane;
-       private MidiDeviceTree deviceTree;
+       private MidiOpenedDevicesView desktopPane;
+       @Override
+       public void actionPerformed(ActionEvent event) { setVisible(true); }
+
        public MidiDeviceDialog(List<MidiConnecterListModel> deviceModelList) {
                setTitle("MIDI device connection");
                setBounds( 300, 300, 800, 500 );
-               deviceTree = new MidiDeviceTree(new MidiDeviceTreeModel(deviceModelList));
-               deviceTree.addTreeSelectionListener(this);
-               deviceInfoPane = new JEditorPane("text/html","<html></html>") {
-                       {
-                               setEditable(false);
-                       }
-               };
-               desktopPane = new MidiDesktopPane(deviceTree);
+               deviceTree = new MidiDeviceTreeView(new MidiDeviceTreeModel(deviceModelList)) {{
+                       addTreeSelectionListener(new TreeSelectionListener() {
+                               @Override
+                               public void valueChanged(TreeSelectionEvent e) {
+                                       Object lastSelected = deviceTree.getLastSelectedPathComponent();
+                                       String html = "<html><head></head><body>";
+                                       if( lastSelected instanceof MidiConnecterListModel ) {
+                                               MidiConnecterListModel deviceModel = (MidiConnecterListModel)lastSelected;
+                                               MidiDevice.Info info = deviceModel.getMidiDevice().getDeviceInfo();
+                                               html += "<b>"+deviceModel+"</b><br/>"
+                                                       + "<table border=\"1\"><tbody>"
+                                                       + "<tr><th>Version</th><td>"+info.getVersion()+"</td></tr>"
+                                                       + "<tr><th>Description</th><td>"+info.getDescription()+"</td></tr>"
+                                                       + "<tr><th>Vendor</th><td>"+info.getVendor()+"</td></tr>"
+                                                       + "</tbody></table>";
+                                               MidiDeviceFrame deviceFrame = desktopPane.getMidiDeviceFrameOf(deviceModel);
+                                               if( deviceFrame != null ) {
+                                                       try {
+                                                               deviceFrame.setSelected(true);
+                                                       } catch( PropertyVetoException ex ) {
+                                                               ex.printStackTrace();
+                                                       }
+                                               }
+                                       }
+                                       else if( lastSelected instanceof MidiDeviceInOutType ) {
+                                               MidiDeviceInOutType ioType = (MidiDeviceInOutType)lastSelected;
+                                               html += "<b>"+ioType+"</b><br/>";
+                                               html += ioType.getDescription()+"<br/>";
+                                       }
+                                       else if( lastSelected != null ) {
+                                               html += lastSelected.toString();
+                                       }
+                                       html += "</body></html>";
+                                       deviceInfoPane.setText(html);
+                               }
+                       });
+               }};
+               deviceInfoPane = new JEditorPane("text/html","<html></html>") {{ setEditable(false); }};
+               desktopPane = new MidiOpenedDevicesView(deviceTree);
                add(new JSplitPane(
                        JSplitPane.HORIZONTAL_SPLIT,
                        new JSplitPane(
@@ -50,51 +83,11 @@ public class MidiDeviceDialog extends JDialog
                        setDividerLocation(250);
                }});
                addWindowListener(new WindowAdapter() {
+                       private void setTimer(boolean flag) { desktopPane.setAllDeviceTimestampTimers(flag); }
                        @Override
-                       public void windowClosing(WindowEvent e) {
-                               desktopPane.setAllDeviceTimestampTimers(false);
-                       }
+                       public void windowClosing(WindowEvent e) { setTimer(false); }
                        @Override
-                       public void windowActivated(WindowEvent e) {
-                               desktopPane.setAllDeviceTimestampTimers(true);
-                       }
+                       public void windowActivated(WindowEvent e) { setTimer(true); }
                });
        }
-       @Override
-       public void actionPerformed(ActionEvent event) {
-               setVisible(true);
-       }
-       @Override
-       public void valueChanged(TreeSelectionEvent e) {
-               Object lastSelected = deviceTree.getLastSelectedPathComponent();
-               String html = "<html><head></head><body>";
-               if( lastSelected instanceof MidiConnecterListModel ) {
-                       MidiConnecterListModel deviceModel = (MidiConnecterListModel)lastSelected;
-                       MidiDevice.Info info = deviceModel.getMidiDevice().getDeviceInfo();
-                       html += "<b>"+deviceModel+"</b><br/>"
-                               + "<table border=\"1\"><tbody>"
-                               + "<tr><th>Version</th><td>"+info.getVersion()+"</td></tr>"
-                               + "<tr><th>Description</th><td>"+info.getDescription()+"</td></tr>"
-                               + "<tr><th>Vendor</th><td>"+info.getVendor()+"</td></tr>"
-                               + "</tbody></table>";
-                       MidiDeviceFrame frame = desktopPane.getFrameOf(deviceModel);
-                       if( frame != null ) {
-                               try {
-                                       frame.setSelected(true);
-                               } catch( PropertyVetoException ex ) {
-                                       ex.printStackTrace();
-                               }
-                       }
-               }
-               else if( lastSelected instanceof MidiDeviceInOutType ) {
-                       MidiDeviceInOutType ioType = (MidiDeviceInOutType)lastSelected;
-                       html += "<b>"+ioType+"</b><br/>";
-                       html += ioType.getDescription()+"<br/>";
-               }
-               else if( lastSelected != null ) {
-                       html += lastSelected.toString();
-               }
-               html += "</body></html>";
-               deviceInfoPane.setText(html);
-       }
 }
index 5c97aab..65bce47 100644 (file)
@@ -1,6 +1,5 @@
 package camidion.chordhelper.mididevice;
 
-import java.awt.Rectangle;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
 
@@ -29,8 +28,9 @@ public class MidiDeviceFrame extends JInternalFrame {
        /**
         * MIDIデバイスのモデルからフレームビューを構築します。
         * @param model MIDIデバイスのTransmitter/Receiverリストモデル
+        * @param cablePane MIDIケーブル描画面
         */
-       public MidiDeviceFrame(MidiConnecterListModel model) {
+       public MidiDeviceFrame(MidiConnecterListModel model, MidiCablePane cablePane) {
                super( null, true, true, false, false );
                //
                // タイトルの設定
@@ -42,19 +42,19 @@ public class MidiDeviceFrame extends JInternalFrame {
                        title = (model.rxSupported()?"[OUT] ":"[No I/O] ")+title;
                }
                setTitle(title);
-               listView = new MidiConnecterListView(model);
+               listView = new MidiConnecterListView(model, cablePane);
                setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
                addInternalFrameListener(
                        new InternalFrameAdapter() {
                                public void internalFrameOpened(InternalFrameEvent e) {
-                                       if( ! listView.getModel().getMidiDevice().isOpen() )
-                                               setVisible(false);
+                                       boolean isOpen = listView.getModel().getMidiDevice().isOpen();
+                                       if( ! isOpen ) setVisible(isOpen);
                                }
                                public void internalFrameClosing(InternalFrameEvent e) {
                                        MidiConnecterListModel m = listView.getModel();
                                        m.closeDevice();
-                                       if( ! m.getMidiDevice().isOpen() )
-                                               setVisible(false);
+                                       boolean isOpen = m.getMidiDevice().isOpen();
+                                       if( isVisible() != isOpen ) setVisible(isOpen);
                                }
                        }
                );
@@ -81,29 +81,4 @@ public class MidiDeviceFrame extends JInternalFrame {
                        }});
                }});
        }
-       /**
-        * 指定されたインデックスが示す仮想MIDI端子リストの要素のセル範囲を返します。
-        *
-        * @param index リスト要素のインデックス
-        * @return セル範囲の矩形
-        */
-       public Rectangle getListCellBounds(int index) {
-               Rectangle rect = listView.getCellBounds(index,index);
-               if( rect == null )
-                       return null;
-               rect.translate(
-                       getRootPane().getX() + getContentPane().getX(),
-                       getRootPane().getY() + getContentPane().getY()
-               );
-               return rect;
-       }
-       /**
-        * 仮想MIDI端子リストの指定された要素のセル範囲を返します。
-        *
-        * @param transciver 要素となるMIDI端子(Transmitter または Receiver)
-        * @return セル範囲の矩形
-        */
-       public Rectangle getListCellBounds(AutoCloseable transciver) {
-               return getListCellBounds(listView.getModel().indexOf(transciver));
-       }
-}
\ No newline at end of file
+}
index 17b4167..9795671 100644 (file)
@@ -34,54 +34,57 @@ public class MidiDeviceModelList extends Vector<MidiConnecterListModel> {
        private MidiConnecterListModel firstMidiOutModel;
        /**
         * MIDIデバイスモデルリストを生成します。
-        * @param vmdList 仮想MIDIデバイスのリスト
+        * @param guiVirtualDeviceList GUI仮想MIDIデバイスのリスト
         */
-       public MidiDeviceModelList(List<VirtualMidiDevice> vmdList) {
-               MidiDevice.Info[] devInfos = MidiSystem.getMidiDeviceInfo();
-               MidiConnecterListModel guiModels[] = new MidiConnecterListModel[vmdList.size()];
+       public MidiDeviceModelList(List<VirtualMidiDevice> guiVirtualDeviceList) {
+               MidiDevice.Info[] deviceInfos = MidiSystem.getMidiDeviceInfo();
+               MidiConnecterListModel guiModels[] = new MidiConnecterListModel[guiVirtualDeviceList.size()];
                MidiConnecterListModel firstMidiInModel = null;
-               for( int i=0; i<vmdList.size(); i++ )
-                       guiModels[i] = addMidiDevice(vmdList.get(i));
+               //
+               // GUI仮想MIDIデバイスリストの構築
+               for( int i=0; i<guiVirtualDeviceList.size(); i++ )
+                       guiModels[i] = addMidiDevice(guiVirtualDeviceList.get(i));
+               //
+               // シーケンサの取得
                Sequencer sequencer;
                try {
                        sequencer = MidiSystem.getSequencer(false);
                        sequencerModel = (MidiSequencerModel)addMidiDevice(sequencer);
                } catch( MidiUnavailableException e ) {
-                       System.out.println(
-                               ChordHelperApplet.VersionInfo.NAME +
-                               " : MIDI sequencer unavailable"
-                       );
+                       System.out.println(ChordHelperApplet.VersionInfo.NAME +" : MIDI sequencer unavailable");
                        e.printStackTrace();
                }
+               // MIDIエディタの生成
                editorDialog = new MidiSequenceEditor(sequencerModel);
                editorDialogModel = addMidiDevice(editorDialog.getVirtualMidiDevice());
-               for( MidiDevice.Info info : devInfos ) {
+               for( MidiDevice.Info info : deviceInfos ) {
+                       // MIDIデバイスの取得
                        MidiDevice device;
                        try {
                                device = MidiSystem.getMidiDevice(info);
                        } catch( MidiUnavailableException e ) {
                                e.printStackTrace(); continue;
                        }
+                       // シーケンサの場合はすでに取得済みなのでスキップ
                        if( device instanceof Sequencer ) continue;
+                       //
+                       // Java内蔵シンセサイザ
                        if( device instanceof Synthesizer ) {
                                try {
                                        synthModel = addMidiDevice(MidiSystem.getSynthesizer());
                                } catch( MidiUnavailableException e ) {
-                                       System.out.println(
-                                               ChordHelperApplet.VersionInfo.NAME +
-                                               " : Java internal MIDI synthesizer unavailable"
-                                       );
+                                       System.out.println(ChordHelperApplet.VersionInfo.NAME +
+                                                       " : Java internal MIDI synthesizer unavailable");
                                        e.printStackTrace();
                                }
                                continue;
                        }
+                       // MIDIデバイスを追加し、最初のエントリを覚えておく
                        MidiConnecterListModel m = addMidiDevice(device);
-                       if( m.rxSupported() && firstMidiOutModel == null )
-                               firstMidiOutModel = m;
-                       if( m.txSupported() && firstMidiInModel == null )
-                               firstMidiInModel = m;
+                       if( m.rxSupported() && firstMidiOutModel == null ) firstMidiOutModel = m;
+                       if( m.txSupported() && firstMidiInModel == null ) firstMidiInModel = m;
                }
-               // デバイスを開く。
+               // MIDIデバイスを開く。
                //   NOTE: 必ず MIDI OUT Rx デバイスを先に開くこと。
                //
                //   そうすれば、後から開いた MIDI IN Tx デバイスからの
@@ -100,34 +103,27 @@ public class MidiDeviceModelList extends Vector<MidiConnecterListModel> {
                                firstMidiInModel,
                                editorDialogModel,
                        };
-                       for( MidiConnecterListModel m : openModels ) {
-                               if( m != null ) m.openDevice();
-                       }
-                       for( MidiConnecterListModel m : guiModels ) {
-                               m.openDevice();
-                       }
+                       for( MidiConnecterListModel m : openModels ) if( m != null ) m.openDevice();
+                       for( MidiConnecterListModel m : guiModels ) m.openDevice();
                } catch( MidiUnavailableException ex ) {
                        ex.printStackTrace();
                }
                // 初期接続
                //
                for( MidiConnecterListModel mtx : guiModels ) {
-                       for( MidiConnecterListModel mrx : guiModels )
-                               mtx.connectToReceiverOf(mrx);
+                       for( MidiConnecterListModel mrx : guiModels ) mtx.connectToReceiverOf(mrx);
                        mtx.connectToReceiverOf(sequencerModel);
                        mtx.connectToReceiverOf(synthModel);
                        mtx.connectToReceiverOf(firstMidiOutModel);
                }
                if( firstMidiInModel != null ) {
-                       for( MidiConnecterListModel m : guiModels )
-                               firstMidiInModel.connectToReceiverOf(m);
+                       for( MidiConnecterListModel m : guiModels ) firstMidiInModel.connectToReceiverOf(m);
                        firstMidiInModel.connectToReceiverOf(sequencerModel);
                        firstMidiInModel.connectToReceiverOf(synthModel);
                        firstMidiInModel.connectToReceiverOf(firstMidiOutModel);
                }
                if( sequencerModel != null ) {
-                       for( MidiConnecterListModel m : guiModels )
-                               sequencerModel.connectToReceiverOf(m);
+                       for( MidiConnecterListModel m : guiModels ) sequencerModel.connectToReceiverOf(m);
                        sequencerModel.connectToReceiverOf(synthModel);
                        sequencerModel.connectToReceiverOf(firstMidiOutModel);
                }
diff --git a/src/camidion/chordhelper/mididevice/MidiDeviceTree.java b/src/camidion/chordhelper/mididevice/MidiDeviceTree.java
deleted file mode 100644 (file)
index 645b858..0000000
+++ /dev/null
@@ -1,97 +0,0 @@
-package camidion.chordhelper.mididevice;
-
-import java.awt.Component;
-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 javax.swing.JTree;
-import javax.swing.event.InternalFrameEvent;
-import javax.swing.event.InternalFrameListener;
-import javax.swing.tree.DefaultTreeCellRenderer;
-import javax.swing.tree.TreeModel;
-
-/**
- * MIDIデバイスツリービュー
- */
-public class MidiDeviceTree extends JTree
-       implements Transferable, DragGestureListener, InternalFrameListener
-{
-       /**
-        * MIDIデバイスツリービューを構築します。
-        * @param model このビューにデータを提供するモデル
-        */
-       public MidiDeviceTree(MidiDeviceTreeModel model) {
-               super(model);
-        (new DragSource()).createDefaultDragGestureRecognizer(
-               this, DnDConstants.ACTION_COPY_OR_MOVE, this
-        );
-        setCellRenderer(new DefaultTreeCellRenderer() {
-               @Override
-               public Component getTreeCellRendererComponent(
-                       JTree tree, Object value,
-                       boolean selected, boolean expanded, boolean leaf, int row,
-                       boolean hasFocus
-               ) {
-                       super.getTreeCellRendererComponent(
-                               tree, value, selected, expanded, leaf, row, hasFocus
-                       );
-                       if(leaf) {
-                                       setIcon(MidiConnecterListView.MIDI_CONNECTER_ICON);
-                                       setDisabledIcon(MidiConnecterListView.MIDI_CONNECTER_ICON);
-                               MidiConnecterListModel listModel = (MidiConnecterListModel)value;
-                               setEnabled( ! listModel.getMidiDevice().isOpen() );
-                       }
-                       return this;
-               }
-       });
-       }
-       /**
-        * このデバイスツリーからドラッグされるデータフレーバ
-        */
-       public static final DataFlavor
-               TREE_MODEL_FLAVOR = new DataFlavor(TreeModel.class, "TreeModel");
-       private static final DataFlavor
-               TREE_MODE_FLAVORS[] = {TREE_MODEL_FLAVOR};
-       @Override
-       public Object getTransferData(DataFlavor flavor) {
-               return getLastSelectedPathComponent();
-       }
-       @Override
-       public DataFlavor[] getTransferDataFlavors() {
-               return TREE_MODE_FLAVORS;
-       }
-       @Override
-       public boolean isDataFlavorSupported(DataFlavor flavor) {
-               return flavor.equals(TREE_MODEL_FLAVOR);
-       }
-       @Override
-       public void dragGestureRecognized(DragGestureEvent dge) {
-               int action = dge.getDragAction();
-               if( (action & DnDConstants.ACTION_COPY_OR_MOVE) != 0 ) {
-                       dge.startDrag(DragSource.DefaultMoveDrop, this, null);
-               }
-       }
-       @Override
-       public void internalFrameOpened(InternalFrameEvent e) {}
-       /**
-        *      MidiDeviceFrame のクローズ処理中に再描画リクエストを送ります。
-        */
-       @Override
-       public void internalFrameClosing(InternalFrameEvent e) {
-               repaint();
-       }
-       @Override
-       public void internalFrameClosed(InternalFrameEvent e) {}
-       @Override
-       public void internalFrameIconified(InternalFrameEvent e) {}
-       @Override
-       public void internalFrameDeiconified(InternalFrameEvent e) {}
-       @Override
-       public void internalFrameActivated(InternalFrameEvent e) {}
-       @Override
-       public void internalFrameDeactivated(InternalFrameEvent e) {}
-}
\ No newline at end of file
diff --git a/src/camidion/chordhelper/mididevice/MidiDeviceTreeView.java b/src/camidion/chordhelper/mididevice/MidiDeviceTreeView.java
new file mode 100644 (file)
index 0000000..6e2a3d1
--- /dev/null
@@ -0,0 +1,76 @@
+package camidion.chordhelper.mididevice;
+
+import java.awt.Component;
+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 javax.swing.JTree;
+import javax.swing.event.InternalFrameAdapter;
+import javax.swing.event.InternalFrameEvent;
+import javax.swing.event.InternalFrameListener;
+import javax.swing.tree.DefaultTreeCellRenderer;
+import javax.swing.tree.TreeModel;
+
+/**
+ * MIDIデバイスツリービュー
+ */
+public class MidiDeviceTreeView extends JTree
+{
+       /**
+        *      {@link MidiDeviceFrame} を閉じたことを検知して再描画するためのリスナー
+        */
+       public final InternalFrameListener midiDeviceFrameListener = new InternalFrameAdapter() {
+               @Override
+               public void internalFrameClosing(InternalFrameEvent e) { repaint(); }
+       };
+
+       public static final DataFlavor TREE_MODEL_FLAVOR = new DataFlavor(TreeModel.class, "TreeModel");
+
+       private Transferable draggingObject = new Transferable() {
+               private DataFlavor flavors[] = {TREE_MODEL_FLAVOR};
+               @Override
+               public Object getTransferData(DataFlavor flavor) { return getLastSelectedPathComponent(); }
+               @Override
+               public DataFlavor[] getTransferDataFlavors() { return flavors; }
+               @Override
+               public boolean isDataFlavorSupported(DataFlavor flavor) {
+                       return flavor.equals(TREE_MODEL_FLAVOR);
+               }
+       };
+       /**
+        * MIDIデバイスツリービューを構築します。
+        * @param model このビューにデータを提供するモデル
+        */
+       public MidiDeviceTreeView(MidiDeviceTreeModel model) {
+               super(model);
+               (new DragSource()).createDefaultDragGestureRecognizer(
+                       this, DnDConstants.ACTION_COPY_OR_MOVE, new DragGestureListener() {
+                               @Override
+                               public void dragGestureRecognized(DragGestureEvent dge) {
+                                       if( (dge.getDragAction() & DnDConstants.ACTION_COPY_OR_MOVE) != 0 ) {
+                                               dge.startDrag(DragSource.DefaultMoveDrop, draggingObject, null);
+                                       }
+                               }
+                       }
+               );
+               setCellRenderer(new DefaultTreeCellRenderer() {
+                       @Override
+                       public Component getTreeCellRendererComponent(JTree tree, Object value,
+                                       boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus)
+                       {
+                               super.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus);
+                               if(leaf) {
+                                       setIcon(MidiConnecterListView.MIDI_CONNECTER_ICON);
+                                       setDisabledIcon(MidiConnecterListView.MIDI_CONNECTER_ICON);
+                                       MidiConnecterListModel listModel = (MidiConnecterListModel)value;
+                                       setEnabled( ! listModel.getMidiDevice().isOpen() );
+                               }
+                               return this;
+                       }
+               });
+       }
+}
\ No newline at end of file
diff --git a/src/camidion/chordhelper/mididevice/MidiOpenedDevicesView.java b/src/camidion/chordhelper/mididevice/MidiOpenedDevicesView.java
new file mode 100644 (file)
index 0000000..e3b7a9a
--- /dev/null
@@ -0,0 +1,142 @@
+package camidion.chordhelper.mididevice;
+
+import java.awt.Point;
+import java.awt.dnd.DnDConstants;
+import java.awt.dnd.DropTarget;
+import java.awt.dnd.DropTargetAdapter;
+import java.awt.dnd.DropTargetDragEvent;
+import java.awt.dnd.DropTargetDropEvent;
+import java.awt.event.ComponentAdapter;
+import java.awt.event.ComponentEvent;
+
+import javax.sound.midi.MidiUnavailableException;
+import javax.swing.JDesktopPane;
+import javax.swing.JInternalFrame;
+import javax.swing.JLayeredPane;
+import javax.swing.JOptionPane;
+import javax.swing.Timer;
+
+/**
+ * 開いている MIDI デバイスを置くためのデスクトップビュー
+ */
+public class MidiOpenedDevicesView extends JDesktopPane {
+       private MidiCablePane cablePane = new MidiCablePane(this);
+       private DropTargetAdapter dropTargetListener = new DropTargetAdapter() {
+               @Override
+               public void dragEnter(DropTargetDragEvent dtde) {
+                       if( dtde.getTransferable().isDataFlavorSupported(MidiDeviceTreeView.TREE_MODEL_FLAVOR) ) {
+                               dtde.acceptDrag(DnDConstants.ACTION_COPY_OR_MOVE);
+                       }
+               }
+               @Override
+               public void drop(DropTargetDropEvent dtde) {
+                       dtde.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
+                       try {
+                               int action = dtde.getDropAction() ;
+                               if( (action & DnDConstants.ACTION_COPY_OR_MOVE) != 0 ) {
+                                       Object data = dtde.getTransferable().getTransferData(MidiDeviceTreeView.TREE_MODEL_FLAVOR);
+                                       if( data instanceof MidiConnecterListModel ) {
+                                               MidiConnecterListModel deviceModel = (MidiConnecterListModel)data;
+                                               try {
+                                                       deviceModel.openDevice();
+                                               } catch( MidiUnavailableException e ) {
+                                                       //
+                                                       // デバイスを開くのに失敗した場合
+                                                       //
+                                                       //   例えば、「Microsort MIDI マッパー」と
+                                                       //   「Microsoft GS Wavetable SW Synth」を
+                                                       //   同時に開こうとするとここに来る。
+                                                       //
+                                                       String title = "Cannot open MIDI device";
+                                                       String message = "MIDIデバイス "+deviceModel+" はすでに使用中です。\n"
+                                                               +"他のデバイスが連動して開いていないか確認してください。\n\n"
+                                                               + e.getMessage();
+                                                       dtde.dropComplete(false);
+                                                       JOptionPane.showMessageDialog(null, message, title, JOptionPane.ERROR_MESSAGE);
+                                                       return;
+                                               }
+                                               if( ! deviceModel.getMidiDevice().isOpen() ) {
+                                                       // 例外が出なかったにも関わらずデバイスが開かれていない場合
+                                                       String title = "Cannot open MIDI device";
+                                                       String message = "MIDIデバイス "+deviceModel+" はすでに使用中です。\n"
+                                                               +"他のデバイスが連動して開いていないか確認してください。";
+                                                       dtde.dropComplete(false);
+                                                       JOptionPane.showMessageDialog(null, message, title, JOptionPane.ERROR_MESSAGE);
+                                                       return;
+                                               }
+                                               dtde.dropComplete(true);
+                                               //
+                                               // デバイスが正常に開かれたことを確認できたら
+                                               // ドロップした場所へフレームを配置して可視化する。
+                                               //
+                                               MidiDeviceFrame deviceFrame = getMidiDeviceFrameOf(deviceModel);
+                                               if( deviceFrame == null ) return;
+                                               Point loc = dtde.getLocation();
+                                               loc.translate( -deviceFrame.getWidth()/2, 0 );
+                                               deviceFrame.setLocation(loc);
+                                               deviceFrame.setVisible(true);
+                                               return;
+                                       }
+                               }
+                       }
+                       catch (Exception ex) {
+                               ex.printStackTrace();
+                       }
+                       dtde.dropComplete(false);
+               }
+       };
+       public MidiOpenedDevicesView(MidiDeviceTreeView deviceTree) {
+               add(cablePane, JLayeredPane.PALETTE_LAYER);
+               int frameIndex = 0;
+               MidiDeviceTreeModel treeModel = (MidiDeviceTreeModel)deviceTree.getModel();
+               for( MidiConnecterListModel deviceModel : treeModel.deviceModelList ) {
+                       deviceModel.addListDataListener(cablePane.midiConnecterListDataListener);
+                       MidiDeviceFrame frame = new MidiDeviceFrame(deviceModel, cablePane) {{
+                               addInternalFrameListener(cablePane.midiDeviceFrameListener);
+                               addComponentListener(cablePane.midiDeviceFrameComponentListener);
+                       }};
+                       frame.addInternalFrameListener(deviceTree.midiDeviceFrameListener);
+                       add(frame);
+                       if( deviceModel.getMidiDevice().isOpen() ) {
+                               frame.setBounds( 10+(frameIndex%2)*260, 10+frameIndex*55, 250, 100 );
+                               frame.setVisible(true);
+                               frameIndex++;
+                       }
+               }
+               addComponentListener(new ComponentAdapter() {
+                       @Override
+                       public void componentResized(ComponentEvent e) { cablePane.setSize(getSize()); }
+               });
+               new DropTarget( this, DnDConstants.ACTION_COPY_OR_MOVE, dropTargetListener, true );
+       }
+       /**
+        * 指定されたMIDIデバイスモデルに対するMIDIデバイスフレームを返します。
+        *
+        * @param deviceModel MIDIデバイスモデル
+        * @return 対応するMIDIデバイスフレーム(ない場合 null)
+        */
+       public MidiDeviceFrame getMidiDeviceFrameOf(MidiConnecterListModel deviceModel) {
+               JInternalFrame[] frames = getAllFramesInLayer(JLayeredPane.DEFAULT_LAYER);
+               for( JInternalFrame frame : frames ) {
+                       if( ! (frame instanceof MidiDeviceFrame) ) continue;
+                       MidiDeviceFrame deviceFrame = (MidiDeviceFrame)frame;
+                       if( deviceFrame.listView.getModel() == deviceModel ) return deviceFrame;
+               }
+               return null;
+       }
+       private boolean isTimerStarted;
+       /**
+        * タイムスタンプを更新するタイマーを開始または停止します。
+        * @param toStart trueで開始、falseで停止
+        */
+       public void setAllDeviceTimestampTimers(boolean toStart) {
+               if( isTimerStarted == toStart ) return;
+               isTimerStarted = toStart;
+               JInternalFrame[] frames = getAllFramesInLayer(JLayeredPane.DEFAULT_LAYER);
+               for( JInternalFrame frame : frames ) {
+                       if( ! (frame instanceof MidiDeviceFrame) ) continue;
+                       Timer timer = ((MidiDeviceFrame)frame).timer;
+                       if( toStart ) timer.start(); else timer.stop();
+               }
+       }
+}