1 package camidion.chordhelper.mididevice;
3 import java.awt.datatransfer.DataFlavor;
4 import java.awt.event.ComponentAdapter;
5 import java.awt.event.ComponentEvent;
6 import java.beans.PropertyVetoException;
7 import java.util.HashMap;
9 import java.util.Objects;
11 import java.util.stream.Collectors;
12 import java.util.stream.Stream;
14 import javax.sound.midi.MidiUnavailableException;
15 import javax.swing.JDesktopPane;
16 import javax.swing.JInternalFrame;
17 import javax.swing.JLayeredPane;
18 import javax.swing.JOptionPane;
19 import javax.swing.TransferHandler;
20 import javax.swing.event.TreeModelEvent;
21 import javax.swing.event.TreeModelListener;
22 import javax.swing.tree.TreePath;
24 import camidion.chordhelper.ChordHelperApplet;
27 * 開いているMIDIデバイスを置くためのデスクトップビュー
29 public class MidiDeviceDesktopPane extends JDesktopPane {
31 * MIDIデバイスモデルからフレームを割り出すためのマップ
33 private Map<MidiDeviceModel, MidiDeviceFrame> frameOfModel = new HashMap<>();
35 * 指定されたMIDIデバイスモデルを表示しているフレームを選択します。
36 * nullを指定するとフレームの選択を解除します。
37 * @param deviceModel 対象のMIDIデバイスモデル
39 public void setSelectedMidiDeviceModel(MidiDeviceModel deviceModel) {
40 if( deviceModel != null ) {
41 MidiDeviceFrame deviceFrame = frameOfModel.get(deviceModel);
42 if( deviceFrame != null ) {
43 deviceFrame.toFront();
45 deviceFrame.setSelected(true);
46 } catch( PropertyVetoException ex ) {
52 JInternalFrame frame = getSelectedFrame();
53 if( frame instanceof MidiDeviceFrame ) try {
54 ((MidiDeviceFrame)frame).setSelected(false);
55 } catch( PropertyVetoException ex ) {
60 * 指定されたツリーパスが、オープンされているMIDIデバイスモデルの場合、それを表示しているフレームを選択します。
61 * それ以外の場合、フレームの選択を解除します。
62 * @param treePath 対象ツリーパス
64 public void setTreePath(TreePath treePath) {
65 if( treePath != null ) {
66 Object leaf = treePath.getLastPathComponent();
67 if( leaf instanceof MidiDeviceModel && ((MidiDeviceModel)leaf).getMidiDevice().isOpen() ) {
68 setSelectedMidiDeviceModel((MidiDeviceModel)leaf);
72 setSelectedMidiDeviceModel(null);
75 public MidiDeviceDesktopPane(MidiDeviceTreeView deviceTreeView,
76 MidiDeviceInfoPane deviceInfoPane)
78 MidiCablePane cablePane = new MidiCablePane(this);
79 add(cablePane, JLayeredPane.PALETTE_LAYER);
80 addComponentListener(new ComponentAdapter() {
82 public void componentResized(ComponentEvent e) {
83 cablePane.setSize(getSize());
86 public void componentShown(ComponentEvent e) {
90 MidiDeviceTreeModel deviceTreeModel = deviceTreeView.getModel();
91 TreeModelListener treeModelListener = new TreeModelListener() {
93 public void treeNodesChanged(TreeModelEvent e) { }
95 public void treeNodesInserted(TreeModelEvent e) { }
97 public void treeNodesRemoved(TreeModelEvent e) { }
99 * デバイスツリーの変更に応じてフレームの削除や追加を行います。
101 * USBからMIDIデバイスが着脱された場合のフレームの削除や追加にも使います。
102 * @param e デバイスツリーからのツリーモデルイベント
105 public void treeStructureChanged(TreeModelEvent e) {
106 Set<MidiDeviceModel> removedUsbMidiDevices =
107 frameOfModel.keySet().stream()
108 .filter(dm-> ! deviceTreeModel.contains(dm))
109 .collect(Collectors.toSet());
110 removedUsbMidiDevices.stream()
111 .map(dm->frameOfModel.remove(dm))
112 .filter(Objects::nonNull)
113 .forEach(f->remove(f));
114 deviceTreeModel.stream()
115 .filter(dm-> ! frameOfModel.containsKey(dm))
118 frameOfModel.put(dm, df = new MidiDeviceFrame(dm, cablePane));
120 // トランスミッタ、レシーバの接続変更時の描画予約
121 Stream.of(dm.getTransmitterListModel(), dm.getReceiverListModel())
122 .filter(Objects::nonNull)
123 .forEach(lm->lm.addListDataListener(cablePane.midiConnecterListDataListener));
127 cablePane.midiDeviceFrameListener,
128 deviceTreeView.midiDeviceFrameListener,
129 deviceInfoPane.midiDeviceFrameListener
130 ).forEach(fl->df.addInternalFrameListener(fl));
133 df.addComponentListener(cablePane.midiDeviceFrameComponentListener);
137 if(dm.getMidiDevice().isOpen()) df.setVisible(true);
141 deviceTreeModel.addTreeModelListener(treeModelListener);
142 treeModelListener.treeStructureChanged(null);
147 for( MidiDeviceModel deviceModel : deviceTreeModel ) {
148 if( ! deviceModel.getMidiDevice().isOpen() ) continue;
149 frameOfModel.get(deviceModel).setLocation(toX, toY);
150 toX = (toX == 10 ? 270 : 10);
153 deviceTreeView.expandAll();
156 setTransferHandler(new TransferHandler() {
158 public boolean canImport(TransferSupport support) {
159 if( ! support.isDrop() ) return false;
160 if( support.isDataFlavorSupported(MidiDeviceTreeView.deviceModelFlavor) ) {
161 // MIDIデバイスを開くためのドロップを受け付ける
164 if( support.isDataFlavorSupported(TransmitterListView.transmitterFlavor) ) {
165 cablePane.draggedOutOfDestination();
166 // Transmitterの切り離しができるよう、ドロップを容認
169 if( support.isDataFlavorSupported(ReceiverListView.receiverFlavor) ) {
170 cablePane.draggedOutOfDestination();
176 public boolean importData(TransferSupport support) {
177 // canImport()がTransmitterを容認しているので、ここにTransmitterが来ることがある。
178 // そこで、DataFlavorをチェックし、MIDIデバイスでなければ拒否する。
179 DataFlavor flavor = MidiDeviceTreeView.deviceModelFlavor;
180 if( ! support.isDataFlavorSupported(flavor) ) return false;
181 MidiDeviceModel deviceModel = null;
183 deviceModel = (MidiDeviceModel)support.getTransferable().getTransferData(flavor);
184 MidiDeviceFrame deviceFrame = frameOfModel.get(deviceModel);
185 if( deviceFrame == null ) return false;
187 if( ! deviceModel.getMidiDevice().isOpen() ) {
188 throw new MidiUnavailableException("開いたはずのMIDIデバイスが、開かれた状態になっていません。");
190 if( ! deviceFrame.isVisible() ) {
191 deviceFrame.setLocation(support.getDropLocation().getDropPoint());
192 deviceFrame.setVisible(true);
195 } catch( MidiUnavailableException e ) {
199 // 例えば、「Microsort MIDI マッパー」と「Microsoft GS Wavetable SW Synth」は
200 // 連動して開かれるため、同時に開こうとすると、ここにたどり着く。
202 String title = ChordHelperApplet.VersionInfo.NAME;
203 String message = "Cannot open MIDI device '"+deviceModel+"'"
204 + "\nMIDIデバイス "+deviceModel+" を開くことができません。\n"
205 +"すでに他のデバイスが連動して開いている可能性があります。\n\n"
207 JOptionPane.showMessageDialog(null, message, title, JOptionPane.ERROR_MESSAGE);
208 } catch (Exception ex) {
209 ex.printStackTrace();