OSDN Git Service

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