OSDN Git Service

MIDI Editor の表のセル幅調整
[midichordhelper/MIDIChordHelper.git] / src / ChordHelperApplet.java
1 import java.awt.Color;\r
2 import java.awt.Component;\r
3 import java.awt.Desktop;\r
4 import java.awt.Dimension;\r
5 import java.awt.Graphics;\r
6 import java.awt.Image;\r
7 import java.awt.Insets;\r
8 import java.awt.dnd.DnDConstants;\r
9 import java.awt.dnd.DropTarget;\r
10 import java.awt.event.ActionEvent;\r
11 import java.awt.event.ActionListener;\r
12 import java.awt.event.InputEvent;\r
13 import java.awt.event.ItemEvent;\r
14 import java.awt.event.ItemListener;\r
15 import java.awt.event.MouseAdapter;\r
16 import java.awt.event.MouseEvent;\r
17 import java.awt.event.MouseListener;\r
18 import java.io.IOException;\r
19 import java.io.UnsupportedEncodingException;\r
20 import java.net.URI;\r
21 import java.net.URISyntaxException;\r
22 import java.net.URL;\r
23 import java.security.AccessControlException;\r
24 import java.util.Arrays;\r
25 import java.util.Vector;\r
26 \r
27 import javax.sound.midi.InvalidMidiDataException;\r
28 import javax.sound.midi.MetaEventListener;\r
29 import javax.sound.midi.MetaMessage;\r
30 import javax.sound.midi.Sequence;\r
31 import javax.sound.midi.Sequencer;\r
32 import javax.swing.Box;\r
33 import javax.swing.BoxLayout;\r
34 import javax.swing.ButtonGroup;\r
35 import javax.swing.ImageIcon;\r
36 import javax.swing.JApplet;\r
37 import javax.swing.JButton;\r
38 import javax.swing.JCheckBoxMenuItem;\r
39 import javax.swing.JComponent;\r
40 import javax.swing.JEditorPane;\r
41 import javax.swing.JLabel;\r
42 import javax.swing.JOptionPane;\r
43 import javax.swing.JPanel;\r
44 import javax.swing.JPopupMenu;\r
45 import javax.swing.JRadioButtonMenuItem;\r
46 import javax.swing.JSlider;\r
47 import javax.swing.JSplitPane;\r
48 import javax.swing.JTextField;\r
49 import javax.swing.JToggleButton;\r
50 import javax.swing.SwingUtilities;\r
51 import javax.swing.border.Border;\r
52 import javax.swing.event.ChangeEvent;\r
53 import javax.swing.event.ChangeListener;\r
54 import javax.swing.event.HyperlinkEvent;\r
55 import javax.swing.event.HyperlinkListener;\r
56 import javax.swing.event.PopupMenuEvent;\r
57 import javax.swing.event.PopupMenuListener;\r
58 \r
59 /**\r
60  * MIDI Chord Helper - Circle-of-fifth oriented chord pad\r
61  * (アプレットクラス)\r
62  *\r
63  *      @auther\r
64  *              Copyright (C) 2004-2013 @きよし - Akiyoshi Kamide\r
65  *              http://www.yk.rim.or.jp/~kamide/music/chordhelper/\r
66  */\r
67 public class ChordHelperApplet extends JApplet {\r
68         /////////////////////////////////////////////////////////////////////\r
69         //\r
70         // JavaScript などからの呼び出しインターフェース\r
71         //\r
72         /////////////////////////////////////////////////////////////////////\r
73         /**\r
74          * 未保存の修正済み MIDI ファイルがあるかどうか調べます。\r
75          * @return 未保存の修正済み MIDI ファイルがあれば true\r
76          */\r
77         public boolean isModified() {\r
78                 return editorDialog.sequenceListTable.getModel().isModified();\r
79         }\r
80         /**\r
81          * 指定された小節数の曲を、乱数で自動作曲してプレイリストへ追加します。\r
82          * @param measureLength 小節数\r
83          * @return 追加先のインデックス値(0から始まる)。追加できなかったときは -1\r
84          */\r
85         public int addRandomSongToPlaylist(int measureLength) {\r
86                 editorDialog.newSequenceDialog.setRandomChordProgression(measureLength);\r
87                 Sequence sequence = editorDialog.newSequenceDialog.getMidiSequence();\r
88                 return editorDialog.sequenceListTable.getModel().addSequenceAndPlay(sequence);\r
89         }\r
90         /**\r
91          * URLで指定されたMIDIファイルをプレイリストへ追加します。\r
92          *\r
93          * <p>URL の最後の / より後ろの部分がファイル名として取り込まれます。\r
94          * 指定できる MIDI ファイルには、param タグの midi_file パラメータと同様の制限があります。\r
95          * </p>\r
96          * @param midiFileUrl 追加するMIDIファイルのURL\r
97          * @return 追加先のインデックス値(0から始まる)。追加できなかったときは -1\r
98          */\r
99         public int addToPlaylist(String midiFileUrl) {\r
100                 try {\r
101                         return editorDialog.sequenceListTable.getModel().addSequenceFromURL(midiFileUrl);\r
102                 } catch( URISyntaxException|IOException|InvalidMidiDataException e ) {\r
103                         editorDialog.showWarning(e.getMessage());\r
104                 } catch( AccessControlException e ) {\r
105                         e.printStackTrace();\r
106                         editorDialog.showError(e.getMessage());\r
107                 }\r
108                 return -1;\r
109         }\r
110         /**\r
111          * Base64 エンコードされた MIDI ファイルをプレイリストへ追加します。\r
112          *\r
113          * @param base64EncodedText Base64エンコードされたMIDIファイル\r
114          * @return 追加先のインデックス値(0から始まる)。追加できなかったときは -1\r
115          */\r
116         public int addToPlaylistBase64(String base64EncodedText) {\r
117                 return addToPlaylistBase64(base64EncodedText, null);\r
118         }\r
119         /**\r
120          * ファイル名を指定して、\r
121          * Base64エンコードされたMIDIファイルをプレイリストへ追加します。\r
122          *\r
123          * @param base64EncodedText Base64エンコードされたMIDIファイル\r
124          * @param filename ディレクトリ名を除いたファイル名\r
125          * @return 追加先のインデックス値(0から始まる)。追加できなかったときは -1\r
126          */\r
127         public int addToPlaylistBase64(String base64EncodedText, String filename) {\r
128                 Base64Dialog d = editorDialog.base64Dialog;\r
129                 d.setBase64Data(base64EncodedText);\r
130                 try {\r
131                         return editorDialog.sequenceListTable.getModel().addSequence(d.getMIDIData(), filename);\r
132                 } catch (IOException | InvalidMidiDataException e) {\r
133                         e.printStackTrace();\r
134                         editorDialog.showWarning(e.getMessage());\r
135                         return -1;\r
136                 }\r
137         }\r
138         /**\r
139          * プレイリスト上で現在選択されているMIDIシーケンスを、\r
140          * シーケンサへロードして再生します。\r
141          */\r
142         public void play() {\r
143                 play(editorDialog.sequenceListTable.getModel().sequenceListSelectionModel.getMinSelectionIndex());\r
144         }\r
145         /**\r
146          * 指定されたインデックス値が示すプレイリスト上のMIDIシーケンスを、\r
147          * シーケンサへロードして再生します。\r
148          * @param index インデックス値(0から始まる)\r
149          */\r
150         public void play(int index) {\r
151                 editorDialog.sequenceListTable.getModel().loadToSequencer(index);\r
152                 deviceModelList.sequencerModel.start();\r
153         }\r
154         /**\r
155          * シーケンサが実行中かどうかを返します。\r
156          * {@link Sequencer#isRunning()} の戻り値をそのまま返します。\r
157          *\r
158          * @return 実行中のときtrue\r
159          */\r
160         public boolean isRunning() {\r
161                 return deviceModelList.sequencerModel.getSequencer().isRunning();\r
162         }\r
163         /**\r
164          * シーケンサが再生中かどうかを返します。\r
165          * @return 再生中のときtrue\r
166          */\r
167         public boolean isPlaying() { return isRunning(); }\r
168         /**\r
169          * 現在シーケンサにロードされているMIDIデータを\r
170          * Base64テキストに変換した結果を返します。\r
171          * @return MIDIデータをBase64テキストに変換した結果\r
172          */\r
173         public String getMidiDataBase64() {\r
174                 SequenceTrackListTableModel sequenceModel =\r
175                         editorDialog.sequenceListTable.getModel().sequencerModel.getSequenceTableModel();\r
176                 editorDialog.base64Dialog.setMIDIData(sequenceModel.getMIDIdata());\r
177                 return editorDialog.base64Dialog.getBase64Data();\r
178         }\r
179         /**\r
180          * 現在シーケンサにロードされているMIDIファイルのファイル名を返します。\r
181          * @return MIDIファイル名(設定されていないときは空文字列)\r
182          */\r
183         public String getMidiFilename() {\r
184                 SequenceTrackListTableModel seq_model = deviceModelList.sequencerModel.getSequenceTableModel();\r
185                 if( seq_model == null ) return null;\r
186                 String fn = seq_model.getFilename();\r
187                 return fn == null ? "" : fn ;\r
188         }\r
189         /**\r
190          * オクターブ位置を設定します。\r
191          * @param octavePosition オクターブ位置(デフォルト:4)\r
192          */\r
193         public void setOctavePosition(int octavePosition) {\r
194                 keyboardPanel.keyboardCenterPanel.keyboard.octaveRangeModel.setValue(octavePosition);\r
195         }\r
196         /**\r
197          * 操作対象のMIDIチャンネルを変更します。\r
198          * @param ch チャンネル番号 - 1(チャンネル1のとき0、デフォルトは0)\r
199          */\r
200         public void setChannel(int ch) {\r
201                 keyboardPanel.keyboardCenterPanel.keyboard.midiChComboboxModel.setSelectedChannel(ch);\r
202         }\r
203         /**\r
204          * 操作対象のMIDIチャンネルを返します。\r
205          * @return 操作対象のMIDIチャンネル\r
206          */\r
207         public int getChannel() {\r
208                 return keyboardPanel.keyboardCenterPanel.keyboard.midiChComboboxModel.getSelectedChannel();\r
209         }\r
210         /**\r
211          * 操作対象のMIDIチャンネルに対してプログラム(音色)を設定します。\r
212          * @param program 音色(0~127:General MIDI に基づく)\r
213          */\r
214         public void programChange(int program) {\r
215                 keyboardPanel.keyboardCenterPanel.keyboard.getSelectedChannel().programChange(program);\r
216         }\r
217         /**\r
218          * 操作対象のMIDIチャンネルに対してプログラム(音色)を設定します。\r
219          * 内部的には {@link #programChange(int)} を呼び出しているだけです。\r
220          * @param program 音色(0~127:General MIDI に基づく)\r
221          */\r
222         public void setProgram(int program) { programChange(program); }\r
223         /**\r
224          * 自動転回モードを変更します。初期値は true です。\r
225          * @param isAuto true:自動転回を行う false:自動転回を行わない\r
226          */\r
227         public void setAutoInversion(boolean isAuto) {\r
228                 inversionOmissionButton.setAutoInversion(isAuto);\r
229         }\r
230         /**\r
231          * 省略したい構成音を指定します。\r
232          * @param index\r
233          * <ul>\r
234          * <li>-1:省略しない(デフォルト)</li>\r
235          * <li>0:ルート音を省略</li>\r
236          * <li>1:三度を省略</li>\r
237          * <li>2:五度を省略</li>\r
238          * </ul>\r
239          */\r
240         public void setOmissionNoteIndex(int index) {\r
241                 inversionOmissionButton.setOmissionNoteIndex(index);\r
242         }\r
243         /**\r
244          * コードダイアグラムの表示・非表示を切り替えます。\r
245          * @param isVisible 表示するときtrue\r
246          */\r
247         public void setChordDiagramVisible(boolean isVisible) {\r
248                 keyboardSplitPane.resetToPreferredSizes();\r
249                 if( ! isVisible )\r
250                         keyboardSplitPane.setDividerLocation((double)1.0);\r
251         }\r
252         /**\r
253          * コードダイヤグラムをギターモードに変更します。\r
254          * 初期状態ではウクレレモードになっています。\r
255          */\r
256         public void setChordDiagramForGuitar() {\r
257                 chordDiagram.setTargetInstrument(ChordDiagram.TargetInstrument.Guitar);\r
258         }\r
259         /**\r
260          * ダークモード(暗い表示)と明るい表示とを切り替えます。\r
261          * @param isDark ダークモードのときtrue、明るい表示のときfalse(デフォルト)\r
262          */\r
263         public void setDarkMode(boolean isDark) {\r
264                 darkModeToggleButton.setSelected(isDark);\r
265         }\r
266         /**\r
267          * バージョン情報\r
268          */\r
269         public static class VersionInfo {\r
270                 public static final String      NAME = "MIDI Chord Helper";\r
271                 public static final String      VERSION = "Ver.20131206.1";\r
272                 public static final String      COPYRIGHT = "Copyright (C) 2004-2013";\r
273                 public static final String      AUTHER = "@きよし - Akiyoshi Kamide";\r
274                 public static final String      URL = "http://www.yk.rim.or.jp/~kamide/music/chordhelper/";\r
275                 public static String getInfo() {\r
276                         return NAME + " " + VERSION + " " + COPYRIGHT + " " + AUTHER + " " + URL;\r
277                 }\r
278         }\r
279         @Override\r
280         public String getAppletInfo() {\r
281                 return VersionInfo.getInfo();\r
282         }\r
283         private class AboutMessagePane extends JEditorPane implements ActionListener {\r
284                 URI uri = null;\r
285                 public AboutMessagePane() { this(true); }\r
286                 public AboutMessagePane(boolean link_enabled) {\r
287                         super( "text/html", "" );\r
288                         String link_string, tooltip = null;\r
289                         if( link_enabled && Desktop.isDesktopSupported() ) {\r
290                                 tooltip = "Click this URL to open with your web browser - URLをクリックしてWebブラウザで開く";\r
291                                 link_string =\r
292                                         "<a href=\"" + VersionInfo.URL + "\" title=\"" +\r
293                                         tooltip + "\">" + VersionInfo.URL + "</a>" ;\r
294                         }\r
295                         else {\r
296                                 link_enabled = false; link_string = VersionInfo.URL;\r
297                         }\r
298                         setText(\r
299                                 "<html><center><font size=\"+1\">" + VersionInfo.NAME + "</font>  " +\r
300                                                 VersionInfo.VERSION + "<br/><br/>" +\r
301                                                 VersionInfo.COPYRIGHT + " " + VersionInfo.AUTHER + "<br/>" +\r
302                                                 link_string + "</center></html>"\r
303                         );\r
304                         setToolTipText(tooltip);\r
305                         setOpaque(false);\r
306                         putClientProperty(JEditorPane.HONOR_DISPLAY_PROPERTIES, Boolean.TRUE);\r
307                         setEditable(false);\r
308                         //\r
309                         // メッセージ内の <a href=""> ~ </a> によるリンクを\r
310                         // 実際に機能させる(ブラウザで表示されるようにする)ための設定\r
311                         //\r
312                         if( ! link_enabled ) return;\r
313                         try {\r
314                                 uri = new URI(VersionInfo.URL);\r
315                         }catch( URISyntaxException use ) {\r
316                                 use.printStackTrace();\r
317                                 return;\r
318                         }\r
319                         addHyperlinkListener(new HyperlinkListener() {\r
320                                 public void hyperlinkUpdate(HyperlinkEvent e) {\r
321                                         if(e.getEventType()==HyperlinkEvent.EventType.ACTIVATED) {\r
322                                                 try{\r
323                                                         Desktop.getDesktop().browse(uri);\r
324                                                 }catch(IOException ioe) {\r
325                                                         ioe.printStackTrace();\r
326                                                 }\r
327                                         }\r
328                                 }\r
329                         });\r
330                 }\r
331                 @Override\r
332                 public void actionPerformed(ActionEvent e) {\r
333                         JOptionPane.showMessageDialog(\r
334                                 null, this, "Version info",\r
335                                 JOptionPane.INFORMATION_MESSAGE, imageIcon\r
336                         );\r
337                 }\r
338         }\r
339         // 終了してよいか確認する\r
340         public boolean isConfirmedToExit() {\r
341                 return ! isModified() || JOptionPane.showConfirmDialog(\r
342                         this,\r
343                         "MIDI file not saved, exit anyway ?\n保存されていないMIDIファイルがありますが、終了してよろしいですか?",\r
344                         VersionInfo.NAME,\r
345                         JOptionPane.YES_NO_OPTION,\r
346                         JOptionPane.WARNING_MESSAGE\r
347                 ) == JOptionPane.YES_OPTION ;\r
348         }\r
349         // アプリケーションのアイコンイメージ\r
350         public ImageIcon imageIcon;\r
351         // ボタンの余白を詰めたいときは setMargin() の引数にこれを指定する\r
352         public static final Insets      ZERO_INSETS = new Insets(0,0,0,0);\r
353         //\r
354         private JPanel keyboardSequencerPanel;\r
355         private JPanel chordGuide;\r
356         private Color rootPaneDefaultBgcolor;\r
357         private Color lyricDisplayDefaultBgcolor;\r
358         private Border lyricDisplayDefaultBorder;\r
359         private JSplitPane mainSplitPane;\r
360         private JSplitPane keyboardSplitPane;\r
361         private ChordButtonLabel enterButtonLabel;\r
362         private ChordTextField  lyricDisplay;\r
363         private MidiKeyboardPanel keyboardPanel;\r
364         ChordMatrix chordMatrix;\r
365         private InversionAndOmissionLabel inversionOmissionButton;\r
366         private JToggleButton darkModeToggleButton;\r
367         MidiDeviceModelList     deviceModelList;\r
368         MidiDeviceDialog midiConnectionDialog;\r
369         MidiEditor editorDialog;\r
370         ChordDiagram chordDiagram;\r
371         TempoSelecter tempoSelecter;\r
372         TimeSignatureSelecter timesigSelecter;\r
373         KeySignatureLabel keysigLabel;\r
374         JLabel songTitleLabel = new JLabel();\r
375         //\r
376         // あの楽器\r
377         AnoGakkiLayeredPane anoGakkiLayeredPane;\r
378         JToggleButton anoGakkiToggleButton;\r
379 \r
380         public void init() {\r
381                 String imageIconPath = "images/midichordhelper.png";\r
382                 URL imageIconUrl = getClass().getResource(imageIconPath);\r
383                 if( imageIconUrl == null ) {\r
384                         // System.out.println("icon "+imageIconPath+" not found");\r
385                         imageIcon = null;\r
386                 }\r
387                 else {\r
388                         imageIcon = new ImageIcon(imageIconUrl);\r
389                 }\r
390                 Image iconImage = (imageIcon == null) ? null : imageIcon.getImage();\r
391                 rootPaneDefaultBgcolor = getContentPane().getBackground();\r
392                 chordMatrix = new ChordMatrix() {{\r
393                         addChordMatrixListener(new ChordMatrixListener(){\r
394                                 public void keySignatureChanged() {\r
395                                         Music.Key capoKey = getKeySignatureCapo();\r
396                                         keyboardPanel.keySelecter.setKey(capoKey);\r
397                                         keyboardPanel.keyboardCenterPanel.keyboard.setKeySignature(capoKey);\r
398                                 }\r
399                                 public void chordChanged() { chordOn(); }\r
400                         });\r
401                 }};\r
402                 chordMatrix.capoSelecter.checkbox.addItemListener(\r
403                         new ItemListener() {\r
404                                 public void itemStateChanged(ItemEvent e) {\r
405                                         chordOn();\r
406                                         keyboardPanel.keyboardCenterPanel.keyboard.chordDisplay.setNote(-1);\r
407                                         chordDiagram.clear();\r
408                                 }\r
409                         }\r
410                 );\r
411                 chordMatrix.capoSelecter.valueSelecter.addActionListener(\r
412                         new ActionListener() {\r
413                                 public void actionPerformed(ActionEvent e) {\r
414                                         chordOn();\r
415                                         keyboardPanel.keyboardCenterPanel.keyboard.chordDisplay.setNote(-1);\r
416                                         chordDiagram.clear();\r
417                                 }\r
418                         }\r
419                 );\r
420                 keyboardPanel = new MidiKeyboardPanel(chordMatrix) {{\r
421                         keyboardCenterPanel.keyboard.addPianoKeyboardListener(\r
422                                 new PianoKeyboardAdapter() {\r
423                                         public void pianoKeyPressed(int n, InputEvent e) {\r
424                                                 chordDiagram.clear();\r
425                                         }\r
426                                 }\r
427                         );\r
428                         keySelecter.keysigCombobox.addActionListener(\r
429                                 new ActionListener() {\r
430                                         public void actionPerformed(ActionEvent e) {\r
431                                                 Music.Key key = keySelecter.getKey();\r
432                                                 key.transpose( - chordMatrix.capoSelecter.getCapo() );\r
433                                                 chordMatrix.setKeySignature(key);\r
434                                         }\r
435                                 }\r
436                         );\r
437                         keyboardCenterPanel.keyboard.setPreferredSize(new Dimension(571, 80));\r
438                 }};\r
439                 deviceModelList = new MidiDeviceModelList(\r
440                         new Vector<VirtualMidiDevice>() {\r
441                                 {\r
442                                         add(keyboardPanel.keyboardCenterPanel.keyboard.midiDevice);\r
443                                 }\r
444                         }\r
445                 );\r
446                 editorDialog = new MidiEditor(deviceModelList.sequencerModel);\r
447                 editorDialog.setIconImage(iconImage);\r
448                 deviceModelList.setMidiEditor(editorDialog);\r
449                 new DropTarget(this, DnDConstants.ACTION_COPY_OR_MOVE, editorDialog, true);\r
450                 keyboardPanel.eventDialog = editorDialog.eventDialog;\r
451                 midiConnectionDialog = new MidiDeviceDialog(deviceModelList);\r
452                 midiConnectionDialog.setIconImage(iconImage);\r
453                 lyricDisplay = new ChordTextField() {\r
454                         {\r
455                                 deviceModelList.sequencerModel.getSequencer().addMetaEventListener(this);\r
456                                 addActionListener(\r
457                                         new ActionListener() {\r
458                                                 public void actionPerformed(ActionEvent event) {\r
459                                                         chordMatrix.setSelectedChord(\r
460                                                                 event.getActionCommand().trim().split("[ \t\r\n]")[0]\r
461                                                         );\r
462                                                 }\r
463                                         }\r
464                                 );\r
465                         }\r
466                 };\r
467                 lyricDisplayDefaultBorder = lyricDisplay.getBorder();\r
468                 lyricDisplayDefaultBgcolor = lyricDisplay.getBackground();\r
469                 //\r
470                 // Chord diagram\r
471                 //\r
472                 chordDiagram = new ChordDiagram(this);\r
473                 //\r
474                 // MetaEvent listeners\r
475                 //\r
476                 tempoSelecter = new TempoSelecter() {{\r
477                         setEditable(false);\r
478                         deviceModelList.sequencerModel.getSequencer().addMetaEventListener(this);\r
479                 }};\r
480                 timesigSelecter = new TimeSignatureSelecter() {{\r
481                         setEditable(false);\r
482                         deviceModelList.sequencerModel.getSequencer().addMetaEventListener(this);\r
483                 }};\r
484                 keysigLabel = new KeySignatureLabel() {{\r
485                         addMouseListener(new MouseAdapter() {\r
486                                 public void mousePressed(MouseEvent e) {\r
487                                         chordMatrix.setKeySignature(getKey());\r
488                                 }\r
489                         });\r
490                 }};\r
491                 deviceModelList.sequencerModel.getSequencer().addMetaEventListener(\r
492                         new MetaEventListener() {\r
493                                 class SetKeySignatureRunnable implements Runnable {\r
494                                         Music.Key key;\r
495                                         public SetKeySignatureRunnable(Music.Key key) {\r
496                                                 this.key = key;\r
497                                         }\r
498                                         @Override\r
499                                         public void run() { setKeySignature(key); }\r
500                                 }\r
501                                 @Override\r
502                                 public void meta(MetaMessage msg) {\r
503                                         switch(msg.getType()) {\r
504                                         case 0x59: // Key signature (2 bytes) : 調号\r
505                                                 Music.Key key = new Music.Key(msg.getData());\r
506                                                 if( ! SwingUtilities.isEventDispatchThread() ) {\r
507                                                         SwingUtilities.invokeLater(\r
508                                                                 new SetKeySignatureRunnable(key)\r
509                                                         );\r
510                                                 }\r
511                                                 setKeySignature(key);\r
512                                                 break;\r
513                                         }\r
514                                 }\r
515                                 private void setKeySignature(Music.Key key) {\r
516                                         keysigLabel.setKeySignature(key);\r
517                                         chordMatrix.setKeySignature(key);\r
518                                 }\r
519                         }\r
520                 );\r
521                 //シーケンサーの時間スライダーの値が変わったときのリスナーを登録\r
522                 deviceModelList.sequencerModel.addChangeListener(new ChangeListener() {\r
523                         @Override\r
524                         public void stateChanged(ChangeEvent e) {\r
525                                 SequenceTrackListTableModel sequenceTableModel = deviceModelList.sequencerModel.getSequenceTableModel();\r
526                                 int loadedSequenceIndex = editorDialog.sequenceListTable.getModel().indexOfSequenceOnSequencer();\r
527                                 songTitleLabel.setText(\r
528                                         "<html>"+(\r
529                                                 loadedSequenceIndex < 0 ? "[No MIDI file loaded]" :\r
530                                                 "MIDI file " + loadedSequenceIndex + ": " + (\r
531                                                         sequenceTableModel == null ||\r
532                                                         sequenceTableModel.toString() == null ||\r
533                                                         sequenceTableModel.toString().isEmpty() ?\r
534                                                         "[Untitled]" :\r
535                                                         "<font color=maroon>"+sequenceTableModel+"</font>"\r
536                                                 )\r
537                                         )+"</html>"\r
538                                 );\r
539                                 Sequencer sequencer = deviceModelList.sequencerModel.getSequencer();\r
540                                 chordMatrix.setPlaying(sequencer.isRunning());\r
541                                 if( sequenceTableModel != null ) {\r
542                                         SequenceTickIndex tickIndex = sequenceTableModel.getSequenceTickIndex();\r
543                                         long tickPos = sequencer.getTickPosition();\r
544                                         tickIndex.tickToMeasure(tickPos);\r
545                                         chordMatrix.setBeat(tickIndex);\r
546                                         if(\r
547                                                 deviceModelList.sequencerModel.getValueIsAdjusting() ||\r
548                                                 ! (sequencer.isRunning() || sequencer.isRecording())\r
549                                         ) {\r
550                                                 MetaMessage msg;\r
551                                                 msg = tickIndex.lastMetaMessageAt(SequenceTickIndex.TIME_SIGNATURE, tickPos);\r
552                                                 timesigSelecter.setValue(msg==null ? null : msg.getData());\r
553                                                 msg = tickIndex.lastMetaMessageAt(SequenceTickIndex.TEMPO, tickPos);\r
554                                                 tempoSelecter.setTempo(msg==null ? null : msg.getData());\r
555                                                 msg = tickIndex.lastMetaMessageAt(SequenceTickIndex.KEY_SIGNATURE, tickPos);\r
556                                                 if( msg == null )\r
557                                                         keysigLabel.clear();\r
558                                                 else {\r
559                                                         Music.Key key = new Music.Key(msg.getData());\r
560                                                         keysigLabel.setKeySignature(key);\r
561                                                         chordMatrix.setKeySignature(key);\r
562                                                 }\r
563                                         }\r
564                                 }\r
565                         }\r
566                 });\r
567                 deviceModelList.sequencerModel.fireStateChanged();\r
568                 chordGuide = new JPanel() {\r
569                         {\r
570                                 setLayout(new BoxLayout(this, BoxLayout.X_AXIS));\r
571                                 add( Box.createHorizontalStrut(2) );\r
572                                 add( chordMatrix.chordGuide );\r
573                                 add( Box.createHorizontalStrut(2) );\r
574                                 add( lyricDisplay );\r
575                                 add( Box.createHorizontalStrut(2) );\r
576                                 add( enterButtonLabel = new ChordButtonLabel("Enter",chordMatrix) {{\r
577                                         addMouseListener(new MouseAdapter() {\r
578                                                 public void mousePressed(MouseEvent e) {\r
579                                                         if( (e.getModifiersEx() & InputEvent.BUTTON3_DOWN_MASK) != 0 ) // RightClicked\r
580                                                                 chordMatrix.setSelectedChord( (Music.Chord)null );\r
581                                                         else\r
582                                                                 chordMatrix.setSelectedChord( lyricDisplay.getText() );\r
583                                                 }\r
584                                         });\r
585                                 }});\r
586                                 add( Box.createHorizontalStrut(5) );\r
587                                 add( chordMatrix.chordDisplay );\r
588                                 add( Box.createHorizontalStrut(5) );\r
589                                 add( darkModeToggleButton = new JToggleButton(new ButtonIcon(ButtonIcon.DARK_MODE_ICON)) {{\r
590                                         setMargin(ZERO_INSETS);\r
591                                         addItemListener(new ItemListener() {\r
592                                                 public void itemStateChanged(ItemEvent e) {\r
593                                                         innerSetDarkMode(darkModeToggleButton.isSelected());\r
594                                                 }\r
595                                         });\r
596                                         setToolTipText("Light / Dark - 明かりを点灯/消灯");\r
597                                         setBorder(null);\r
598                                 }});\r
599                                 add( Box.createHorizontalStrut(5) );\r
600                                 add( anoGakkiToggleButton = new JToggleButton(\r
601                                         new ButtonIcon(ButtonIcon.ANO_GAKKI_ICON)\r
602                                 ) {{\r
603                                         setOpaque(false);\r
604                                         setMargin(ZERO_INSETS);\r
605                                         setBorder( null );\r
606                                         setToolTipText("あの楽器");\r
607                                         addItemListener(\r
608                                                 new ItemListener() {\r
609                                                         public void itemStateChanged(ItemEvent e) {\r
610                                                                 keyboardPanel.keyboardCenterPanel.keyboard.anoGakkiLayeredPane\r
611                                                                 = anoGakkiToggleButton.isSelected() ? anoGakkiLayeredPane : null ;\r
612                                                         }\r
613                                                 }\r
614                                         );\r
615                                 }} );\r
616                                 add( Box.createHorizontalStrut(5) );\r
617                                 add( inversionOmissionButton = new InversionAndOmissionLabel() );\r
618                                 add( Box.createHorizontalStrut(5) );\r
619                                 add( chordMatrix.capoSelecter );\r
620                                 add( Box.createHorizontalStrut(2) );\r
621                         }\r
622                 };\r
623                 keyboardSequencerPanel = new JPanel() {{\r
624                         setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));\r
625                         add(chordGuide);\r
626                         add(Box.createVerticalStrut(5));\r
627                         add(keyboardSplitPane = new JSplitPane(\r
628                                 JSplitPane.HORIZONTAL_SPLIT, keyboardPanel, chordDiagram\r
629                         ) {{\r
630                                 setOneTouchExpandable(true);\r
631                                 setResizeWeight(1.0);\r
632                                 setAlignmentX((float)0.5);\r
633                         }});\r
634                         add(Box.createVerticalStrut(5));\r
635                         add(new JPanel() {{\r
636                                 setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));\r
637                                 add(new JPanel() {{\r
638                                         setLayout(new BoxLayout(this, BoxLayout.X_AXIS));\r
639                                         add( Box.createHorizontalStrut(12) );\r
640                                         add( keysigLabel );\r
641                                         add( Box.createHorizontalStrut(12) );\r
642                                         add( timesigSelecter );\r
643                                         add( Box.createHorizontalStrut(12) );\r
644                                         add( tempoSelecter );\r
645                                         add( Box.createHorizontalStrut(12) );\r
646                                         add( new MeasureIndicator(deviceModelList.sequencerModel) );\r
647                                         add( Box.createHorizontalStrut(12) );\r
648                                         add( songTitleLabel );\r
649                                         add( Box.createHorizontalStrut(12) );\r
650                                         add( new JButton(editorDialog.openAction) {{ setMargin(ZERO_INSETS); }});\r
651                                 }});\r
652                                 add(new JPanel() {{\r
653                                         setLayout(new BoxLayout(this, BoxLayout.X_AXIS));\r
654                                         add( Box.createHorizontalStrut(10) );\r
655                                         add( new JSlider(deviceModelList.sequencerModel) );\r
656                                         add( new TimeIndicator(deviceModelList.sequencerModel) );\r
657                                         add( Box.createHorizontalStrut(5) );\r
658                                         add( new JButton(editorDialog.sequenceListTable.getModel().moveToTopAction) {{\r
659                                                 setMargin(ZERO_INSETS);\r
660                                         }});\r
661                                         add(new JButton(deviceModelList.sequencerModel.moveBackwardAction) {{\r
662                                                 setMargin(ZERO_INSETS);\r
663                                         }});\r
664                                         add(new JToggleButton(deviceModelList.sequencerModel.startStopAction));\r
665                                         add(new JButton(deviceModelList.sequencerModel.moveForwardAction) {{\r
666                                                 setMargin(ZERO_INSETS);\r
667                                         }});\r
668                                         add(new JButton(editorDialog.sequenceListTable.getModel().moveToBottomAction) {{\r
669                                                 setMargin(ZERO_INSETS);\r
670                                         }});\r
671                                         add(new JToggleButton(editorDialog.sequenceListTable.getModel().toggleRepeatAction) {{\r
672                                                 setMargin(ZERO_INSETS);\r
673                                         }});\r
674                                         add( Box.createHorizontalStrut(10) );\r
675                                 }});\r
676                                 add(new JPanel() {{\r
677                                         add(new JButton(\r
678                                                 "MIDI device connection",\r
679                                                 new ButtonIcon( ButtonIcon.MIDI_CONNECTOR_ICON )\r
680                                         ) {{\r
681                                                 addActionListener(midiConnectionDialog);\r
682                                         }});\r
683                                         add(new JButton("Version info") {{\r
684                                                 setToolTipText(VersionInfo.NAME + " " + VersionInfo.VERSION);\r
685                                                 addActionListener(new AboutMessagePane());\r
686                                         }});\r
687                                 }});\r
688                         }});\r
689                 }};\r
690                 mainSplitPane = new JSplitPane(\r
691                         JSplitPane.VERTICAL_SPLIT, chordMatrix, keyboardSequencerPanel\r
692                 ) {{\r
693                         setResizeWeight(0.5);\r
694                         setAlignmentX((float)0.5);\r
695                         setDividerSize(5);\r
696                 }};\r
697                 anoGakkiLayeredPane = new AnoGakkiLayeredPane() {{ add(mainSplitPane); }};\r
698                 setContentPane(anoGakkiLayeredPane);\r
699                 setPreferredSize(new Dimension(750,470));\r
700         }\r
701         /////////////////////////////////////////\r
702         //\r
703         // アプレット開始(その2)\r
704         //\r
705         public void start() {\r
706                 //\r
707                 // コードボタンで設定されている現在の調を\r
708                 // ピアノキーボードに伝える\r
709                 chordMatrix.fireKeySignatureChanged();\r
710                 //\r
711                 // アプレットのパラメータにMIDIファイルのURLが指定されていたら\r
712                 // それを再生する\r
713                 String midi_url = getParameter("midi_file");\r
714                 System.gc();\r
715                 if( midi_url != null ) {\r
716                         addToPlaylist(midi_url);\r
717                         play();\r
718                 }\r
719         }\r
720         // アプレット終了\r
721         public void stop() {\r
722                 deviceModelList.sequencerModel.stop(); // MIDI再生を強制終了\r
723                 System.gc();\r
724         }\r
725         private void innerSetDarkMode(boolean is_dark) {\r
726                 Color col = is_dark ? Color.black : null;\r
727                 // Color fgcol = is_dark ? Color.pink : null;\r
728                 getContentPane().setBackground(\r
729                         is_dark ? Color.black : rootPaneDefaultBgcolor\r
730                 );\r
731                 mainSplitPane.setBackground( col );\r
732                 keyboardSplitPane.setBackground( col );\r
733                 enterButtonLabel.setDarkMode( is_dark );\r
734                 chordGuide.setBackground( col );\r
735                 lyricDisplay.setBorder( is_dark ? null : lyricDisplayDefaultBorder );\r
736                 lyricDisplay.setBackground( is_dark ?\r
737                         chordMatrix.darkModeColorset.backgrounds[2] : lyricDisplayDefaultBgcolor\r
738                 );\r
739                 lyricDisplay.setForeground( is_dark ? Color.white : null );\r
740                 inversionOmissionButton.setBackground( col );\r
741                 anoGakkiToggleButton.setBackground( col );\r
742                 keyboardSequencerPanel.setBackground( col );\r
743                 chordDiagram.setBackground( col );\r
744                 chordDiagram.titleLabel.setDarkMode( is_dark );\r
745                 chordMatrix.setDarkMode( is_dark );\r
746                 keyboardPanel.setDarkMode( is_dark );\r
747         }\r
748 \r
749         private int[] chordOnNotes = null;\r
750         /**\r
751          * 和音を発音します。\r
752          * <p>この関数を直接呼ぶとアルペジオが効かないので、\r
753          * chord_matrix.setSelectedChord() を使うことを推奨\r
754          * </p>\r
755          */\r
756         public void chordOn() {\r
757                 Music.Chord playChord = chordMatrix.getSelectedChord();\r
758                 if(\r
759                         chordOnNotes != null &&\r
760                         chordMatrix.getNoteIndex() < 0 &&\r
761                         (! chordMatrix.isDragged() || playChord == null)\r
762                 ) {\r
763                         // コードが鳴っている状態で、新たなコードを鳴らそうとしたり、\r
764                         // もう鳴らさないという信号が来た場合は、今鳴っている音を止める。\r
765                         //\r
766                         for( int n : chordOnNotes )\r
767                                 keyboardPanel.keyboardCenterPanel.keyboard.noteOff(n);\r
768                         chordOnNotes = null;\r
769                 }\r
770                 if( playChord == null ) {\r
771                         // もう鳴らさないので、歌詞表示に通知して終了\r
772                         if( lyricDisplay != null )\r
773                                 lyricDisplay.currentChord = null;\r
774                         return;\r
775                 }\r
776                 // あの楽器っぽい表示\r
777                 if( keyboardPanel.keyboardCenterPanel.keyboard.anoGakkiLayeredPane != null ) {\r
778                         JComponent btn = chordMatrix.getSelectedButton();\r
779                         if( btn != null )\r
780                                 anoGakkiLayeredPane.start(chordMatrix,btn);\r
781                 }\r
782                 // コードボタンからのコードを、カポつき演奏キーからオリジナルキーへ変換\r
783                 Music.Key originalKey = chordMatrix.getKeySignatureCapo();\r
784                 Music.Chord originalChord = playChord.clone().transpose(\r
785                         chordMatrix.capoSelecter.getCapo(),\r
786                         chordMatrix.getKeySignature()\r
787                 );\r
788                 // 変換後のコードをキーボード画面に設定\r
789                 keyboardPanel.keyboardCenterPanel.keyboard.setChord(originalChord);\r
790                 //\r
791                 // 音域を決める。これにより鳴らす音が確定する。\r
792                 Music.Range chordRange = new Music.Range(\r
793                         keyboardPanel.keyboardCenterPanel.keyboard.getChromaticOffset() + 10 +\r
794                         ( keyboardPanel.keyboardCenterPanel.keyboard.getOctaves() / 4 ) * 12,\r
795                         inversionOmissionButton.isAutoInversionMode() ?\r
796                         keyboardPanel.keyboardCenterPanel.keyboard.getChromaticOffset() + 21 :\r
797                         keyboardPanel.keyboardCenterPanel.keyboard.getChromaticOffset() + 33,\r
798                         -2,\r
799                         inversionOmissionButton.isAutoInversionMode()\r
800                 );\r
801                 int[] notes = originalChord.toNoteArray(chordRange, originalKey);\r
802                 //\r
803                 // 前回鳴らしたコード構成音を覚えておく\r
804                 int[] prevChordOnNotes = null;\r
805                 if( chordMatrix.isDragged() || chordMatrix.getNoteIndex() >= 0 )\r
806                         prevChordOnNotes = Arrays.copyOf(chordOnNotes, chordOnNotes.length);\r
807                 //\r
808                 // 次に鳴らす構成音を決める\r
809                 chordOnNotes = new int[notes.length];\r
810                 int i = 0;\r
811                 for( int n : notes ) {\r
812                         if( inversionOmissionButton.getOmissionNoteIndex() == i ) {\r
813                                 i++; continue;\r
814                         }\r
815                         chordOnNotes[i++] = n;\r
816                         //\r
817                         // その音が今鳴っているか調べる\r
818                         boolean isNoteOn = false;\r
819                         if( prevChordOnNotes != null ) {\r
820                                 for( int prevN : prevChordOnNotes ) {\r
821                                         if( n == prevN ) {\r
822                                                 isNoteOn = true;\r
823                                                 break;\r
824                                         }\r
825                                 }\r
826                         }\r
827                         // すでに鳴っているのに単音を鳴らそうとする場合、\r
828                         // 鳴らそうとしている音を一旦止める。\r
829                         if( isNoteOn && chordMatrix.getNoteIndex() >= 0 &&\r
830                                 notes[chordMatrix.getNoteIndex()] - n == 0\r
831                         ) {\r
832                                 keyboardPanel.keyboardCenterPanel.keyboard.noteOff(n);\r
833                                 isNoteOn = false;\r
834                         }\r
835                         // その音が鳴っていなかったら鳴らす。\r
836                         if( ! isNoteOn )\r
837                                 keyboardPanel.keyboardCenterPanel.keyboard.noteOn(n);\r
838                 }\r
839                 //\r
840                 // コードを表示\r
841                 keyboardPanel.keyboardCenterPanel.keyboard.setChord(originalChord);\r
842                 chordMatrix.chordDisplay.setChord(playChord);\r
843                 //\r
844                 // コードダイアグラム用にもコードを表示\r
845                 Music.Chord diagramChord;\r
846                 int chordDiagramCapo = chordDiagram.capoSelecterView.getCapo();\r
847                 if( chordDiagramCapo == chordMatrix.capoSelecter.getCapo() )\r
848                         diagramChord = playChord.clone();\r
849                 else\r
850                         diagramChord = originalChord.clone().transpose(\r
851                                 - chordDiagramCapo, originalKey\r
852                         );\r
853                 chordDiagram.setChord(diagramChord);\r
854                 if( chordDiagram.recordTextButton.isSelected() )\r
855                         lyricDisplay.appendChord(diagramChord);\r
856         }\r
857 }\r
858 \r
859 /***************************************************************************\r
860  *\r
861  *      GUI parts\r
862  *\r
863  ***************************************************************************/\r
864 \r
865 class ChordDisplay extends JLabel {\r
866         Music.Chord chord = null;\r
867         PianoKeyboard keyboard = null;\r
868         ChordMatrix chordMatrix = null;\r
869         String defaultString = null;\r
870         int noteNumber = -1;\r
871         private boolean isDark = false;\r
872         private boolean isMouseEntered = false;\r
873 \r
874         public ChordDisplay(String defaultString, ChordMatrix chordMatrix, PianoKeyboard keyboard) {\r
875                 super(defaultString, JLabel.CENTER);\r
876                 this.defaultString = defaultString;\r
877                 this.keyboard = keyboard;\r
878                 this.chordMatrix = chordMatrix;\r
879                 if( chordMatrix != null ) {\r
880                         addMouseListener(new MouseAdapter() {\r
881                                 public void mousePressed(MouseEvent e) {\r
882                                         if( chord != null ) { // コードが表示されている場合\r
883                                                 if( (e.getModifiersEx() & InputEvent.BUTTON3_DOWN_MASK) != 0 ) {\r
884                                                         // 右クリックでコードを止める\r
885                                                         ChordDisplay.this.chordMatrix.setSelectedChord((Music.Chord)null);\r
886                                                 }\r
887                                                 else {\r
888                                                         // コードを鳴らす。\r
889                                                         //   キーボードが指定されている場合、オリジナルキー(カポ反映済)のコードを使う。\r
890                                                         if( ChordDisplay.this.keyboard == null )\r
891                                                                 ChordDisplay.this.chordMatrix.setSelectedChord(chord);\r
892                                                         else\r
893                                                                 ChordDisplay.this.chordMatrix.setSelectedChordCapo(chord);\r
894                                                 }\r
895                                         }\r
896                                         else if( noteNumber >= 0 ) { // 音階が表示されている場合\r
897                                                 ChordDisplay.this.keyboard.noteOn(noteNumber);\r
898                                         }\r
899                                 }\r
900                                 public void mouseReleased(MouseEvent e) {\r
901                                         if( noteNumber >= 0 )\r
902                                                 ChordDisplay.this.keyboard.noteOff(noteNumber);\r
903                                 }\r
904                                 public void mouseEntered(MouseEvent e) {\r
905                                         mouseEntered(true);\r
906                                 }\r
907                                 public void mouseExited(MouseEvent e) {\r
908                                         mouseEntered(false);\r
909                                 }\r
910                                 private void mouseEntered(boolean isMouseEntered) {\r
911                                         ChordDisplay.this.isMouseEntered = isMouseEntered;\r
912                                         if( noteNumber >= 0 || chord != null )\r
913                                                 repaint();\r
914                                 }\r
915                         });\r
916                         addMouseWheelListener(this.chordMatrix);\r
917                 }\r
918         }\r
919         public void paint(Graphics g) {\r
920                 super.paint(g);\r
921                 Dimension d = getSize();\r
922                 if( isMouseEntered && (noteNumber >= 0 || chord != null) ) {\r
923                         g.setColor(Color.gray);\r
924                         g.drawRect( 0, 0, d.width-1, d.height-1 );\r
925                 }\r
926         }\r
927         private void setChordText() {\r
928                 setText( chord.toHtmlString(isDark ? "#FFCC33" : "maroon") );\r
929         }\r
930         void setNote(int note_no) { setNote( note_no, false ); }\r
931         void setNote(int note_no, boolean is_rhythm_part) {\r
932                 setToolTipText(null);\r
933                 this.chord = null;\r
934                 this.noteNumber = note_no;\r
935                 if( note_no < 0 ) {\r
936                         //\r
937                         // Clear\r
938                         //\r
939                         setText(defaultString);\r
940                         return;\r
941                 }\r
942                 if( is_rhythm_part ) {\r
943                         setText(\r
944                                         "MIDI note No." + note_no + " : "\r
945                                                         + MIDISpec.getPercussionName(note_no)\r
946                                         );\r
947                 }\r
948                 else {\r
949                         setText(\r
950                                         "Note: " + Music.NoteSymbol.noteNoToSymbol(note_no)\r
951                                         + "  -  MIDI note No." + note_no + " : "\r
952                                         + Math.round(Music.noteNoToFrequency(note_no)) + "Hz" );\r
953                 }\r
954         }\r
955         void setChord(Music.Chord chord) {\r
956                 this.chord = chord;\r
957                 this.noteNumber = -1;\r
958                 if( chord == null ) {\r
959                         setText( defaultString );\r
960                         setToolTipText( null );\r
961                 }\r
962                 else {\r
963                         setChordText();\r
964                         setToolTipText( "Chord: " + chord.toName() );\r
965                 }\r
966         }\r
967         void setDarkMode(boolean is_dark) {\r
968                 this.isDark = is_dark;\r
969                 if( chord != null ) setChordText();\r
970         }\r
971 }\r
972 \r
973 /**\r
974  * 転回・省略音メニューボタン\r
975  */\r
976 class InversionAndOmissionLabel extends JLabel\r
977         implements MouseListener, PopupMenuListener\r
978 {\r
979         JPopupMenu popup_menu;\r
980         ButtonGroup omission_group = new ButtonGroup();\r
981         ButtonIcon icon = new ButtonIcon(ButtonIcon.INVERSION_ICON);\r
982         JRadioButtonMenuItem radioButtonitems[] = new JRadioButtonMenuItem[4];\r
983         JCheckBoxMenuItem cb_inversion;\r
984 \r
985         public InversionAndOmissionLabel() {\r
986                 setIcon(icon);\r
987                 popup_menu = new JPopupMenu();\r
988                 popup_menu.add(\r
989                         cb_inversion = new JCheckBoxMenuItem("Auto Inversion",true)\r
990                 );\r
991                 popup_menu.addSeparator();\r
992                 omission_group.add(\r
993                         radioButtonitems[0] = new JRadioButtonMenuItem("All notes",true)\r
994                 );\r
995                 popup_menu.add(radioButtonitems[0]);\r
996                 omission_group.add(\r
997                         radioButtonitems[1] = new JRadioButtonMenuItem("Omit 5th")\r
998                 );\r
999                 popup_menu.add(radioButtonitems[1]);\r
1000                 omission_group.add(\r
1001                         radioButtonitems[2] = new JRadioButtonMenuItem("Omit 3rd (Power Chord)")\r
1002                 );\r
1003                 popup_menu.add(radioButtonitems[2]);\r
1004                 omission_group.add(\r
1005                         radioButtonitems[3] = new JRadioButtonMenuItem("Omit root")\r
1006                 );\r
1007                 popup_menu.add(radioButtonitems[3]);\r
1008                 addMouseListener(this);\r
1009                 popup_menu.addPopupMenuListener(this);\r
1010                 setToolTipText("Automatic inversion and Note omission - 自動転回と省略音の設定");\r
1011         }\r
1012         public void mousePressed(MouseEvent e) {\r
1013                 Component c = e.getComponent();\r
1014                 if( c == this ) popup_menu.show( c, 0, getHeight() );\r
1015         }\r
1016         public void mouseReleased(MouseEvent e) { }\r
1017         public void mouseEntered(MouseEvent e) { }\r
1018         public void mouseExited(MouseEvent e) { }\r
1019         public void mouseClicked(MouseEvent e) { }\r
1020         public void popupMenuCanceled(PopupMenuEvent e) { }\r
1021         public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {\r
1022                 repaint(); // To repaint icon image\r
1023         }\r
1024         public void popupMenuWillBecomeVisible(PopupMenuEvent e) { }\r
1025         public boolean isAutoInversionMode() {\r
1026                 return cb_inversion.isSelected();\r
1027         }\r
1028         public void setAutoInversion(boolean is_auto) {\r
1029                 cb_inversion.setSelected(is_auto);\r
1030         }\r
1031         public int getOmissionNoteIndex() {\r
1032                 if( radioButtonitems[3].isSelected() ) { // Root\r
1033                         return 0;\r
1034                 }\r
1035                 else if( radioButtonitems[2].isSelected() ) { // 3rd\r
1036                         return 1;\r
1037                 }\r
1038                 else if( radioButtonitems[1].isSelected() ) { // 5th\r
1039                         return 2;\r
1040                 }\r
1041                 else { // No omission\r
1042                         return -1;\r
1043                 }\r
1044         }\r
1045         public void setOmissionNoteIndex(int index) {\r
1046                 switch(index) {\r
1047                 case 0: radioButtonitems[3].setSelected(true); break;\r
1048                 case 1: radioButtonitems[2].setSelected(true); break;\r
1049                 case 2: radioButtonitems[1].setSelected(true); break;\r
1050                 default: radioButtonitems[0].setSelected(true); break;\r
1051                 }\r
1052         }\r
1053 }\r
1054 \r
1055 class ChordTextField extends JTextField implements MetaEventListener {\r
1056         Music.Chord currentChord = null;\r
1057         private long lyricArrivedTime = System.nanoTime();\r
1058         public ChordTextField() {\r
1059                 super(80);\r
1060                 //\r
1061                 // JTextField は、サイズ設定をしないとリサイズ時に縦に伸び過ぎてしまう。\r
1062                 // 1行しか入力できないので、縦に伸びすぎるのはスペースがもったいない。\r
1063                 // そこで、このような現象を防止するために、最大サイズを明示的に\r
1064                 // 画面サイズと同じに設定する。\r
1065                 //\r
1066                 // To reduce resized height, set maximum size to screen size.\r
1067                 //\r
1068                 setMaximumSize(\r
1069                         java.awt.Toolkit.getDefaultToolkit().getScreenSize()\r
1070                 );\r
1071         }\r
1072         @Override\r
1073         public void meta(MetaMessage msg) {\r
1074                 switch(msg.getType()) {\r
1075                 case 0x01: // Text(任意のテキスト:コメントなど)\r
1076                 case 0x02: // Copyright(著作権表示)\r
1077                 case 0x05: // Lyrics(歌詞)\r
1078                 case 0x06: // Marker\r
1079                 case 0x03: // Sequence Name / Track Name(曲名またはトラック名)\r
1080                         byte[] data = msg.getData();\r
1081                         if( ! SwingUtilities.isEventDispatchThread() ) {\r
1082                                 // MIDIシーケンサの EDT から呼ばれた場合、\r
1083                                 // 表示処理を Swing の EDT に振り直す。\r
1084                                 SwingUtilities.invokeLater(new AddLyricJob(data));\r
1085                                 break;\r
1086                         }\r
1087                         addLyric(data);\r
1088                         break;\r
1089                 default:\r
1090                         break;\r
1091                 }\r
1092         }\r
1093         /**\r
1094          * 歌詞を追加するジョブ\r
1095          */\r
1096         private class AddLyricJob implements Runnable {\r
1097                 private byte[] data;\r
1098                 public AddLyricJob(byte[] data) { this.data = data; }\r
1099                 @Override\r
1100                 public void run() { addLyric(data); }\r
1101         }\r
1102         /**\r
1103          * 歌詞を追加し、カーソルを末尾に移動します。\r
1104          * @param data 歌詞データ\r
1105          */\r
1106         public void addLyric(byte[] data) {\r
1107                 long startTime = System.nanoTime();\r
1108                 // 歌詞を表示\r
1109                 String additionalLyric;\r
1110                 try {\r
1111                         additionalLyric = (new String(data,"JISAutoDetect")).trim();\r
1112                 } catch( UnsupportedEncodingException e ) {\r
1113                         additionalLyric = (new String(data)).trim();\r
1114                 }\r
1115                 String lyric = getText();\r
1116                 if( startTime - lyricArrivedTime > 1000000000L /* 1sec */\r
1117                         && (\r
1118                                 additionalLyric.length() > 8 || additionalLyric.isEmpty()\r
1119                                 || lyric == null || lyric.isEmpty()\r
1120                         )\r
1121                 ) {\r
1122                         // 長い歌詞や空白が来たり、追加先に歌詞がなかった場合は上書きする。\r
1123                         // ただし、前回から充分に時間が経っていない場合は上書きしない。\r
1124                         lyric = additionalLyric;\r
1125                 }\r
1126                 else {\r
1127                         // 短い歌詞だった場合は、既存の歌詞に追加する\r
1128                         lyric += " " + additionalLyric;\r
1129                 }\r
1130                 setText(lyric);\r
1131                 setCaretPosition(getText().length());\r
1132                 lyricArrivedTime = startTime;\r
1133         }\r
1134         /**\r
1135          * コードを追加します。\r
1136          * @param chord コード\r
1137          */\r
1138         public void appendChord(Music.Chord chord) {\r
1139                 if( currentChord == null && chord == null )\r
1140                         return;\r
1141                 if( currentChord != null && chord != null && chord.equals(currentChord) )\r
1142                         return;\r
1143                 String delimiter = ""; // was "\n"\r
1144                 setText( getText() + (chord == null ? delimiter : chord + " ") );\r
1145                 currentChord = ( chord == null ? null : chord.clone() );\r
1146         }\r
1147 }\r
1148 \r
1149 \r