OSDN Git Service

de97d1e179001c59e005d1e1186355fd100fc335
[midichordhelper/MIDIChordHelper.git] / src / camidion / chordhelper / mididevice / MidiDeviceDesktopPane.java
1 package camidion.chordhelper.mididevice;
2
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;
8 import java.util.Map;
9 import java.util.Objects;
10 import java.util.Set;
11 import java.util.stream.Collectors;
12 import java.util.stream.Stream;
13
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;
23
24 import camidion.chordhelper.ChordHelperApplet;
25
26 /**
27  * 開いているMIDIデバイスを置くためのデスクトップビュー
28  */
29 public class MidiDeviceDesktopPane extends JDesktopPane {
30         /**
31          * MIDIデバイスモデルからフレームを割り出すためのマップ
32          */
33         private Map<MidiDeviceModel, MidiDeviceFrame> frameOfModel = new HashMap<>();
34         /**
35          * 指定されたMIDIデバイスモデルを表示しているフレームを選択します。
36          * nullを指定するとフレームの選択を解除します。
37          * @param deviceModel 対象のMIDIデバイスモデル
38          */
39         public void setSelectedMidiDeviceModel(MidiDeviceModel deviceModel) {
40                 if( deviceModel != null ) {
41                         MidiDeviceFrame deviceFrame = frameOfModel.get(deviceModel);
42                         if( deviceFrame != null ) {
43                                 deviceFrame.toFront();
44                                 try {
45                                         deviceFrame.setSelected(true);
46                                 } catch( PropertyVetoException ex ) {
47                                         ex.printStackTrace();
48                                 }
49                                 return;
50                         }
51                 }
52                 JInternalFrame frame = getSelectedFrame();
53                 if( frame instanceof MidiDeviceFrame ) try {
54                         ((MidiDeviceFrame)frame).setSelected(false);
55                 } catch( PropertyVetoException ex ) {
56                         ex.printStackTrace();
57                 }
58         }
59         /**
60          * 指定されたツリーパスが、オープンされているMIDIデバイスモデルの場合、それを表示しているフレームを選択します。
61          * それ以外の場合、フレームの選択を解除します。
62          * @param treePath 対象ツリーパス
63          */
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);
69                                 return;
70                         }
71                 }
72                 setSelectedMidiDeviceModel(null);
73         }
74
75         public MidiDeviceDesktopPane(MidiDeviceTreeView deviceTreeView,
76                         MidiDeviceInfoPane deviceInfoPane)
77         {
78                 MidiCablePane cablePane = new MidiCablePane(this);
79                 add(cablePane, JLayeredPane.PALETTE_LAYER);
80                 addComponentListener(new ComponentAdapter() {
81                         @Override
82                         public void componentResized(ComponentEvent e) {
83                                 cablePane.setSize(getSize());
84                         }
85                         @Override
86                         public void componentShown(ComponentEvent e) {
87                                 cablePane.repaint();
88                         }
89                 });
90                 MidiDeviceTreeModel deviceTreeModel = deviceTreeView.getModel();
91                 TreeModelListener treeModelListener = new TreeModelListener() {
92                         @Override
93                         public void treeNodesChanged(TreeModelEvent e) { }
94                         @Override
95                         public void treeNodesInserted(TreeModelEvent e) { }
96                         @Override
97                         public void treeNodesRemoved(TreeModelEvent e) { }
98                         /**
99                          * デバイスツリーの変更に応じてフレームの削除や追加を行います。
100                          * 起動時のフレーム追加だけでなく、
101                          * USBからMIDIデバイスが着脱された場合のフレームの削除や追加にも使います。
102                          * @param e デバイスツリーからのツリーモデルイベント
103                          */
104                         @Override
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))
116                                         .forEach(dm->{
117                                                 MidiDeviceFrame df;
118                                                 frameOfModel.put(dm, df = new MidiDeviceFrame(dm, cablePane));
119                                                 //
120                                                 // トランスミッタ、レシーバの接続変更時の描画予約
121                                                 Stream.of(dm.getTransmitterListModel(), dm.getReceiverListModel())
122                                                         .filter(Objects::nonNull)
123                                                         .forEach(lm->lm.addListDataListener(cablePane.midiConnecterListDataListener));
124                                                 //
125                                                 // フレーム開閉時の描画予約
126                                                 Stream.of(
127                                                                 cablePane.midiDeviceFrameListener,
128                                                                 deviceTreeView.midiDeviceFrameListener,
129                                                                 deviceInfoPane.midiDeviceFrameListener
130                                                 ).forEach(fl->df.addInternalFrameListener(fl));
131                                                 //
132                                                 // フレーム移動時、変形時の描画予約
133                                                 df.addComponentListener(cablePane.midiDeviceFrameComponentListener);
134                                                 //
135                                                 // フレームを追加
136                                                 add(df);
137                                                 if(dm.getMidiDevice().isOpen()) df.setVisible(true);
138                                         });
139                         }
140                 };
141                 deviceTreeModel.addTreeModelListener(treeModelListener);
142                 treeModelListener.treeStructureChanged(null);
143                 //
144                 // 表示したデバイスフレームを整列
145                 int toX = 10;
146                 int toY = 10;
147                 for( MidiDeviceModel deviceModel : deviceTreeModel ) {
148                         if( ! deviceModel.getMidiDevice().isOpen() ) continue;
149                         frameOfModel.get(deviceModel).setLocation(toX, toY);
150                         toX = (toX == 10 ? 270 : 10);
151                         toY += 70;
152                 }
153                 deviceTreeView.expandAll();
154                 //
155                 // ドロップ設定
156                 setTransferHandler(new TransferHandler() {
157                         @Override
158                         public boolean canImport(TransferSupport support) {
159                                 if( ! support.isDrop() ) return false;
160                                 if( support.isDataFlavorSupported(MidiDeviceTreeView.deviceModelFlavor) ) {
161                                         // MIDIデバイスを開くためのドロップを受け付ける
162                                         return true;
163                                 }
164                                 if( support.isDataFlavorSupported(TransmitterListView.transmitterFlavor) ) {
165                                         cablePane.draggedOutOfDestination();
166                                         // Transmitterの切り離しができるよう、ドロップを容認
167                                         return true;
168                                 }
169                                 if( support.isDataFlavorSupported(ReceiverListView.receiverFlavor) ) {
170                                         cablePane.draggedOutOfDestination();
171                                         // Receiverはドロップ不可
172                                 }
173                                 return false;
174                         }
175                         @Override
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;
182                                 try {
183                                         deviceModel = (MidiDeviceModel)support.getTransferable().getTransferData(flavor);
184                                         MidiDeviceFrame deviceFrame = frameOfModel.get(deviceModel);
185                                         if( deviceFrame == null ) return false;
186                                         deviceModel.open();
187                                         if( ! deviceModel.getMidiDevice().isOpen() ) {
188                                                 throw new MidiUnavailableException("開いたはずのMIDIデバイスが、開かれた状態になっていません。");
189                                         }
190                                         if( ! deviceFrame.isVisible() ) {
191                                                 deviceFrame.setLocation(support.getDropLocation().getDropPoint());
192                                                 deviceFrame.setVisible(true);
193                                         }
194                                         return true;
195                                 } catch( MidiUnavailableException e ) {
196                                         //
197                                         // デバイスを開くのに失敗した場合
198                                         //
199                                         //   例えば、「Microsort MIDI マッパー」と「Microsoft GS Wavetable SW Synth」は
200                                         //   連動して開かれるため、同時に開こうとすると、ここにたどり着く。
201                                         //
202                                         String title = ChordHelperApplet.VersionInfo.NAME;
203                                         String message = "Cannot open MIDI device '"+deviceModel+"'"
204                                                         + "\nMIDIデバイス "+deviceModel+" を開くことができません。\n"
205                                                 +"すでに他のデバイスが連動して開いている可能性があります。\n\n"
206                                                 + e.getMessage();
207                                         JOptionPane.showMessageDialog(null, message, title, JOptionPane.ERROR_MESSAGE);
208                                 } catch (Exception ex) {
209                                         ex.printStackTrace();
210                                 }
211                                 return false;
212                         }
213                 });
214         }
215
216 }