OSDN Git Service

bb9c55b712f3cc4f6cad7b0d0d13120daf32cda4
[midichordhelper/MIDIChordHelper.git] / src / MIDIDevice.java
1 \r
2 import java.awt.BasicStroke;\r
3 import java.awt.Color;\r
4 import java.awt.Component;\r
5 import java.awt.Graphics;\r
6 import java.awt.Graphics2D;\r
7 import java.awt.Insets;\r
8 import java.awt.Point;\r
9 import java.awt.Rectangle;\r
10 import java.awt.Stroke;\r
11 import java.awt.datatransfer.DataFlavor;\r
12 import java.awt.datatransfer.Transferable;\r
13 import java.awt.dnd.DnDConstants;\r
14 import java.awt.dnd.DragGestureEvent;\r
15 import java.awt.dnd.DragGestureListener;\r
16 import java.awt.dnd.DragSource;\r
17 import java.awt.dnd.DropTarget;\r
18 import java.awt.dnd.DropTargetDragEvent;\r
19 import java.awt.dnd.DropTargetDropEvent;\r
20 import java.awt.dnd.DropTargetEvent;\r
21 import java.awt.dnd.DropTargetListener;\r
22 import java.awt.event.ActionEvent;\r
23 import java.awt.event.ActionListener;\r
24 import java.awt.event.ComponentAdapter;\r
25 import java.awt.event.ComponentEvent;\r
26 import java.awt.event.ComponentListener;\r
27 import java.beans.PropertyVetoException; // PropertyVetoException\r
28 import java.util.Hashtable;\r
29 import java.util.List;\r
30 import java.util.Vector; // Vector\r
31 \r
32 import javax.sound.midi.InvalidMidiDataException;\r
33 import javax.sound.midi.MidiChannel;\r
34 import javax.sound.midi.MidiDevice;\r
35 import javax.sound.midi.MidiMessage;\r
36 import javax.sound.midi.MidiSystem;\r
37 import javax.sound.midi.MidiUnavailableException;\r
38 import javax.sound.midi.Receiver;\r
39 import javax.sound.midi.Sequencer;\r
40 import javax.sound.midi.ShortMessage;\r
41 import javax.sound.midi.Synthesizer;\r
42 import javax.sound.midi.SysexMessage;\r
43 import javax.sound.midi.Transmitter;\r
44 import javax.swing.AbstractListModel;\r
45 import javax.swing.BoxLayout;\r
46 import javax.swing.Icon;\r
47 import javax.swing.JButton;\r
48 import javax.swing.JComponent;\r
49 import javax.swing.JDesktopPane;\r
50 import javax.swing.JDialog;\r
51 import javax.swing.JEditorPane;\r
52 import javax.swing.JInternalFrame;\r
53 import javax.swing.JLabel;\r
54 import javax.swing.JLayeredPane;\r
55 import javax.swing.JList;\r
56 import javax.swing.JOptionPane;\r
57 import javax.swing.JPanel;\r
58 import javax.swing.JScrollPane;\r
59 import javax.swing.JSplitPane;\r
60 import javax.swing.JTree;\r
61 import javax.swing.ListCellRenderer;\r
62 import javax.swing.ListSelectionModel;\r
63 import javax.swing.event.EventListenerList;\r
64 import javax.swing.event.InternalFrameAdapter;\r
65 import javax.swing.event.InternalFrameEvent;\r
66 import javax.swing.event.InternalFrameListener;\r
67 import javax.swing.event.ListDataEvent;\r
68 import javax.swing.event.ListDataListener;\r
69 import javax.swing.event.TreeModelEvent;\r
70 import javax.swing.event.TreeModelListener;\r
71 import javax.swing.event.TreeSelectionEvent;\r
72 import javax.swing.event.TreeSelectionListener;\r
73 import javax.swing.tree.DefaultTreeCellRenderer;\r
74 import javax.swing.tree.TreeModel;\r
75 import javax.swing.tree.TreePath;\r
76 \r
77 /**\r
78  * 仮想MIDIデバイス\r
79  */\r
80 interface VirtualMidiDevice extends MidiDevice {\r
81         MidiChannel[] getChannels();\r
82         void sendMidiMessage( MidiMessage msg );\r
83         void setReceiver(Receiver rx);\r
84 }\r
85 /**\r
86  * 仮想MIDIデバイスの最小限の実装を提供するクラス\r
87  */\r
88 abstract class AbstractVirtualMidiDevice implements VirtualMidiDevice {\r
89         protected boolean is_open = false;\r
90         protected long top_microsecond = -1;\r
91         protected Info info;\r
92 \r
93         private int maxTransmitters = -1;\r
94         protected List<Transmitter> txList = new Vector<Transmitter>();\r
95         protected MidiChannelMessageSender[]\r
96                 channels = new MidiChannelMessageSender[MIDISpec.MAX_CHANNELS];\r
97 \r
98         private int maxReceivers = 1;\r
99         protected List<Receiver> rxList = new Vector<Receiver>();\r
100 \r
101         protected AbstractVirtualMidiDevice() {\r
102                 for( int i=0; i<channels.length; i++ )\r
103                         channels[i] = new MidiChannelMessageSender(this,i);\r
104         }\r
105         protected void setMaxReceivers(int max_rx) {\r
106                 maxReceivers = max_rx;\r
107         }\r
108         protected void setMaxTransmitters(int max_tx) {\r
109                 maxTransmitters = max_tx;\r
110         }\r
111         public void open() {\r
112                 is_open = true;\r
113                 top_microsecond = System.nanoTime()/1000;\r
114         }\r
115         public void close() {\r
116                 txList.clear();\r
117                 is_open = false;\r
118         }\r
119         public boolean isOpen() { return is_open; }\r
120         public Info getDeviceInfo() { return info; }\r
121         public long getMicrosecondPosition() {\r
122                 return (top_microsecond == -1 ? -1: System.nanoTime()/1000 - top_microsecond);\r
123         }\r
124         public int getMaxReceivers() { return maxReceivers; }\r
125         public Receiver getReceiver() {\r
126                 return rxList.isEmpty() ? null : rxList.get(0);\r
127         }\r
128         public List<Receiver> getReceivers() { return rxList; }\r
129         public int getMaxTransmitters() { return maxTransmitters; }\r
130         public Transmitter getTransmitter() throws MidiUnavailableException {\r
131                 if( maxTransmitters == 0 ) {\r
132                         throw new MidiUnavailableException();\r
133                 }\r
134                 Transmitter new_tx = new Transmitter() {\r
135                         private Receiver rx = null;\r
136                         public void close() { txList.remove(this); }\r
137                         public Receiver getReceiver() { return rx; }\r
138                         public void setReceiver(Receiver rx) { this.rx = rx; }\r
139                 };\r
140                 txList.add(new_tx);\r
141                 return new_tx;\r
142         }\r
143         public List<Transmitter> getTransmitters() { return txList; }\r
144         public MidiChannel[] getChannels() { return channels; }\r
145         public void sendMidiMessage( MidiMessage msg ) {\r
146                 long time_stamp = getMicrosecondPosition();\r
147                 for( Transmitter tx : txList ) {\r
148                         Receiver rx = tx.getReceiver();\r
149                         if( rx != null )\r
150                                 rx.send( msg, time_stamp );\r
151                 }\r
152         }\r
153         public void setReceiver(Receiver rx) {\r
154                 if( maxReceivers == 0 )\r
155                         return;\r
156                 if( ! rxList.isEmpty() )\r
157                         rxList.clear();\r
158                 rxList.add(rx);\r
159         }\r
160 }\r
161 \r
162 /**\r
163  * 仮想MIDIデバイスからのMIDIチャンネルメッセージ送信クラス\r
164  */\r
165 class MidiChannelMessageSender implements MidiChannel {\r
166         /**\r
167          * このMIDIチャンネルの親となる仮想MIDIデバイス\r
168          */\r
169         private VirtualMidiDevice vmd;\r
170         /**\r
171          * MIDIチャンネルインデックス(チャンネル 1 のとき 0)\r
172          */\r
173         private int channel;\r
174         /**\r
175          * 指定の仮想MIDIデバイスの指定のMIDIチャンネルの\r
176          * メッセージを送信するためのインスタンスを構築します。\r
177          * @param vmd 仮想MIDIデバイス\r
178          * @param channel MIDIチャンネルインデックス(チャンネル 1 のとき 0)\r
179          */\r
180         public MidiChannelMessageSender(VirtualMidiDevice vmd, int channel) {\r
181                 this.vmd = vmd;\r
182                 this.channel = channel;\r
183         }\r
184         /**\r
185          * 仮想MIDIデバイスからこのMIDIチャンネルのショートメッセージを送信します。\r
186          * @param command このメッセージで表される MIDI コマンド\r
187          * @param data1 第 1 データバイト\r
188          * @param data2 第 2 データバイト\r
189          * @see ShortMessage#setMessage(int, int, int, int)\r
190          */\r
191         public void sendShortMessage(int command, int data1, int data2) {\r
192                 ShortMessage short_msg = new ShortMessage();\r
193                 try {\r
194                         short_msg.setMessage( command, channel, data1, data2 );\r
195                 } catch(InvalidMidiDataException e) {\r
196                         e.printStackTrace();\r
197                         return;\r
198                 }\r
199                 vmd.sendMidiMessage((MidiMessage)short_msg);\r
200         }\r
201         public void noteOff( int note_no ) { noteOff( note_no, 64 ); }\r
202         public void noteOff( int note_no, int velocity ) {\r
203                 sendShortMessage( ShortMessage.NOTE_OFF, note_no, velocity );\r
204         }\r
205         public void noteOn( int note_no, int velocity ) {\r
206                 sendShortMessage( ShortMessage.NOTE_ON, note_no, velocity );\r
207         }\r
208         public void setPolyPressure(int note_no, int pressure) {\r
209                 sendShortMessage( ShortMessage.POLY_PRESSURE, note_no, pressure );\r
210         }\r
211         public int getPolyPressure(int noteNumber) { return 0x40; }\r
212         public void controlChange(int controller, int value) {\r
213                 sendShortMessage( ShortMessage.CONTROL_CHANGE, controller, value );\r
214         }\r
215         public int getController(int controller) { return 0x40; }\r
216         public void programChange( int program ) {\r
217                 sendShortMessage( ShortMessage.PROGRAM_CHANGE, program, 0 );\r
218         }\r
219         public void programChange(int bank, int program) {\r
220                 controlChange( 0x00, ((bank>>7) & 0x7F) );\r
221                 controlChange( 0x20, (bank & 0x7F) );\r
222                 programChange( program );\r
223         }\r
224         public int getProgram() { return 0; }\r
225         public void setChannelPressure(int pressure) {\r
226                 sendShortMessage( ShortMessage.CHANNEL_PRESSURE, pressure, 0 );\r
227         }\r
228         public int getChannelPressure() { return 0x40; }\r
229         public void setPitchBend(int bend) {\r
230                 // NOTE: Pitch Bend data byte order is Little Endian\r
231                 sendShortMessage(\r
232                         ShortMessage.PITCH_BEND,\r
233                         (bend & 0x7F), ((bend>>7) & 0x7F)\r
234                 );\r
235         }\r
236         public int getPitchBend() { return MIDISpec.PITCH_BEND_NONE; }\r
237         public void allSoundOff() { controlChange( 0x78, 0 ); }\r
238         public void resetAllControllers() { controlChange( 0x79, 0 ); }\r
239         public boolean localControl(boolean on) {\r
240                 controlChange( 0x7A, on ? 0x7F : 0x00 );\r
241                 return false;\r
242         }\r
243         public void allNotesOff() { controlChange( 0x7B, 0 ); }\r
244         public void setOmni(boolean on) {\r
245                 controlChange( on ? 0x7D : 0x7C, 0 );\r
246         }\r
247         public boolean getOmni() { return false; }\r
248         public void setMono(boolean on) {}\r
249         public boolean getMono() { return false; }\r
250         public void setMute(boolean mute) {}\r
251         public boolean getMute() { return false; }\r
252         public void setSolo(boolean soloState) {}\r
253         public boolean getSolo() { return false; }\r
254 }\r
255 \r
256 /**\r
257  * 仮想 MIDI デバイスからの MIDI 受信とチャンネル状態の管理\r
258  */\r
259 abstract class AbstractMidiStatus extends Vector<AbstractMidiChannelStatus>\r
260         implements Receiver\r
261 {\r
262         private void resetStatus() { resetStatus(false); }\r
263         private void resetStatus(boolean is_GS) {\r
264                 for( AbstractMidiChannelStatus mcs : this )\r
265                         mcs.resetAllValues(is_GS);\r
266         }\r
267         public void close() { }\r
268         public void send(MidiMessage message, long timeStamp) {\r
269                 if ( message instanceof ShortMessage ) {\r
270                         ShortMessage sm = (ShortMessage)message;\r
271                         switch ( sm.getCommand() ) {\r
272 \r
273                         case ShortMessage.NOTE_ON:\r
274                                 get(sm.getChannel()).noteOn( sm.getData1(), sm.getData2() );\r
275                                 break;\r
276 \r
277                         case ShortMessage.NOTE_OFF:\r
278                                 get(sm.getChannel()).noteOff( sm.getData1(), sm.getData2() );\r
279                                 break;\r
280 \r
281                         case ShortMessage.CONTROL_CHANGE:\r
282                                 get(sm.getChannel()).controlChange( sm.getData1(), sm.getData2() );\r
283                                 break;\r
284 \r
285                         case ShortMessage.PROGRAM_CHANGE:\r
286                                 get(sm.getChannel()).programChange( sm.getData1() );\r
287                                 break;\r
288 \r
289                         case ShortMessage.PITCH_BEND:\r
290                                 get(sm.getChannel()).setPitchBend(\r
291                                         ( sm.getData1() & 0x7F ) + ( (sm.getData2() & 0x7F) << 7 )\r
292                                 );\r
293                                 break;\r
294 \r
295                                 /* Pressure系も受信したい場合、この部分を有効にする\r
296       case ShortMessage.POLY_PRESSURE:\r
297         get(sm.getChannel()).setPolyPressure( sm.getData1(), sm.getData2() );\r
298         break;\r
299       case ShortMessage.CHANNEL_PRESSURE:\r
300         get(sm.getChannel()).setChannelPressure( sm.getData1() );\r
301         break;\r
302                                  */\r
303                         }\r
304                 }\r
305                 else if ( message instanceof SysexMessage ) {\r
306                         SysexMessage sxm = (SysexMessage)message;\r
307                         switch ( sxm.getStatus() ) {\r
308 \r
309                         case SysexMessage.SYSTEM_EXCLUSIVE:\r
310                                 byte data[] = sxm.getData();\r
311                                 switch( data[0] ) {\r
312                                 case 0x7E: // Non-Realtime Universal System Exclusive Message\r
313                                         if( data[2] == 0x09 ) { // General MIDI (GM)\r
314                                                 if( data[3] == 0x01 ) { // GM System ON\r
315                                                         resetStatus();\r
316                                                 }\r
317                                                 else if( data[3] == 0x02 ) { // GM System OFF\r
318                                                         resetStatus();\r
319                                                 }\r
320                                         }\r
321                                         break;\r
322                                 case 0x41: // Roland\r
323                                         if( data[2]==0x42 && data[3]==0x12 ) { // GS DT1\r
324                                                 if( data[4]==0x40 && data[5]==0x00 && data[6]==0x7F &&\r
325                                                                 data[7]==0x00 && data[8]==0x41\r
326                                                                 ) {\r
327                                                         resetStatus(true);\r
328                                                 }\r
329                                                 else if( data[4]==0x40 && (data[5] & 0xF0)==0x10 && data[6]==0x15 ) {\r
330                                                         // Drum Map 1 or 2, otherwise Normal Part\r
331                                                         boolean is_rhythm_part = ( data[7]==1 || data[7]==2 );\r
332                                                         int ch = (data[5] & 0x0F);\r
333                                                         if( ch == 0 ) ch = 9; else if( ch <= 9 ) ch--;\r
334                                                         get(ch).setRhythmPart(is_rhythm_part);\r
335                                                 }\r
336                                                 else if( data[4]==0x00 && data[5]==0x00 && data[6]==0x7F ) {\r
337                                                         if( data[7]==0x00 && data[8]==0x01 ) {\r
338                                                                 // GM System Mode Set (1)\r
339                                                                 resetStatus(true);\r
340                                                         }\r
341                                                         if( data[7]==0x01 && data[8]==0x00 ) {\r
342                                                                 // GM System Mode Set (2)\r
343                                                                 resetStatus(true);\r
344                                                         }\r
345                                                 }\r
346                                         }\r
347                                         break;\r
348                                 case 0x43: // Yamaha\r
349                                         if( data[2] == 0x4C\r
350                                         && data[3]==0 && data[4]==0 && data[5]==0x7E\r
351                                         && data[6]==0\r
352                                                         ) {\r
353                                                 // XG System ON\r
354                                                 resetStatus();\r
355                                         }\r
356                                         break;\r
357                                 }\r
358                                 break;\r
359                         }\r
360                 }\r
361         }\r
362 }\r
363 abstract class AbstractMidiChannelStatus implements MidiChannel {\r
364         protected int channel;\r
365         protected int program = 0;\r
366         protected int pitch_bend = MIDISpec.PITCH_BEND_NONE;\r
367         protected int controller_values[] = new int[0x80];\r
368         protected boolean is_rhythm_part = false;\r
369 \r
370         protected static final int DATA_NONE = 0;\r
371         protected static final int DATA_FOR_RPN = 1;\r
372         protected final int DATA_FOR_NRPN = 2;\r
373         protected int data_for = DATA_NONE;\r
374 \r
375         public AbstractMidiChannelStatus(int channel) {\r
376                 this.channel = channel;\r
377                 resetAllValues(true);\r
378         }\r
379         public int getChannel() { return channel; }\r
380         public boolean isRhythmPart() { return is_rhythm_part; }\r
381         public void setRhythmPart(boolean is_rhythm_part) {\r
382                 this.is_rhythm_part = is_rhythm_part;\r
383         }\r
384         public void resetRhythmPart() {\r
385                 is_rhythm_part = (channel == 9);\r
386         }\r
387         public void resetAllValues() { resetAllValues(false); }\r
388         public void resetAllValues(boolean is_GS) {\r
389                 for( int i=0; i<controller_values.length; i++ )\r
390                         controller_values[i] = 0;\r
391                 if( is_GS ) resetRhythmPart();\r
392                 resetAllControllers();\r
393                 controller_values[10] = 0x40; // Set pan to center\r
394         }\r
395         public void fireRpnChanged() {}\r
396         protected void changeRPNData( int data_diff ) {\r
397                 int data_msb = controller_values[0x06];\r
398                 int data_lsb = controller_values[0x26];\r
399                 if( data_diff != 0 ) {\r
400                         // Data increment or decrement\r
401                         data_lsb += data_diff;\r
402                         if( data_lsb >= 100 ) {\r
403                                 data_lsb = 0;\r
404                                 controller_values[0x26] = ++data_msb;\r
405                         }\r
406                         else if( data_lsb < 0 ) {\r
407                                 data_lsb = 0;\r
408                                 controller_values[0x26] = --data_msb;\r
409                         }\r
410                         controller_values[0x06] = data_lsb;\r
411                 }\r
412                 fireRpnChanged();\r
413         }\r
414         @Override\r
415         public void noteOff( int note_no ) {}\r
416         @Override\r
417         public void noteOff( int note_no, int velocity ) {}\r
418         @Override\r
419         public void noteOn( int note_no, int velocity ) {}\r
420         @Override\r
421         public int getController(int controller) {\r
422                 return controller_values[controller];\r
423         }\r
424         @Override\r
425         public void programChange( int program ) {\r
426                 this.program = program;\r
427         }\r
428         @Override\r
429         public void programChange(int bank, int program) {\r
430                 controlChange( 0x00, ((bank>>7) & 0x7F) );\r
431                 controlChange( 0x20, (bank & 0x7F) );\r
432                 programChange( program );\r
433         }\r
434         @Override\r
435         public int getProgram() { return program; }\r
436         @Override\r
437         public void setPitchBend(int bend) { pitch_bend = bend; }\r
438         @Override\r
439         public int getPitchBend() { return pitch_bend; }\r
440         @Override\r
441         public void setPolyPressure(int note_no, int pressure) {}\r
442         @Override\r
443         public int getPolyPressure(int noteNumber) { return 0x40; }\r
444         @Override\r
445         public void setChannelPressure(int pressure) {}\r
446         @Override\r
447         public int getChannelPressure() { return 0x40; }\r
448         @Override\r
449         public void allSoundOff() {}\r
450         @Override\r
451         public void allNotesOff() {}\r
452         @Override\r
453         public void resetAllControllers() {\r
454                 //\r
455                 // See also:\r
456                 //   Recommended Practice (RP-015)\r
457                 //   Response to Reset All Controllers\r
458                 //   http://www.midi.org/techspecs/rp15.php\r
459                 //\r
460                 // modulation\r
461                 controller_values[0] = 0;\r
462                 //\r
463                 // pedals\r
464                 for(int i=64; i<=67; i++) controller_values[i] = 0;\r
465                 //\r
466                 // Set pitch bend to center\r
467                 pitch_bend = 8192;\r
468                 //\r
469                 // Set NRPN / RPN to null value\r
470                 for(int i=98; i<=101; i++) controller_values[i] = 127;\r
471         }\r
472         @Override\r
473         public boolean localControl(boolean on) {\r
474                 controlChange( 0x7A, on ? 0x7F : 0x00 );\r
475                 return false;\r
476         }\r
477         @Override\r
478         public void setOmni(boolean on) {\r
479                 controlChange( on ? 0x7D : 0x7C, 0 );\r
480         }\r
481         @Override\r
482         public boolean getOmni() { return false; }\r
483         @Override\r
484         public void setMono(boolean on) {}\r
485         @Override\r
486         public boolean getMono() { return false; }\r
487         @Override\r
488         public void setMute(boolean mute) {}\r
489         @Override\r
490         public boolean getMute() { return false; }\r
491         @Override\r
492         public void setSolo(boolean soloState) {}\r
493         @Override\r
494         public boolean getSolo() { return false; }\r
495         @Override\r
496         public void controlChange(int controller, int value) {\r
497                 controller_values[controller] = value & 0x7F;\r
498                 switch( controller ) {\r
499 \r
500                 case 0x78: // All Sound Off\r
501                         allSoundOff();\r
502                         break;\r
503 \r
504                 case 0x7B: // All Notes Off\r
505                         allNotesOff();\r
506                         break;\r
507 \r
508                 case 0x79: // Reset All Controllers\r
509                         resetAllControllers();\r
510                         break;\r
511 \r
512                 case 0x06: // Data Entry (MSB)\r
513                 case 0x26: // Data Entry (LSB)\r
514                         changeRPNData(0);\r
515                         break;\r
516 \r
517                 case 0x60: // Data Increment\r
518                         changeRPNData(1);\r
519                         break;\r
520 \r
521                 case 0x61: // Data Decrement\r
522                         changeRPNData(-1);\r
523                         break;\r
524 \r
525                         // Non-Registered Parameter Number\r
526                 case 0x62: // NRPN (LSB)\r
527                 case 0x63: // NRPN (MSB)\r
528                         data_for = DATA_FOR_NRPN;\r
529                         // fireRpnChanged();\r
530                         break;\r
531 \r
532                         // Registered Parameter Number\r
533                 case 0x64: // RPN (LSB)\r
534                 case 0x65: // RPN (MSB)\r
535                         data_for = DATA_FOR_RPN;\r
536                         fireRpnChanged();\r
537                         break;\r
538                 }\r
539         }\r
540 }\r
541 \r
542 /**\r
543  * Transmitter(Tx)/Receiver(Rx) のリスト(view)\r
544  *\r
545  * <p>マウスで Tx からドラッグして Rx へドロップする機能を備えた\r
546  * 仮想MIDI端子リストです。\r
547  * </p>\r
548  */\r
549 class MidiConnecterListView extends JList<AutoCloseable>\r
550         implements Transferable, DragGestureListener, DropTargetListener\r
551 {\r
552         public static final Icon MIDI_CONNECTER_ICON =\r
553                 new ButtonIcon(ButtonIcon.MIDI_CONNECTOR_ICON);\r
554         private class CellRenderer extends JLabel implements ListCellRenderer<AutoCloseable> {\r
555                 public Component getListCellRendererComponent(\r
556                         JList<? extends AutoCloseable> list,\r
557                         AutoCloseable value,\r
558                         int index,\r
559                         boolean isSelected,\r
560                         boolean cellHasFocus\r
561                 ) {\r
562                         String text;\r
563                         if( value instanceof Transmitter ) text = "Tx";\r
564                         else if( value instanceof Receiver ) text = "Rx";\r
565                         else text = (value==null ? null : value.toString());\r
566                         setText(text);\r
567                         setIcon(MIDI_CONNECTER_ICON);\r
568                         if (isSelected) {\r
569                                 setBackground(list.getSelectionBackground());\r
570                                 setForeground(list.getSelectionForeground());\r
571                         } else {\r
572                                 setBackground(list.getBackground());\r
573                                 setForeground(list.getForeground());\r
574                         }\r
575                         setEnabled(list.isEnabled());\r
576                         setFont(list.getFont());\r
577                         setOpaque(true);\r
578                         return this;\r
579                 }\r
580         }\r
581         /**\r
582          * 仮想MIDI端子リストビューを生成します。\r
583          * @param model このビューから参照されるデータモデル\r
584          */\r
585         public MidiConnecterListView(MidiConnecterListModel model) {\r
586                 super(model);\r
587                 setCellRenderer(new CellRenderer());\r
588                 setSelectionMode(ListSelectionModel.SINGLE_SELECTION);\r
589                 setLayoutOrientation(JList.HORIZONTAL_WRAP);\r
590                 setVisibleRowCount(0);\r
591         (new DragSource()).createDefaultDragGestureRecognizer(\r
592                 this, DnDConstants.ACTION_COPY_OR_MOVE, this\r
593         );\r
594                 new DropTarget( this, DnDConstants.ACTION_COPY_OR_MOVE, this, true );\r
595         }\r
596         public static final DataFlavor transmitterFlavor =\r
597                 new DataFlavor(Transmitter.class, "Transmitter");\r
598         public static final DataFlavor transmitterFlavors[] = {transmitterFlavor};\r
599         public Object getTransferData(DataFlavor flavor) {\r
600                 return getModel().getElementAt(getSelectedIndex());\r
601         }\r
602         public DataFlavor[] getTransferDataFlavors() {\r
603                 return transmitterFlavors;\r
604         }\r
605         public boolean isDataFlavorSupported(DataFlavor flavor) {\r
606                 return flavor.equals(transmitterFlavor);\r
607         }\r
608         public void dragGestureRecognized(DragGestureEvent dge) {\r
609                 int action = dge.getDragAction();\r
610                 if( (action & DnDConstants.ACTION_COPY_OR_MOVE) == 0 )\r
611                         return;\r
612                 int index = locationToIndex(dge.getDragOrigin());\r
613                 AutoCloseable data = getModel().getElementAt(index);\r
614                 if( data instanceof Transmitter ) {\r
615                         dge.startDrag(DragSource.DefaultLinkDrop, this, null);\r
616                 }\r
617         }\r
618         public void dragEnter(DropTargetDragEvent event) {\r
619                 if( event.isDataFlavorSupported(transmitterFlavor) )\r
620                         event.acceptDrag(DnDConstants.ACTION_COPY_OR_MOVE);\r
621         }\r
622         public void dragExit(DropTargetEvent dte) {}\r
623         public void dragOver(DropTargetDragEvent dtde) {}\r
624         public void dropActionChanged(DropTargetDragEvent dtde) {}\r
625         public void drop(DropTargetDropEvent event) {\r
626                 event.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);\r
627                 try {\r
628                         int maskedBits = event.getDropAction() & DnDConstants.ACTION_COPY_OR_MOVE;\r
629                         if( maskedBits != 0 ) {\r
630                                 Transferable t = event.getTransferable();\r
631                                 Object data = t.getTransferData(transmitterFlavor);\r
632                                 if( data instanceof Transmitter ) {\r
633                                         getModel().ConnectToReceiver((Transmitter)data);\r
634                                         event.dropComplete(true);\r
635                                         return;\r
636                                 }\r
637                         }\r
638                         event.dropComplete(false);\r
639                 }\r
640                 catch (Exception ex) {\r
641                         ex.printStackTrace();\r
642                         event.dropComplete(false);\r
643                 }\r
644         }\r
645         @Override\r
646         public MidiConnecterListModel getModel() {\r
647                 return (MidiConnecterListModel)super.getModel();\r
648         }\r
649 }\r
650 \r
651 /**\r
652  * 1個の MIDI デバイスに属する Transmitter/Receiver のリストモデル\r
653  */\r
654 class MidiConnecterListModel extends AbstractListModel<AutoCloseable> {\r
655         private MidiDevice device;\r
656         private List<MidiConnecterListModel> modelList;\r
657         /**\r
658          * 指定のMIDIデバイスに属する\r
659          *  {@link Transmitter}/{@link Receiver} のリストモデルを構築します。\r
660          *\r
661          * @param device 対象MIDIデバイス\r
662          * @param modelList リストモデルのリスト\r
663          */\r
664         public MidiConnecterListModel(\r
665                 MidiDevice device,\r
666                 List<MidiConnecterListModel> modelList\r
667         ) {\r
668                 this.device = device;\r
669                 this.modelList = modelList;\r
670         }\r
671         /**\r
672          * 対象MIDIデバイスを返します。\r
673          * @return 対象MIDIデバイス\r
674          */\r
675         public MidiDevice getMidiDevice() {\r
676                 return device;\r
677         }\r
678         /**\r
679          * 対象MIDIデバイスの名前を返します。\r
680          */\r
681         public String toString() {\r
682                 return device.getDeviceInfo().toString();\r
683         }\r
684         @Override\r
685         public AutoCloseable getElementAt(int index) {\r
686                 List<Receiver> rxList = device.getReceivers();\r
687                 int rxSize = rxList.size();\r
688                 if( index < rxSize ) return rxList.get(index);\r
689                 index -= rxSize;\r
690                 List<Transmitter> txList = device.getTransmitters();\r
691                 return index < txList.size() ? txList.get(index) : null;\r
692         }\r
693         @Override\r
694         public int getSize() {\r
695                 return\r
696                         device.getReceivers().size() +\r
697                         device.getTransmitters().size();\r
698         }\r
699         /**\r
700          * 指定の要素がこのリストモデルで最初に見つかった位置を返します。\r
701          *\r
702          * @param element 探したい要素\r
703          * @return 位置のインデックス(先頭が 0、見つからないとき -1)\r
704          */\r
705         public int indexOf(AutoCloseable element) {\r
706                 List<Receiver> rxList = device.getReceivers();\r
707                 int index = rxList.indexOf(element);\r
708                 if( index < 0 ) {\r
709                         List<Transmitter> txList = device.getTransmitters();\r
710                         if( (index = txList.indexOf(element)) >= 0 )\r
711                                 index += rxList.size();\r
712                 }\r
713                 return index;\r
714         }\r
715         /**\r
716          * このリストが {@link Transmitter} をサポートしているか調べます。\r
717          * @return {@link Transmitter} をサポートしていたら true\r
718          */\r
719         public boolean txSupported() {\r
720                 return device.getMaxTransmitters() != 0;\r
721         }\r
722         /**\r
723          * このリストが {@link Receiver} をサポートしているか調べます。\r
724          * @return {@link Receiver} をサポートしていたら true\r
725          */\r
726         public boolean rxSupported() {\r
727                 return device.getMaxReceivers() != 0;\r
728         }\r
729         /**\r
730          * このリストのMIDIデバイスの入出力タイプを返します。\r
731          * <p>レシーバからMIDI信号を受けて外部へ出力できるデバイスの場合は MIDI_OUT、\r
732          * 外部からMIDI信号を入力してトランスミッタからレシーバへ転送できるデバイスの場合は MIDI_IN、\r
733          * 両方できるデバイスの場合は MIDI_IN_OUT を返します。\r
734          * </p>\r
735          * @return このリストのMIDIデバイスの入出力タイプ\r
736          */\r
737         public MidiDeviceInOutType getMidiDeviceInOutType() {\r
738                 if( rxSupported() ) {\r
739                         if( txSupported() )\r
740                                 return MidiDeviceInOutType.MIDI_IN_OUT;\r
741                         else\r
742                                 return MidiDeviceInOutType.MIDI_OUT;\r
743                 }\r
744                 else {\r
745                         if( txSupported() )\r
746                                 return MidiDeviceInOutType.MIDI_IN;\r
747                         else\r
748                                 return null;\r
749                 }\r
750         }\r
751         /**\r
752          * 引数で指定されたトランスミッタを、最初のレシーバに接続します。\r
753          * <p>接続先のレシーバがない場合は無視されます。\r
754          * </p>\r
755          * @param tx トランスミッタ\r
756          */\r
757         public void ConnectToReceiver(Transmitter tx) {\r
758                 List<Receiver> receivers = device.getReceivers();\r
759                 if( receivers.size() == 0 )\r
760                         return;\r
761                 tx.setReceiver(receivers.get(0));\r
762                 fireContentsChanged(this,0,getSize());\r
763         }\r
764         /**\r
765          * 未接続のトランスミッタを、\r
766          * 引数で指定されたリストモデルの最初のレシーバに接続します。\r
767          * @param anotherModel 接続先レシーバを持つリストモデル\r
768          */\r
769         public void connectToReceiverOf(MidiConnecterListModel anotherModel) {\r
770                 if( ! txSupported() )\r
771                         return;\r
772                 if( anotherModel == null || ! anotherModel.rxSupported() )\r
773                         return;\r
774                 List<Receiver> rxList = anotherModel.device.getReceivers();\r
775                 if( rxList.isEmpty() )\r
776                         return;\r
777                 getUnconnectedTransmitter().setReceiver(rxList.get(0));\r
778         }\r
779         /**\r
780          * レシーバに未接続の最初のトランスミッタを返します。\r
781          * @return 未接続のトランスミッタ\r
782          */\r
783         public Transmitter getUnconnectedTransmitter() {\r
784                 if( ! txSupported() ) {\r
785                         return null;\r
786                 }\r
787                 List<Transmitter> txList = device.getTransmitters();\r
788                 for( Transmitter tx : txList ) {\r
789                         if( tx.getReceiver() == null )\r
790                                 return tx;\r
791                 }\r
792                 Transmitter tx;\r
793                 try {\r
794                         tx = device.getTransmitter();\r
795                 } catch( MidiUnavailableException e ) {\r
796                         e.printStackTrace();\r
797                         return null;\r
798                 }\r
799                 fireIntervalAdded(this,0,getSize());\r
800                 return tx;\r
801         }\r
802         /**\r
803          * 指定のトランスミッタを閉じます。\r
804          * <p>このリストモデルにないトランスミッタが指定された場合、無視されます。\r
805          * </p>\r
806          * @param txToClose 閉じたいトランスミッタ\r
807          */\r
808         public void closeTransmitter(Transmitter txToClose) {\r
809                 List<Transmitter> txList = device.getTransmitters();\r
810                 if( ! txList.contains(txToClose) ) {\r
811                         return;\r
812                 }\r
813                 txToClose.close();\r
814                 fireIntervalRemoved(this,0,getSize());\r
815         }\r
816         /**\r
817          * 対象MIDIデバイスを開きます。\r
818          * @throws MidiUnavailableException デバイスを開くことができない場合\r
819          */\r
820         public void openDevice() throws MidiUnavailableException {\r
821                 device.open();\r
822                 if( rxSupported() && device.getReceivers().size() == 0 ) {\r
823                         device.getReceiver();\r
824                 }\r
825         }\r
826         /**\r
827          * 対象MIDIデバイスを閉じます。\r
828          *\r
829          * <p>対象MIDIデバイスの Receiver を設定している\r
830          *  {@link Transmitter} があればすべて閉じます。\r
831          * </p>\r
832          */\r
833         public void closeDevice() {\r
834                 if( rxSupported() ) {\r
835                         Receiver rx = device.getReceivers().get(0);\r
836                         for( MidiConnecterListModel m : modelList ) {\r
837                                 if( m == this || ! m.txSupported() )\r
838                                         continue;\r
839                                 for( int i=0; i<m.getSize(); i++ ) {\r
840                                         AutoCloseable ac = m.getElementAt(i);\r
841                                         if( ! (ac instanceof Transmitter) )\r
842                                                 continue;\r
843                                         Transmitter tx = ((Transmitter)ac);\r
844                                         if( tx.getReceiver() == rx )\r
845                                                 m.closeTransmitter(tx);\r
846                                 }\r
847                         }\r
848                 }\r
849                 device.close();\r
850         }\r
851         /**\r
852          * マイクロ秒位置をリセットします。\r
853          * <p>これはMIDIデバイスからリアルタイムレコーディングを開始するタイミングで\r
854          * 必ず行う必要があります。\r
855          * (マイクロ秒位置がリセットされていないと、そのままシーケンサに記録され、\r
856          * 記録位置が大幅に後ろのほうにずれてしまいます)\r
857          * </p>\r
858          */\r
859         public void resetMicrosecondPosition() {\r
860                 if( ! txSupported() || device instanceof Sequencer )\r
861                         return;\r
862                 //\r
863                 // デバイスを閉じる前に接続相手の情報を保存\r
864                 List<Transmitter> txList = device.getTransmitters();\r
865                 List<Receiver> peerRxList = new Vector<Receiver>();\r
866                 for( Transmitter tx : txList ) {\r
867                         Receiver rx = tx.getReceiver();\r
868                         if( rx != null ) peerRxList.add(rx);\r
869                 }\r
870                 List<Transmitter> peerTxList = null;\r
871                 Receiver rx = null;\r
872                 if( rxSupported() ) {\r
873                         rx = device.getReceivers().get(0);\r
874                         peerTxList = new Vector<Transmitter>();\r
875                         for( MidiConnecterListModel m : modelList ) {\r
876                                 if( m == this || ! m.txSupported() )\r
877                                         continue;\r
878                                 for( int i=0; i<m.getSize(); i++ ) {\r
879                                         Object obj = m.getElementAt(i);\r
880                                         if( ! (obj instanceof Transmitter) )\r
881                                                 continue;\r
882                                         Transmitter tx = ((Transmitter)obj);\r
883                                         if( tx.getReceiver() == rx )\r
884                                                 peerTxList.add(tx);\r
885                                 }\r
886                         }\r
887                 }\r
888                 // デバイスを一旦閉じてまた開くことにより\r
889                 // マイクロ秒位置をリセットする\r
890                 device.close();\r
891                 try {\r
892                         device.open();\r
893                 } catch( MidiUnavailableException e ) {\r
894                         e.printStackTrace();\r
895                 }\r
896                 // 元通りに接続し直す\r
897                 for( Receiver peerRx : peerRxList ) {\r
898                         Transmitter tx = getUnconnectedTransmitter();\r
899                         if( tx == null ) continue;\r
900                         tx.setReceiver(peerRx);\r
901                 }\r
902                 if( peerTxList != null ) {\r
903                         rx = device.getReceivers().get(0);\r
904                         for( Transmitter peerTx : peerTxList ) {\r
905                                 peerTx.setReceiver(rx);\r
906                         }\r
907                 }\r
908         }\r
909 }\r
910 \r
911 /**\r
912  * MIDIデバイスフレームビュー\r
913  */\r
914 class MidiDeviceFrame extends JInternalFrame {\r
915         private static Insets ZERO_INSETS = new Insets(0,0,0,0);\r
916         /**\r
917          * デバイスの仮想MIDI端子リストビュー\r
918          */\r
919         MidiConnecterListView listView;\r
920         /**\r
921          * MIDIデバイスのモデルからフレームビューを構築します。\r
922          * @param model MIDIデバイスのTransmitter/Receiverリストモデル\r
923          */\r
924         public MidiDeviceFrame( MidiConnecterListModel model ) {\r
925                 super( null, true, true, false, false );\r
926                 //\r
927                 // タイトルの設定\r
928                 String title = model.toString();\r
929                 if( model.txSupported() ) {\r
930                         if( ! model.rxSupported() ) title = "[IN] "+title;\r
931                 }\r
932                 else {\r
933                         title = (model.rxSupported()?"[OUT] ":"[No IN/OUT] ")+title;\r
934                 }\r
935                 setTitle(title);\r
936                 listView = new MidiConnecterListView(model);\r
937                 setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);\r
938                 addInternalFrameListener(\r
939                         new InternalFrameAdapter() {\r
940                                 public void internalFrameOpened(InternalFrameEvent e) {\r
941                                         if( ! listView.getModel().getMidiDevice().isOpen() )\r
942                                                 setVisible(false);\r
943                                 }\r
944                                 public void internalFrameClosing(InternalFrameEvent e) {\r
945                                         MidiConnecterListModel m = listView.getModel();\r
946                                         m.closeDevice();\r
947                                         if( ! m.getMidiDevice().isOpen() )\r
948                                                 setVisible(false);\r
949                                 }\r
950                         }\r
951                 );\r
952                 setLayout( new BoxLayout( getContentPane(), BoxLayout.Y_AXIS ) );\r
953                 add( new JScrollPane(listView) );\r
954                 if( model.txSupported() ) {\r
955                         JPanel button_panel = new JPanel();\r
956                         button_panel.add(\r
957                                 new JButton("New Tx") {\r
958                                         {\r
959                                                 setMargin(ZERO_INSETS);\r
960                                                 addActionListener(\r
961                                                         new ActionListener() {\r
962                                                                 public void actionPerformed(ActionEvent event) {\r
963                                                                         listView.getModel().getUnconnectedTransmitter();\r
964                                                                 }\r
965                                                         }\r
966                                                 );\r
967                                         }\r
968                                 }\r
969                         );\r
970                         button_panel.add(\r
971                                 new JButton("Close Tx") {\r
972                                         {\r
973                                                 setMargin(ZERO_INSETS);\r
974                                                 addActionListener(\r
975                                                         new ActionListener() {\r
976                                                                 public void actionPerformed(ActionEvent event) {\r
977                                                                         listView.getModel().closeTransmitter(\r
978                                                                                 (Transmitter)listView.getSelectedValue()\r
979                                                                         );\r
980                                                                 }\r
981                                                         }\r
982                                                 );\r
983                                         }\r
984                                 }\r
985                         );\r
986                         add(button_panel);\r
987                 }\r
988                 setSize(250,100);\r
989         }\r
990         /**\r
991          * 指定されたインデックスが示す仮想MIDI端子リストの要素のセル範囲を返します。\r
992          *\r
993          * @param index リスト要素のインデックス\r
994          * @return セル範囲の矩形\r
995          */\r
996         public Rectangle getListCellBounds(int index) {\r
997                 Rectangle rect = listView.getCellBounds(index,index);\r
998                 if( rect == null )\r
999                         return null;\r
1000                 rect.translate(\r
1001                         getRootPane().getX() + getContentPane().getX(),\r
1002                         getRootPane().getY() + getContentPane().getY()\r
1003                 );\r
1004                 return rect;\r
1005         }\r
1006         /**\r
1007          * 仮想MIDI端子リストの指定された要素のセル範囲を返します。\r
1008          *\r
1009          * @param transciver 要素となるMIDI端子(Transmitter または Receiver)\r
1010          * @return セル範囲の矩形\r
1011          */\r
1012         public Rectangle getListCellBounds(AutoCloseable transciver) {\r
1013                 return getListCellBounds(listView.getModel().indexOf(transciver));\r
1014         }\r
1015 }\r
1016 \r
1017 /**\r
1018  * MIDIデバイス入出力タイプ\r
1019  */\r
1020 enum MidiDeviceInOutType {\r
1021         MIDI_OUT("MIDI output devices (MIDI synthesizer etc.)"),\r
1022         MIDI_IN("MIDI input devices (MIDI keyboard etc.)"),\r
1023         MIDI_IN_OUT("MIDI input/output devices (MIDI sequencer etc.)");\r
1024         private String description;\r
1025         private MidiDeviceInOutType(String description) {\r
1026                 this.description = description;\r
1027         }\r
1028         public String getDescription() {\r
1029                 return description;\r
1030         }\r
1031 }\r
1032 \r
1033 /**\r
1034  * MIDIデバイスツリーモデル\r
1035  */\r
1036 class MidiDeviceTreeModel implements TreeModel {\r
1037         List<MidiConnecterListModel> deviceModelList;\r
1038         public MidiDeviceTreeModel(List<MidiConnecterListModel> deviceModelList) {\r
1039                 this.deviceModelList = deviceModelList;\r
1040         }\r
1041         public Object getRoot() {\r
1042                 return "MIDI devices";\r
1043         }\r
1044         public Object getChild(Object parent, int index) {\r
1045                 if( parent == getRoot() ) {\r
1046                         return MidiDeviceInOutType.values()[index];\r
1047                 }\r
1048                 if( parent instanceof MidiDeviceInOutType ) {\r
1049                         MidiDeviceInOutType ioType = (MidiDeviceInOutType)parent;\r
1050                         for( MidiConnecterListModel deviceModel : deviceModelList )\r
1051                                 if( deviceModel.getMidiDeviceInOutType() == ioType ) {\r
1052                                         if( index == 0 )\r
1053                                                 return deviceModel;\r
1054                                         index--;\r
1055                                 }\r
1056                 }\r
1057                 return null;\r
1058         }\r
1059         public int getChildCount(Object parent) {\r
1060                 if( parent == getRoot() ) {\r
1061                         return MidiDeviceInOutType.values().length;\r
1062                 }\r
1063                 int childCount = 0;\r
1064                 if( parent instanceof MidiDeviceInOutType ) {\r
1065                         MidiDeviceInOutType ioType = (MidiDeviceInOutType)parent;\r
1066                         for( MidiConnecterListModel deviceModel : deviceModelList )\r
1067                                 if( deviceModel.getMidiDeviceInOutType() == ioType )\r
1068                                         childCount++;\r
1069                 }\r
1070                 return childCount;\r
1071         }\r
1072         public int getIndexOfChild(Object parent, Object child) {\r
1073                 if( parent == getRoot() ) {\r
1074                         if( child instanceof MidiDeviceInOutType ) {\r
1075                                 MidiDeviceInOutType ioType = (MidiDeviceInOutType)child;\r
1076                                 return ioType.ordinal();\r
1077                         }\r
1078                 }\r
1079                 if( parent instanceof MidiDeviceInOutType ) {\r
1080                         MidiDeviceInOutType ioType = (MidiDeviceInOutType)parent;\r
1081                         int index = 0;\r
1082                         for( MidiConnecterListModel deviceModel : deviceModelList ) {\r
1083                                 if( deviceModel.getMidiDeviceInOutType() == ioType ) {\r
1084                                         if( deviceModel == child )\r
1085                                                 return index;\r
1086                                         index++;\r
1087                                 }\r
1088                         }\r
1089                 }\r
1090                 return -1;\r
1091         }\r
1092         public boolean isLeaf(Object node) {\r
1093                 return node instanceof MidiConnecterListModel;\r
1094         }\r
1095         public void valueForPathChanged(TreePath path, Object newValue) {}\r
1096         private EventListenerList listenerList = new EventListenerList();\r
1097         public void addTreeModelListener(TreeModelListener listener) {\r
1098                 listenerList.add(TreeModelListener.class, listener);\r
1099         }\r
1100         public void removeTreeModelListener(TreeModelListener listener) {\r
1101                 listenerList.remove(TreeModelListener.class, listener);\r
1102         }\r
1103         public void fireTreeNodesChanged(\r
1104                 Object source, Object[] path, int[] childIndices, Object[] children\r
1105         ) {\r
1106                 Object[] listeners = listenerList.getListenerList();\r
1107                 for (int i = listeners.length-2; i>=0; i-=2) {\r
1108                         if (listeners[i]==TreeModelListener.class) {\r
1109                                 ((TreeModelListener)listeners[i+1]).treeNodesChanged(\r
1110                                         new TreeModelEvent(source,path,childIndices,children)\r
1111                                 );\r
1112                         }\r
1113                 }\r
1114         }\r
1115 /*\r
1116         private static final Object[] midiOutPath =\r
1117                 {rootNode, MidiDeviceInOutType.MIDI_OUT};\r
1118         private static final Object[] midiInPath =\r
1119                 {rootNode, MidiDeviceInOutType.MIDI_IN};\r
1120         public void fireDeviceStatusChanged(MidiConnecterListModel deviceModel) {\r
1121                 Object[] path = deviceModel.rxSupported() ? midiOutPath : midiInPath;\r
1122                 MidiDeviceInOutType parent = deviceModel.getMidiDeviceInOutType();\r
1123                 fireTreeNodesChanged(\r
1124                         this, path,\r
1125                         new int[]{ getIndexOfChild(parent, deviceModel) },\r
1126                         new Object[]{deviceModel}\r
1127                 );\r
1128         }\r
1129 */\r
1130 }\r
1131 \r
1132 /**\r
1133  * MIDIデバイスツリー (View)\r
1134  */\r
1135 class MidiDeviceTree extends JTree\r
1136         implements Transferable, DragGestureListener, InternalFrameListener\r
1137 {\r
1138         /**\r
1139          * MIDIデバイスツリーのビューを構築します。\r
1140          * @param model このビューにデータを提供するモデル\r
1141          */\r
1142         public MidiDeviceTree(MidiDeviceTreeModel model) {\r
1143                 super(model);\r
1144         (new DragSource()).createDefaultDragGestureRecognizer(\r
1145                 this, DnDConstants.ACTION_COPY_OR_MOVE, this\r
1146         );\r
1147         setCellRenderer(new DefaultTreeCellRenderer() {\r
1148                 @Override\r
1149                 public Component getTreeCellRendererComponent(\r
1150                         JTree tree, Object value,\r
1151                         boolean selected, boolean expanded, boolean leaf, int row,\r
1152                         boolean hasFocus\r
1153                 ) {\r
1154                         super.getTreeCellRendererComponent(\r
1155                                 tree, value, selected, expanded, leaf, row, hasFocus\r
1156                         );\r
1157                         if(leaf) {\r
1158                                         setIcon(MidiConnecterListView.MIDI_CONNECTER_ICON);\r
1159                                         setDisabledIcon(MidiConnecterListView.MIDI_CONNECTER_ICON);\r
1160                                 MidiConnecterListModel listModel = (MidiConnecterListModel)value;\r
1161                                 setEnabled( ! listModel.getMidiDevice().isOpen() );\r
1162                         }\r
1163                         return this;\r
1164                 }\r
1165         });\r
1166         }\r
1167         /**\r
1168          * このデバイスツリーからドラッグされるデータフレーバ\r
1169          */\r
1170         public static final DataFlavor\r
1171                 treeModelFlavor = new DataFlavor(TreeModel.class, "TreeModel");\r
1172         private static final DataFlavor treeModelFlavors[] = {treeModelFlavor};\r
1173         @Override\r
1174         public Object getTransferData(DataFlavor flavor) {\r
1175                 return getLastSelectedPathComponent();\r
1176         }\r
1177         @Override\r
1178         public DataFlavor[] getTransferDataFlavors() {\r
1179                 return treeModelFlavors;\r
1180         }\r
1181         @Override\r
1182         public boolean isDataFlavorSupported(DataFlavor flavor) {\r
1183                 return flavor.equals(treeModelFlavor);\r
1184         }\r
1185         @Override\r
1186         public void dragGestureRecognized(DragGestureEvent dge) {\r
1187                 int action = dge.getDragAction();\r
1188                 if( (action & DnDConstants.ACTION_COPY_OR_MOVE) != 0 ) {\r
1189                         dge.startDrag(DragSource.DefaultMoveDrop, this, null);\r
1190                 }\r
1191         }\r
1192         @Override\r
1193         public void internalFrameOpened(InternalFrameEvent e) {}\r
1194         /**\r
1195          *      MidiDeviceFrame のクローズ処理中に再描画リクエストを送ります。\r
1196          */\r
1197         @Override\r
1198         public void internalFrameClosing(InternalFrameEvent e) {\r
1199                 repaint();\r
1200         }\r
1201         @Override\r
1202         public void internalFrameClosed(InternalFrameEvent e) {}\r
1203         @Override\r
1204         public void internalFrameIconified(InternalFrameEvent e) {}\r
1205         @Override\r
1206         public void internalFrameDeiconified(InternalFrameEvent e) {}\r
1207         @Override\r
1208         public void internalFrameActivated(InternalFrameEvent e) {}\r
1209         @Override\r
1210         public void internalFrameDeactivated(InternalFrameEvent e) {}\r
1211 }\r
1212 \r
1213 /**\r
1214  * MIDIデバイスモデルリスト\r
1215  */\r
1216 class MidiDeviceModelList extends Vector<MidiConnecterListModel> {\r
1217         private Sequencer sequencer = null;\r
1218         SpeedSliderModel speedSliderModel = null;\r
1219         SequencerTimeRangeModel timeRangeModel = null;\r
1220         MidiEditor editorDialog = null;\r
1221         private MidiConnecterListModel firstMidiOutModel = null;\r
1222         /**\r
1223          * MIDIデバイスモデルリストを生成します。\r
1224          * @param vmdList 仮想MIDIデバイスのリスト\r
1225          */\r
1226         public MidiDeviceModelList(List<VirtualMidiDevice> vmdList) {\r
1227                 MidiDevice.Info[] devInfos = MidiSystem.getMidiDeviceInfo();\r
1228                 MidiConnecterListModel guiModels[] = new MidiConnecterListModel[vmdList.size()];\r
1229                 MidiConnecterListModel sequencerModel = null;\r
1230                 MidiConnecterListModel firstMidiInModel = null;\r
1231                 for( int i=0; i<vmdList.size(); i++ )\r
1232                         guiModels[i] = addMidiDevice(vmdList.get(i));\r
1233                 try {\r
1234                         sequencer = MidiSystem.getSequencer(false);\r
1235                 } catch( MidiUnavailableException e ) {\r
1236                         System.out.println(\r
1237                                 ChordHelperApplet.VersionInfo.NAME +\r
1238                                 " : MIDI sequencer unavailable"\r
1239                         );\r
1240                         e.printStackTrace();\r
1241                 }\r
1242                 sequencerModel = addMidiDevice(sequencer);\r
1243                 speedSliderModel = new SpeedSliderModel(sequencer);\r
1244                 timeRangeModel = new SequencerTimeRangeModel(this);\r
1245                 for( MidiDevice.Info info : devInfos ) {\r
1246                         MidiDevice device;\r
1247                         try {\r
1248                                 device = MidiSystem.getMidiDevice(info);\r
1249                         } catch( MidiUnavailableException e ) {\r
1250                                 e.printStackTrace(); continue;\r
1251                         }\r
1252                         if( device instanceof Sequencer ) continue;\r
1253                         if( device instanceof Synthesizer ) {\r
1254                                 try {\r
1255                                         addMidiDevice(MidiSystem.getSynthesizer());\r
1256                                 } catch( MidiUnavailableException e ) {\r
1257                                         System.out.println(\r
1258                                                 ChordHelperApplet.VersionInfo.NAME +\r
1259                                                 " : Java internal MIDI synthesizer unavailable"\r
1260                                         );\r
1261                                         e.printStackTrace();\r
1262                                 }\r
1263                                 continue;\r
1264                         }\r
1265                         MidiConnecterListModel m = addMidiDevice(device);\r
1266                         if( m.rxSupported() && firstMidiOutModel == null )\r
1267                                 firstMidiOutModel = m;\r
1268                         if( m.txSupported() && firstMidiInModel == null )\r
1269                                 firstMidiInModel = m;\r
1270                 }\r
1271                 // デバイスを開く\r
1272                 try {\r
1273                         for( MidiConnecterListModel m : guiModels )\r
1274                                 m.openDevice();\r
1275                         if( firstMidiInModel != null )\r
1276                                 firstMidiInModel.openDevice();\r
1277                         if( sequencerModel != null )\r
1278                                 sequencerModel.openDevice();\r
1279                         if( firstMidiOutModel != null )\r
1280                                 firstMidiOutModel.openDevice();\r
1281                 } catch( MidiUnavailableException ex ) {\r
1282                         ex.printStackTrace();\r
1283                 }\r
1284                 //\r
1285                 // 初期接続\r
1286                 //\r
1287                 for( MidiConnecterListModel mtx : guiModels ) {\r
1288                         for( MidiConnecterListModel mrx : guiModels )\r
1289                                 mtx.connectToReceiverOf(mrx);\r
1290                         mtx.connectToReceiverOf(sequencerModel);\r
1291                         mtx.connectToReceiverOf(firstMidiOutModel);\r
1292                 }\r
1293                 if( firstMidiInModel != null ) {\r
1294                         for( MidiConnecterListModel m : guiModels )\r
1295                                 firstMidiInModel.connectToReceiverOf(m);\r
1296                         firstMidiInModel.connectToReceiverOf(sequencerModel);\r
1297                         firstMidiInModel.connectToReceiverOf(firstMidiOutModel);\r
1298                 }\r
1299                 if( sequencerModel != null ) {\r
1300                         for( MidiConnecterListModel m : guiModels )\r
1301                                 sequencerModel.connectToReceiverOf(m);\r
1302                         sequencerModel.connectToReceiverOf(firstMidiOutModel);\r
1303                 }\r
1304         }\r
1305         /**\r
1306          * 指定のMIDIデバイスからMIDIデバイスモデルを生成して追加します。\r
1307          * @param device MIDIデバイス\r
1308          * @return 生成されたMIDIデバイスモデル\r
1309          */\r
1310         private MidiConnecterListModel addMidiDevice(MidiDevice device) {\r
1311                 MidiConnecterListModel m = new MidiConnecterListModel(device,this);\r
1312                 addElement(m);\r
1313                 return m;\r
1314         }\r
1315         /**\r
1316          * MIDIエディタを設定します。\r
1317          * <p>MIDIエディタが持つ仮想MIDIデバイスからMIDIデバイスモデルを生成し、\r
1318          * このデバイスモデルリストに追加します。\r
1319          * </p>\r
1320          * @param editorDialog MIDIエディタ\r
1321          */\r
1322         public void setMidiEditor(MidiEditor editorDialog) {\r
1323                 editorDialog.deviceManager = this;\r
1324                 MidiConnecterListModel mclm = addMidiDevice(\r
1325                         (this.editorDialog = editorDialog).virtualMidiDevice\r
1326                 );\r
1327                 try {\r
1328                         mclm.openDevice();\r
1329                 } catch( MidiUnavailableException ex ) {\r
1330                         ex.printStackTrace();\r
1331                 }\r
1332                 mclm.connectToReceiverOf(firstMidiOutModel);\r
1333         }\r
1334         /**\r
1335          * MIDIシーケンサを返します。\r
1336          * @return MIDIシーケンサ\r
1337          */\r
1338         public Sequencer getSequencer() { return sequencer; }\r
1339         /**\r
1340          * 録音可能かどうか調べます。\r
1341          * @return 録音可能ならtrue\r
1342          */\r
1343         public boolean isRecordable() {\r
1344                 return editorDialog != null && editorDialog.isRecordable();\r
1345         }\r
1346 }\r
1347 \r
1348 /**\r
1349  * MIDIデバイスダイアログ (View)\r
1350  */\r
1351 class MidiDeviceDialog extends JDialog implements ActionListener {\r
1352         MidiDeviceTree deviceTree;\r
1353         JEditorPane deviceInfoPane = new JEditorPane("text/html","<html></html>") {\r
1354                 {\r
1355                         setEditable(false);\r
1356                 }\r
1357         };\r
1358         MidiDesktopPane desktopPane;\r
1359         public MidiDeviceDialog(List<MidiConnecterListModel> deviceModelList) {\r
1360                 setTitle("MIDI device connection");\r
1361                 setBounds( 300, 300, 800, 500 );\r
1362                 desktopPane = new MidiDesktopPane(\r
1363                         deviceTree = new MidiDeviceTree(\r
1364                                 new MidiDeviceTreeModel(deviceModelList)\r
1365                         )\r
1366                 );\r
1367                 deviceTree.addTreeSelectionListener(\r
1368                         new TreeSelectionListener() {\r
1369                                 public void valueChanged(TreeSelectionEvent e) {\r
1370                                         String html = "<html><head></head><body>";\r
1371                                         Object obj = deviceTree.getLastSelectedPathComponent();\r
1372                                         if( obj instanceof MidiConnecterListModel ) {\r
1373                                                 MidiConnecterListModel deviceModel = (MidiConnecterListModel)obj;\r
1374                                                 MidiDevice device = deviceModel.getMidiDevice();\r
1375                                                 MidiDevice.Info info = device.getDeviceInfo();\r
1376                                                 html += "<b>"+deviceModel+"</b><br/>";\r
1377                                                 html += "<table border=\"1\"><tbody>";\r
1378                                                 html += "<tr><th>Version</th><td>"+info.getVersion()+"</td></tr>";\r
1379                                                 html += "<tr><th>Description</th><td>"+info.getDescription()+"</td></tr>";\r
1380                                                 html += "<tr><th>Vendor</th><td>"+info.getVendor()+"</td></tr>";\r
1381                                                 html += "</tbody></table>";\r
1382                                                 MidiDeviceFrame frame = desktopPane.getFrameOf(deviceModel);\r
1383                                                 if( frame != null ) {\r
1384                                                         try {\r
1385                                                                 frame.setSelected(true);\r
1386                                                         } catch( PropertyVetoException ex ) {\r
1387                                                                 ex.printStackTrace();\r
1388                                                         }\r
1389                                                 }\r
1390                                         }\r
1391                                         else if( obj instanceof MidiDeviceInOutType ) {\r
1392                                                 MidiDeviceInOutType ioType = (MidiDeviceInOutType)obj;\r
1393                                                 html += "<b>"+ioType+"</b><br/>";\r
1394                                                 html += ioType.getDescription()+"<br/>";\r
1395                                         }\r
1396                                         else if( obj != null ) {\r
1397                                                 html += obj.toString();\r
1398                                         }\r
1399                                         html += "</body></html>";\r
1400                                         deviceInfoPane.setText(html);\r
1401                                 }\r
1402                         }\r
1403                 );\r
1404                 JSplitPane sideSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT,\r
1405                         new JScrollPane(deviceTree),\r
1406                         new JScrollPane(deviceInfoPane)\r
1407                 ) {\r
1408                         {\r
1409                                 setDividerLocation(300);\r
1410                         }\r
1411                 };\r
1412                 add(new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, sideSplitPane, desktopPane) {\r
1413                         {\r
1414                                 setOneTouchExpandable(true);\r
1415                                 setDividerLocation(250);\r
1416                         }\r
1417                 });\r
1418         }\r
1419         @Override\r
1420         public void actionPerformed(ActionEvent event) {\r
1421                 setVisible(true);\r
1422         }\r
1423 }\r
1424 \r
1425 /**\r
1426  * 開いている MIDI デバイスを置くためのデスクトップ (View)\r
1427  */\r
1428 class MidiDesktopPane extends JDesktopPane implements DropTargetListener {\r
1429         MidiCablePane cablePane = new MidiCablePane(this);\r
1430         public MidiDesktopPane(MidiDeviceTree deviceTree) {\r
1431                 add( cablePane, JLayeredPane.PALETTE_LAYER );\r
1432                 int i=0;\r
1433                 MidiDeviceTreeModel treeModel = (MidiDeviceTreeModel)deviceTree.getModel();\r
1434                 List<MidiConnecterListModel> deviceModelList = treeModel.deviceModelList;\r
1435                 for( MidiConnecterListModel deviceModel : deviceModelList ) {\r
1436                         MidiDeviceFrame frame = new MidiDeviceFrame(deviceModel) {\r
1437                                 {\r
1438                                         addInternalFrameListener(cablePane);\r
1439                                         addComponentListener(cablePane);\r
1440                                 }\r
1441                         };\r
1442                         frame.addInternalFrameListener(deviceTree);\r
1443                         deviceModel.addListDataListener(cablePane);\r
1444                         add(frame);\r
1445                         if( deviceModel.getMidiDevice().isOpen() ) {\r
1446                                 frame.setBounds( 10+(i%2)*260, 10+i*55, 250, 100 );\r
1447                                 frame.setVisible(true);\r
1448                                 i++;\r
1449                         }\r
1450                 }\r
1451                 addComponentListener(\r
1452                         new ComponentAdapter() {\r
1453                                 public void componentResized(ComponentEvent e) {\r
1454                                         cablePane.setSize(getSize());\r
1455                                 }\r
1456                         }\r
1457                 );\r
1458                 new DropTarget( this, DnDConstants.ACTION_COPY_OR_MOVE, this, true );\r
1459         }\r
1460         public void dragEnter(DropTargetDragEvent dtde) {\r
1461                 Transferable trans = dtde.getTransferable();\r
1462                 if( trans.isDataFlavorSupported(MidiDeviceTree.treeModelFlavor) ) {\r
1463                         dtde.acceptDrag(DnDConstants.ACTION_COPY_OR_MOVE);\r
1464                 }\r
1465         }\r
1466         public void dragExit(DropTargetEvent dte) {}\r
1467         public void dragOver(DropTargetDragEvent dtde) {}\r
1468         public void dropActionChanged(DropTargetDragEvent dtde) {}\r
1469         public void drop(DropTargetDropEvent dtde) {\r
1470                 dtde.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);\r
1471                 try {\r
1472                         int action = dtde.getDropAction() ;\r
1473                         if( (action & DnDConstants.ACTION_COPY_OR_MOVE) != 0 ) {\r
1474                                 Transferable trans = dtde.getTransferable();\r
1475                                 Object data = trans.getTransferData(MidiDeviceTree.treeModelFlavor);\r
1476                                 if( data instanceof MidiConnecterListModel ) {\r
1477                                         MidiConnecterListModel deviceModel = (MidiConnecterListModel)data;\r
1478                                         try {\r
1479                                                 deviceModel.openDevice();\r
1480                                         } catch( MidiUnavailableException e ) {\r
1481                                                 //\r
1482                                                 // デバイスを開くのに失敗した場合\r
1483                                                 //\r
1484                                                 //   例えば、「Microsort MIDI マッパー」と\r
1485                                                 //   「Microsoft GS Wavetable SW Synth」を\r
1486                                                 //   同時に開こうとするとここに来る。\r
1487                                                 //\r
1488                                                 dtde.dropComplete(false);\r
1489                                                 String message = "MIDIデバイス "\r
1490                                                                 + deviceModel\r
1491                                                                 +" を開けません。\n"\r
1492                                                                 + "すでに開かれているデバイスが"\r
1493                                                                 + "このデバイスを連動して開いていないか確認してください。\n\n"\r
1494                                                                 + e.getMessage();\r
1495                                                 JOptionPane.showMessageDialog(\r
1496                                                         null, message,\r
1497                                                         "Cannot open MIDI device",\r
1498                                                         JOptionPane.ERROR_MESSAGE\r
1499                                                 );\r
1500                                                 return;\r
1501                                         }\r
1502                                         if( deviceModel.getMidiDevice().isOpen() ) {\r
1503                                                 dtde.dropComplete(true);\r
1504                                                 //\r
1505                                                 // デバイスが正常に開かれたことを確認できたら\r
1506                                                 // ドロップした場所へフレームを配置して可視化する。\r
1507                                                 //\r
1508                                                 JInternalFrame frame = getFrameOf(deviceModel);\r
1509                                                 if( frame != null ) {\r
1510                                                         Point loc = dtde.getLocation();\r
1511                                                         loc.translate( -frame.getWidth()/2, 0 );\r
1512                                                         frame.setLocation(loc);\r
1513                                                         frame.setVisible(true);\r
1514                                                 }\r
1515                                                 return;\r
1516                                         }\r
1517                                 }\r
1518                         }\r
1519                 }\r
1520                 catch (Exception ex) {\r
1521                         ex.printStackTrace();\r
1522                 }\r
1523                 dtde.dropComplete(false);\r
1524         }\r
1525         /**\r
1526          * 指定されたMIDIデバイスモデルに対するMIDIデバイスフレームを返します。\r
1527          *\r
1528          * @param deviceModel MIDIデバイスモデル\r
1529          * @return 対応するMIDIデバイスフレーム(ない場合 null)\r
1530          */\r
1531         public MidiDeviceFrame getFrameOf(MidiConnecterListModel deviceModel) {\r
1532                 JInternalFrame[] frames = getAllFramesInLayer(JLayeredPane.DEFAULT_LAYER);\r
1533                 for( JInternalFrame frame : frames ) {\r
1534                         if( frame instanceof MidiDeviceFrame ) {\r
1535                                 MidiDeviceFrame deviceFrame = (MidiDeviceFrame)frame;\r
1536                                 if( deviceFrame.listView.getModel() == deviceModel )\r
1537                                         return deviceFrame;\r
1538                         }\r
1539                 }\r
1540                 return null;\r
1541         }\r
1542 }\r
1543 \r
1544 /**\r
1545  * MIDI ケーブル描画面\r
1546  */\r
1547 class MidiCablePane extends JComponent\r
1548         implements ListDataListener, ComponentListener, InternalFrameListener\r
1549 {\r
1550         private JDesktopPane desktopPane;\r
1551         //private JTree tree;\r
1552         public MidiCablePane(JDesktopPane desktopPane) {\r
1553                 this.desktopPane = desktopPane;\r
1554                 setOpaque(false);\r
1555                 setVisible(true);\r
1556         }\r
1557         //\r
1558         // MidiDeviceFrame の開閉を検出\r
1559         public void internalFrameActivated(InternalFrameEvent e) {}\r
1560         public void internalFrameClosed(InternalFrameEvent e) { repaint(); }\r
1561         public void internalFrameClosing(InternalFrameEvent e) {\r
1562                 JInternalFrame frame = e.getInternalFrame();\r
1563                 if( ! (frame instanceof MidiDeviceFrame) )\r
1564                         return;\r
1565                 MidiDeviceFrame devFrame = (MidiDeviceFrame)frame;\r
1566                 MidiConnecterListModel devModel = devFrame.listView.getModel();\r
1567                 if( ! devModel.rxSupported() )\r
1568                         return;\r
1569                 colorMap.remove(devModel.getMidiDevice().getReceivers().get(0));\r
1570                 repaint();\r
1571         }\r
1572         public void internalFrameDeactivated(InternalFrameEvent e) { repaint(); }\r
1573         public void internalFrameDeiconified(InternalFrameEvent e) {}\r
1574         public void internalFrameIconified(InternalFrameEvent e) {}\r
1575         public void internalFrameOpened(InternalFrameEvent e) {}\r
1576         //\r
1577         // ウィンドウオペレーションの検出\r
1578         public void componentHidden(ComponentEvent e) {}\r
1579         public void componentMoved(ComponentEvent e) { repaint(); }\r
1580         public void componentResized(ComponentEvent e) { repaint(); }\r
1581         public void componentShown(ComponentEvent e) {}\r
1582         //\r
1583         // MidiConnecterListModel における Transmitter リストの更新を検出\r
1584         public void contentsChanged(ListDataEvent e) { repaint(); }\r
1585         public void intervalAdded(ListDataEvent e) { repaint(); }\r
1586         public void intervalRemoved(ListDataEvent e) { repaint(); }\r
1587         //\r
1588         // ケーブル描画用\r
1589         private static final int ARROW_SIZE = 15;\r
1590         private static final double ARROW_ANGLE = Math.PI / 6.0;\r
1591         private static final Stroke CABLE_STROKE = new BasicStroke(\r
1592                 3, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND\r
1593         );\r
1594         private static final Color[] CABLE_COLORS = {\r
1595                 new Color(255,0,0,191),\r
1596                 new Color(0,255,0,191),\r
1597                 new Color(0,0,255,191),\r
1598                 new Color(191,191,0,191),\r
1599                 new Color(0,191,191,191),\r
1600                 new Color(191,0,191,191),\r
1601         };\r
1602         private int nextColorIndex = 0;\r
1603         private Hashtable<Receiver,Color> colorMap = new Hashtable<>();\r
1604         public void paint(Graphics g) {\r
1605                 super.paint(g);\r
1606                 Graphics2D g2 = (Graphics2D)g;\r
1607                 g2.setStroke(CABLE_STROKE);\r
1608                 JInternalFrame[] frames =\r
1609                         desktopPane.getAllFramesInLayer(JLayeredPane.DEFAULT_LAYER);\r
1610                 for( JInternalFrame frame : frames ) {\r
1611                         if( ! (frame instanceof MidiDeviceFrame) )\r
1612                                 continue;\r
1613                         MidiDeviceFrame txDeviceFrame = (MidiDeviceFrame)frame;\r
1614                         List<Transmitter> txList = txDeviceFrame.listView.getModel().getMidiDevice().getTransmitters();\r
1615                         for( Transmitter tx : txList ) {\r
1616                                 //\r
1617                                 // 送信端子から接続されている受信端子の存在を確認\r
1618                                 Receiver rx = tx.getReceiver();\r
1619                                 if( rx == null )\r
1620                                         continue;\r
1621                                 //\r
1622                                 // 送信端子の矩形を特定\r
1623                                 Rectangle txRect = txDeviceFrame.getListCellBounds(tx);\r
1624                                 if( txRect == null )\r
1625                                         continue;\r
1626                                 //\r
1627                                 // 受信端子のあるMIDIデバイスを探す\r
1628                                 Rectangle rxRect = null;\r
1629                                 for( JInternalFrame anotherFrame : frames ) {\r
1630                                         if( ! (anotherFrame instanceof MidiDeviceFrame) )\r
1631                                                 continue;\r
1632                                         //\r
1633                                         // 受信端子の矩形を探す\r
1634                                         MidiDeviceFrame rxDeviceFrame = (MidiDeviceFrame)anotherFrame;\r
1635                                         if((rxRect = rxDeviceFrame.getListCellBounds(rx)) == null)\r
1636                                                 continue;\r
1637                                         rxRect.translate(rxDeviceFrame.getX(), rxDeviceFrame.getY());\r
1638                                         break;\r
1639                                 }\r
1640                                 if( rxRect == null )\r
1641                                         continue;\r
1642                                 txRect.translate(txDeviceFrame.getX(), txDeviceFrame.getY());\r
1643                                 //\r
1644                                 // 色を探す\r
1645                                 Color color = colorMap.get(rx);\r
1646                                 if( color == null ) {\r
1647                                         colorMap.put(rx, color=CABLE_COLORS[nextColorIndex++]);\r
1648                                         if( nextColorIndex >= CABLE_COLORS.length )\r
1649                                                 nextColorIndex = 0;\r
1650                                 }\r
1651                                 g2.setColor(color);\r
1652                                 //\r
1653                                 // 始点\r
1654                                 int fromX = txRect.x;\r
1655                                 int fromY = txRect.y;\r
1656                                 int d = txRect.height - 2;\r
1657                                 g2.fillOval( fromX, fromY, d, d );\r
1658                                 // 線\r
1659                                 int halfHeight = d / 2;\r
1660                                 fromX += halfHeight;\r
1661                                 fromY += halfHeight;\r
1662                                 halfHeight = (rxRect.height / 2) - 1;\r
1663                                 int toX = rxRect.x + halfHeight;\r
1664                                 int toY = rxRect.y + halfHeight;\r
1665                                 g2.drawLine( fromX, fromY, toX, toY );\r
1666                                 // 矢印\r
1667                                 double lineAngle = Math.atan2(\r
1668                                         (double)(toY - fromY),\r
1669                                         (double)(toX - fromX)\r
1670                                 );\r
1671                                 double arrowAngle = lineAngle-ARROW_ANGLE;\r
1672                                 g2.drawLine(\r
1673                                         toX, toY,\r
1674                                         toX - (int)(ARROW_SIZE * Math.cos(arrowAngle)),\r
1675                                         toY - (int)(ARROW_SIZE * Math.sin(arrowAngle))\r
1676                                 );\r
1677                                 arrowAngle = lineAngle+ARROW_ANGLE;\r
1678                                 g2.drawLine(\r
1679                                         toX, toY,\r
1680                                         toX - (int)(ARROW_SIZE * Math.cos(arrowAngle)),\r
1681                                         toY - (int)(ARROW_SIZE * Math.sin(arrowAngle))\r
1682                                 );\r
1683                         }\r
1684                 }\r
1685         }\r
1686 }\r
1687 \r