OSDN Git Service

70ba611613e426a66d778298b2876839f06408b3
[midichordhelper/MIDIChordHelper.git] / src / camidion / chordhelper / chordmatrix / ChordMatrix.java
1 package camidion.chordhelper.chordmatrix;
2
3 import java.awt.Color;
4 import java.awt.Component;
5 import java.awt.Dimension;
6 import java.awt.Font;
7 import java.awt.Graphics;
8 import java.awt.Graphics2D;
9 import java.awt.GridLayout;
10 import java.awt.event.ActionEvent;
11 import java.awt.event.ActionListener;
12 import java.awt.event.FocusEvent;
13 import java.awt.event.FocusListener;
14 import java.awt.event.InputEvent;
15 import java.awt.event.ItemEvent;
16 import java.awt.event.ItemListener;
17 import java.awt.event.KeyEvent;
18 import java.awt.event.KeyListener;
19 import java.awt.event.MouseEvent;
20 import java.awt.event.MouseListener;
21 import java.awt.event.MouseMotionListener;
22 import java.awt.event.MouseWheelEvent;
23 import java.awt.event.MouseWheelListener;
24 import java.util.ArrayList;
25
26 import javax.swing.JComponent;
27 import javax.swing.JLabel;
28 import javax.swing.JOptionPane;
29 import javax.swing.JPanel;
30
31 import camidion.chordhelper.ButtonIcon;
32 import camidion.chordhelper.ChordDisplayLabel;
33 import camidion.chordhelper.chorddiagram.CapoComboBoxModel;
34 import camidion.chordhelper.chorddiagram.CapoSelecterView;
35 import camidion.chordhelper.midieditor.SequenceTickIndex;
36 import camidion.chordhelper.music.Chord;
37 import camidion.chordhelper.music.Key;
38 import camidion.chordhelper.music.Music;
39 import camidion.chordhelper.music.NoteSymbol;
40 import camidion.chordhelper.music.NoteSymbolLanguage;
41
42 /**
43  * MIDI Chord Helper 用のコードボタンマトリクス
44  *
45  * @author
46  *      Copyright (C) 2004-2016 Akiyoshi Kamide
47  *      http://www.yk.rim.or.jp/~kamide/music/chordhelper/
48  */
49 public class ChordMatrix extends JPanel
50         implements MouseListener, KeyListener, MouseMotionListener, MouseWheelListener
51 {
52         /**
53          * 列数
54          */
55         public static final int N_COLUMNS = Music.SEMITONES_PER_OCTAVE * 2 + 1;
56         /**
57          * 行数
58          */
59         public static final int CHORD_BUTTON_ROWS = 3;
60         /**
61          * 調号ボタン
62          */
63         public Co5Label keysigLabels[] = new Co5Label[ N_COLUMNS ];
64         /**
65          * コードボタン
66          */
67         public ChordLabel chordLabels[] = new ChordLabel[N_COLUMNS * CHORD_BUTTON_ROWS];
68         /**
69          * コードボタンの下のコード表示部
70          */
71         public ChordDisplayLabel chordDisplay = new ChordDisplayLabel("Chord Pad", this, null);
72
73         private static class ChordLabelSelection {
74                 ChordLabel chordLabel;
75                 int bitIndex;
76                 boolean isSus4;
77                 public ChordLabelSelection(ChordLabel chordLabel, int bitIndex) {
78                         this.chordLabel = chordLabel;
79                         this.bitIndex = bitIndex;
80                         this.isSus4 = chordLabel.isSus4;
81                 }
82                 public void setCheckBit(boolean isOn) {
83                         chordLabel.setCheckBit(isOn, bitIndex);
84                 }
85                 public boolean setBassCheckBit(boolean isOn) {
86                         if( bitIndex == 0 && ! isSus4 ) {
87                                 chordLabel.setCheckBit(isOn, 6);
88                                 return true;
89                         }
90                         return false;
91                 }
92         }
93         private static class ChordLabelSelections {
94                 int weight = 0;
95                 int bass_weight = 0;
96                 boolean is_active = false;
97                 boolean is_bass_active = false;
98                 private ChordLabelSelection acls[];
99                 public ChordLabelSelections(ArrayList<ChordLabelSelection> al) {
100                         acls = al.toArray(new ChordLabelSelection[al.size()]);
101                 }
102                 void addWeight(int weight_diff) {
103                         if( (weight += weight_diff) < 0 ) weight = 0;
104                         if( (weight > 0) != is_active ) {
105                                 is_active = !is_active;
106                                 for( ChordLabelSelection cls : acls ) {
107                                         cls.setCheckBit(is_active);
108                                 }
109                         }
110                 }
111                 void addBassWeight(int weight_diff) {
112                         if( (bass_weight += weight_diff) < 0 ) bass_weight = 0;
113                         if( (bass_weight > 0) != is_bass_active ) {
114                                 is_bass_active = !is_bass_active;
115                                 for( ChordLabelSelection cls : acls ) {
116                                         if( ! cls.setBassCheckBit(is_bass_active) ) {
117                                                 // No more root major/minor
118                                                 break;
119                                         }
120                                 }
121                         }
122                         addWeight(weight_diff);
123                 }
124                 void clearWeight() {
125                         weight = bass_weight = 0;
126                         is_active = is_bass_active = false;
127                         for( ChordLabelSelection cls : acls ) {
128                                 cls.setCheckBit(false);
129                                 cls.setBassCheckBit(false);
130                         }
131                 }
132         }
133         private ChordLabelSelections chordLabelSelections[] =
134                 new ChordLabelSelections[Music.SEMITONES_PER_OCTAVE];
135         /**
136          * 発音中のノート表示をクリアします。
137          */
138         public void clearIndicators() {
139                 for( int i=0; i<chordLabelSelections.length; i++ ) {
140                         chordLabelSelections[i].clearWeight();
141                 }
142                 repaint();
143         }
144         /**
145          * MIDIのノートイベント(ON/OFF)を受け取ります。
146          * @param isNoteOn ONのときtrue
147          * @param noteNumber ノート番号
148          */
149         public void note(boolean isNoteOn, int noteNumber) {
150                 int weightDiff = (isNoteOn ? 1 : -1);
151                 ChordLabelSelections cls = chordLabelSelections[Music.mod12(noteNumber)];
152                 if( noteNumber < 49 )
153                         cls.addBassWeight(weightDiff);
154                 else
155                         cls.addWeight(weightDiff);
156         }
157
158         /**
159          * 調号ボタン
160          */
161         private class Co5Label extends JLabel {
162                 public boolean isSelected = false;
163                 public int co5Value = 0;
164                 private Color indicatorColor;
165                 public Co5Label(int v) {
166                         Key key = new Key(co5Value = v);
167                         setOpaque(true);
168                         setBackground(false);
169                         setForeground( currentColorset.foregrounds[0] );
170                         setHorizontalAlignment( JLabel.CENTER );
171                         String tip = "Key signature: ";
172                         if( v != key.toCo5() ) {
173                                 tip += "out of range" ;
174                         }
175                         else {
176                                 tip += key.signatureDescription() + " " +
177                                         key.toStringIn(NoteSymbolLanguage.IN_JAPANESE);
178                                 if( v == 0 ) {
179                                         setIcon(new ButtonIcon(ButtonIcon.NATURAL_ICON));
180                                 }
181                                 else {
182                                         setFont( getFont().deriveFont(Font.PLAIN) );
183                                         setText( key.signature() );
184                                 }
185                         }
186                         setToolTipText(tip);
187                 }
188                 public void paint(Graphics g) {
189                         super.paint(g);
190                         Dimension d = getSize();
191                         if( ChordMatrix.this.isFocusOwner() && isSelected ) {
192                                 g.setColor( currentColorset.focus[1] );
193                                 g.drawRect( 0, 0, d.width-1, d.height-1 );
194                         }
195                         if( !isSelected || !isPlaying || currentBeat+1 == timesigUpper ) {
196                                 return;
197                         }
198                         if( currentBeat == 0 ) {
199                                 g.setColor( indicatorColor );
200                                 g.drawRect( 2, 2, d.width-5, d.height-5 );
201                                 g.setColor( isDark ? indicatorColor.darker() : indicatorColor.brighter() );
202                                 g.drawRect( 0, 0, d.width-1, d.height-1 );
203                                 return;
204                         }
205                         Color color = currentColorset.indicators[0];
206                         g.setColor( color );
207                         if( currentBeat == 1 ) {
208                                 //
209                                 // ||__ii
210                                 g.drawLine( 2, d.height-3, d.width-3, d.height-3 );
211                                 g.drawLine( d.width-3, d.height*3/4, d.width-3, d.height-3 );
212                                 g.drawLine( 2, 2, 2, d.height-3 );
213                                 g.setColor( isDark ? color.darker() : color.brighter() );
214                                 g.drawLine( 0, d.height-1, d.width-1, d.height-1 );
215                                 g.drawLine( d.width-1, d.height*3/4, d.width-1, d.height-1 );
216                                 g.drawLine( 0, 0, 0, d.height-1 );
217                         }
218                         else {
219                                 //
220                                 // ii__
221                                 //
222                                 int vertical_top = (d.height-1) * (currentBeat-1) / (timesigUpper-2) ;
223                                 g.drawLine( 2, vertical_top == 0 ? 2 : vertical_top, 2, d.height-3 );
224                                 g.setColor( isDark ? color.darker() : color.brighter() );
225                                 g.drawLine( 0, vertical_top, 0, d.height-1 );
226                         }
227                 }
228                 public void setBackground(boolean isActive) {
229                         super.setBackground(currentColorset.backgrounds[isActive?2:0]);
230                         setIndicatorColor();
231                         setOpaque(true);
232                 }
233                 public void setSelection(boolean isSelected) {
234                         this.isSelected = isSelected;
235                         setSelection();
236                 }
237                 public void setSelection() {
238                         setForeground(currentColorset.foregrounds[isSelected?1:0]);
239                 }
240                 public void setIndicatorColor() {
241                         if( co5Value < 0 ) {
242                                 indicatorColor = currentColorset.indicators[2];
243                         }
244                         else if( co5Value > 0 ) {
245                                 indicatorColor = currentColorset.indicators[1];
246                         }
247                         else {
248                                 indicatorColor = currentColorset.foregrounds[1];
249                         }
250                 }
251         }
252         /**
253          * コードボタン
254          */
255         private class ChordLabel extends JLabel {
256                 public byte checkBits = 0;
257                 public int co5Value;
258                 public boolean isMinor;
259                 public boolean isSus4;
260                 public boolean isSelected = false;
261                 public Chord chord;
262
263                 private boolean inActiveZone = true;
264                 private Font boldFont;
265                 private Font plainFont;
266                 private int indicatorColorIndices[] = new int[5];
267                 private byte indicatorBits = 0;
268
269                 public ChordLabel(Chord chord) {
270                         this.chord = chord;
271                         isMinor = chord.isSet(Chord.Interval.MINOR);
272                         isSus4 = chord.isSet(Chord.Interval.SUS4);
273                         co5Value = chord.rootNoteSymbol().toCo5();
274                         if( isMinor ) co5Value -= 3;
275                         String labelText = ( isSus4 ? chord.symbolSuffix() : chord.toString() );
276                         if( isMinor && labelText.length() > 3 ) {
277                                 float small_point_size = getFont().getSize2D() - 2;
278                                 boldFont = getFont().deriveFont(Font.BOLD, small_point_size);
279                                 plainFont = getFont().deriveFont(Font.PLAIN, small_point_size);
280                         }
281                         else {
282                                 boldFont = getFont().deriveFont(Font.BOLD);
283                                 plainFont = getFont().deriveFont(Font.PLAIN);
284                         }
285                         setOpaque(true);
286                         setBackground(0);
287                         setForeground( currentColorset.foregrounds[0] );
288                         setBold(false);
289                         setHorizontalAlignment( JLabel.CENTER );
290                         setText(labelText);
291                         setToolTipText( "Chord: " + chord.toName() );
292                 }
293                 public void paint(Graphics g) {
294                         super.paint(g);
295                         Dimension d = getSize();
296                         Graphics2D g2 = (Graphics2D) g;
297                         Color color = null;
298
299                         if( ! inActiveZone ) {
300                                 g2.setColor( Color.gray );
301                         }
302
303                         if( (indicatorBits & 32) != 0 ) {
304                                 //
305                                 // Draw square  []  with 3rd/sus4th note color
306                                 //
307                                 if( inActiveZone ) {
308                                         color = currentColorset.indicators[indicatorColorIndices[1]];
309                                         g2.setColor( color );
310                                 }
311                                 g2.drawRect( 0, 0, d.width-1, d.height-1 );
312                                 g2.drawRect( 2, 2, d.width-5, d.height-5 );
313                         }
314                         if( (indicatorBits & 1) != 0 ) {
315                                 //
316                                 // Draw  ||__  with root note color
317                                 //
318                                 if( inActiveZone ) {
319                                         color = currentColorset.indicators[indicatorColorIndices[0]];
320                                         g2.setColor( color );
321                                 }
322                                 g2.drawLine( 0, 0, 0, d.height-1 );
323                                 g2.drawLine( 2, 2, 2, d.height-3 );
324                         }
325                         if( (indicatorBits & 64) != 0 ) {
326                                 // Draw bass mark with root note color
327                                 //
328                                 if( inActiveZone ) {
329                                         color = currentColorset.indicators[indicatorColorIndices[0]];
330                                         g2.setColor( color );
331                                 }
332                                 g2.fillRect( 6, d.height-7, d.width-12, 2 );
333                         }
334                         if( (indicatorBits & 4) != 0 ) {
335                                 //
336                                 // Draw short  __ii  with parfect 5th color
337                                 //
338                                 if( inActiveZone ) {
339                                         color = currentColorset.indicators[indicatorColorIndices[2]];
340                                         g2.setColor( color );
341                                 }
342                                 g2.drawLine( d.width-1, d.height*3/4, d.width-1, d.height-1 );
343                                 g2.drawLine( d.width-3, d.height*3/4, d.width-3, d.height-3 );
344                         }
345                         if( (indicatorBits & 2) != 0 ) {
346                                 //
347                                 // Draw  __  with 3rd note color
348                                 //
349                                 if( inActiveZone ) {
350                                         color = currentColorset.indicators[indicatorColorIndices[1]];
351                                         g2.setColor( color );
352                                 }
353                                 g2.drawLine( 0, d.height-1, d.width-1, d.height-1 );
354                                 g2.drawLine( 2, d.height-3, d.width-3, d.height-3 );
355                         }
356                         if( (indicatorBits & 8) != 0 ) {
357                                 //
358                                 // Draw circle with diminished 5th color
359                                 //
360                                 if( inActiveZone ) {
361                                         g2.setColor( currentColorset.indicators[indicatorColorIndices[3]] );
362                                 }
363                                 g2.drawOval( 1, 1, d.width-2, d.height-2 );
364                         }
365                         if( (indicatorBits & 16) != 0 ) {
366                                 //
367                                 // Draw + with augument 5th color
368                                 //
369                                 if( inActiveZone ) {
370                                         g2.setColor( currentColorset.indicators[indicatorColorIndices[4]] );
371                                 }
372                                 g2.drawLine( 1, 3, d.width-3, 3 );
373                                 g2.drawLine( 1, 4, d.width-3, 4 );
374                                 g2.drawLine( d.width/2-1, 0, d.width/2-1, 7 );
375                                 g2.drawLine( d.width/2, 0, d.width/2, 7 );
376                         }
377                 }
378                 public void setCheckBit( boolean is_on, int bit_index ) {
379                         //
380                         // Check bits: x6x43210
381                         //   6:BassRoot
382                         //   4:Augumented5th, 3:Diminished5th, 2:Parfect5th,
383                         //   1:Major3rd/minor3rd/sus4th, 0:Root
384                         //
385                         byte mask = ((byte)(1<<bit_index));
386                         byte old_check_bits = checkBits;
387                         if( is_on ) {
388                                 checkBits |= mask;
389                         }
390                         else {
391                                 checkBits &= ~mask;
392                         }
393                         if( old_check_bits == checkBits ) {
394                                 // No bits changed
395                                 return;
396                         }
397                         // Indicator bits: x6543210     6:Bass||_  5:[]  4:+  3:O  2:_ii  1:__  0:||_
398                         //
399                         byte indicator_bits = 0;
400                         if( (checkBits & 1) != 0 ) {
401                                 if( (checkBits & 7) == 7 ) { // All triad notes appared
402                                         //
403                                         // Draw square
404                                         indicator_bits |= 0x20;
405                                         //
406                                         // Draw different-colored vertical lines
407                                         if( indicatorColorIndices[0] != indicatorColorIndices[1] ) {
408                                                 indicator_bits |= 1;
409                                         }
410                                         if( indicatorColorIndices[2] != indicatorColorIndices[1] ) {
411                                                 indicator_bits |= 4;
412                                         }
413                                 }
414                                 else if( !isSus4 ) {
415                                         //
416                                         // Draw vertical lines  || ii
417                                         indicator_bits |= 5;
418                                         //
419                                         if( (checkBits & 2) != 0 && (!isMinor || (checkBits & 0x18) != 0) ) {
420                                                 //
421                                                 // Draw horizontal bottom lines __
422                                                 indicator_bits |= 2;
423                                         }
424                                 }
425                                 if( !isSus4 ) {
426                                         if( isMinor || (checkBits & 2) != 0 ) {
427                                                 indicator_bits |= (byte)(checkBits & 0x18);  // Copy bit 3 and bit 4
428                                         }
429                                         if( (checkBits & 0x40) != 0 ) {
430                                                 indicator_bits |= 0x40; // Bass
431                                         }
432                                 }
433                         }
434                         if( this.indicatorBits == indicator_bits ) {
435                                 // No shapes changed
436                                 return;
437                         }
438                         this.indicatorBits = indicator_bits;
439                         repaint();
440                 }
441                 public void setBackground(int i) {
442                         switch( i ) {
443                         case  0:
444                         case  1:
445                         case  2:
446                         case  3:
447                                 super.setBackground(currentColorset.backgrounds[i]);
448                                 setOpaque(true);
449                                 break;
450                         default: return;
451                         }
452                 }
453                 public void setSelection(boolean is_selected) {
454                         this.isSelected = is_selected;
455                         setSelection();
456                 }
457                 public void setSelection() {
458                         setForeground(currentColorset.foregrounds[this.isSelected?1:0]);
459                 }
460                 public void setBold(boolean is_bold) {
461                         setFont( is_bold ? boldFont : plainFont );
462                 }
463                 public void keyChanged() {
464                         int co5_key = capoKey.toCo5();
465                         int co5_offset = co5Value - co5_key;
466                         inActiveZone = (co5_offset <= 6 && co5_offset >= -6) ;
467                         int root_note = chord.rootNoteSymbol().toNoteNumber();
468                         //
469                         // Reconstruct color index
470                         //
471                         // Root
472                         indicatorColorIndices[0] = Music.isOnScale(
473                                 root_note, co5_key
474                         ) ? 0 : co5_offset > 0 ? 1 : 2;
475                         //
476                         // 3rd / sus4
477                         indicatorColorIndices[1] = Music.isOnScale(
478                                 root_note+(isMinor?3:isSus4?5:4), co5_key
479                         ) ? 0 : co5_offset > 0 ? 1 : 2;
480                         //
481                         // P5th
482                         indicatorColorIndices[2] = Music.isOnScale(
483                                 root_note+7, co5_key
484                         ) ? 0 : co5_offset > 0 ? 1 : 2;
485                         //
486                         // dim5th
487                         indicatorColorIndices[3] = Music.isOnScale(
488                                 root_note+6, co5_key
489                         ) ? 0 : co5_offset > 4 ? 1 : 2;
490                         //
491                         // aug5th
492                         indicatorColorIndices[4] = Music.isOnScale(
493                                 root_note+8, co5_key
494                         ) ? 0 : co5_offset > -3 ? 1 : 2;
495                 }
496         }
497
498         /**
499          * 色セット(ダークモード切替対応)
500          */
501         public class ColorSet {
502                 Color[] focus = new Color[2];   // 0:lost 1:gained
503                 Color[] foregrounds = new Color[2];     // 0:unselected 1:selected
504                 public Color[] backgrounds = new Color[4]; // 0:remote 1:left 2:local 3:right
505                 Color[] indicators = new Color[3];      // 0:natural 1:sharp 2:flat
506         }
507         public ColorSet normalModeColorset = new ColorSet() {
508                 {
509                         foregrounds[0] = null;
510                         foregrounds[1] = new Color(0xFF,0x3F,0x3F);
511                         backgrounds[0] = new Color(0xCF,0xFF,0xCF);
512                         backgrounds[1] = new Color(0x9F,0xFF,0xFF);
513                         backgrounds[2] = new Color(0xFF,0xCF,0xCF);
514                         backgrounds[3] = new Color(0xFF,0xFF,0x9F);
515                         indicators[0] = new Color(0xFF,0x3F,0x3F);
516                         indicators[1] = new Color(0xCF,0x6F,0x00);
517                         indicators[2] = new Color(0x3F,0x3F,0xFF);
518                         focus[0] = null;
519                         focus[1] = getBackground().darker();
520                 }
521         };
522         public ColorSet darkModeColorset = new ColorSet() {
523                 {
524                         foregrounds[0] = Color.gray.darker();
525                         foregrounds[1] = Color.pink.brighter();
526                         backgrounds[0] = Color.black;
527                         backgrounds[1] = new Color(0x00,0x18,0x18);
528                         backgrounds[2] = new Color(0x20,0x00,0x00);
529                         backgrounds[3] = new Color(0x18,0x18,0x00);
530                         indicators[0] = Color.pink;
531                         indicators[1] = Color.yellow;
532                         indicators[2] = Color.cyan;
533                         focus[0] = Color.black;
534                         focus[1] = getForeground().brighter();
535                 }
536         };
537         private ColorSet currentColorset = normalModeColorset;
538
539         /**
540          * カポ値選択コンボボックス(コードボタン側ビュー)
541          */
542         public CapoSelecterView capoSelecter;
543
544         /**
545          * コードボタンマトリクスの構築
546          * @param capoValueModel カポ選択値モデル
547          */
548         public ChordMatrix(CapoComboBoxModel capoValueModel) {
549                 capoSelecter = new CapoSelecterView(capoValueModel) {{
550                         checkbox.addItemListener(new ItemListener() {
551                                 public void itemStateChanged(ItemEvent e) {capoChanged(getCapo());}
552                         });
553                         valueSelecter.addActionListener(new ActionListener() {
554                                 public void actionPerformed(ActionEvent e) {capoChanged(getCapo());}
555                         });
556                 }};
557                 int i, v;
558                 Dimension buttonSize = new Dimension(28,26);
559                 //
560                 // Make key-signature labels and chord labels
561                 Co5Label l;
562                 for (i=0, v= -Music.SEMITONES_PER_OCTAVE; i<N_COLUMNS; i++, v++) {
563                         l = new Co5Label(v);
564                         l.addMouseListener(this);
565                         l.addMouseMotionListener(this);
566                         add( keysigLabels[i] = l );
567                         l.setPreferredSize(buttonSize);
568                 }
569                 int row;
570                 for (i=0; i < N_COLUMNS * CHORD_BUTTON_ROWS; i++) {
571                         row = i / N_COLUMNS;
572                         v = i - (N_COLUMNS * row) - 12;
573                         Chord chord = new Chord(
574                                 new NoteSymbol(row==2 ? v+3 : v)
575                         );
576                         if( row==0 ) chord.set(Chord.Interval.SUS4);
577                         else if( row==2 ) chord.set(Chord.Interval.MINOR);
578                         ChordLabel cl = new ChordLabel(chord);
579                         cl.addMouseListener(this);
580                         cl.addMouseMotionListener(this);
581                         cl.addMouseWheelListener(this);
582                         add(chordLabels[i] = cl);
583                         cl.setPreferredSize(buttonSize);
584                 }
585                 setFocusable(true);
586                 setOpaque(true);
587                 addKeyListener(this);
588                 addFocusListener(new FocusListener() {
589                         public void focusGained(FocusEvent e) {
590                                 repaint();
591                         }
592                         public void focusLost(FocusEvent e) {
593                                 selectedChord = selectedChordCapo = null;
594                                 fireChordChanged();
595                                 repaint();
596                         }
597                 });
598                 setLayout(new GridLayout( 4, N_COLUMNS, 2, 2 ));
599                 setKeySignature( new Key() );
600                 //
601                 // Make chord label selections index
602                 //
603                 int noteIndex;
604                 ArrayList<ChordLabelSelection> al;
605                 Chord chord;
606                 for( int note_no=0; note_no<chordLabelSelections.length; note_no++ ) {
607                         al = new ArrayList<ChordLabelSelection>();
608                         //
609                         // Root major/minor chords
610                         for( ChordLabel cl : chordLabels ) {
611                                 if( ! cl.isSus4 && cl.chord.indexOf(note_no) == 0 ) {
612                                         al.add(new ChordLabelSelection( cl, 0 )); // Root
613                                 }
614                         }
615                         // Root sus4 chords
616                         for( ChordLabel cl : chordLabels ) {
617                                 if( cl.isSus4 && cl.chord.indexOf(note_no) == 0 ) {
618                                         al.add(new ChordLabelSelection( cl, 0 )); // Root
619                                 }
620                         }
621                         // 3rd,sus4th,5th included chords
622                         for( ChordLabel cl : chordLabels ) {
623                                 noteIndex = cl.chord.indexOf(note_no);
624                                 if( noteIndex == 1 || noteIndex == 2 ) {
625                                         al.add(new ChordLabelSelection( cl, noteIndex )); // 3rd,sus4,P5
626                                 }
627                         }
628                         // Diminished chords (major/minor chord button only)
629                         for( ChordLabel cl : chordLabels ) {
630                                 if( cl.isSus4 ) continue;
631                                 (chord = cl.chord.clone()).set(Chord.Interval.FLAT5);
632                                 if( chord.indexOf(note_no) == 2 ) {
633                                         al.add(new ChordLabelSelection( cl, 3 ));
634                                 }
635                         }
636                         // Augumented chords (major chord button only)
637                         for( ChordLabel cl : chordLabels ) {
638                                 if( cl.isSus4 || cl.isMinor ) continue;
639                                 (chord = cl.chord.clone()).set(Chord.Interval.SHARP5);
640                                 if( chord.indexOf(note_no) == 2 ) {
641                                         al.add(new ChordLabelSelection( cl, 4 ));
642                                 }
643                         }
644                         chordLabelSelections[note_no] = new ChordLabelSelections(al);
645                 }
646         }
647         //
648         // MouseListener
649         public void mousePressed(MouseEvent e) {
650                 Component obj = e.getComponent();
651                 if( obj instanceof ChordLabel ) {
652                         ChordLabel cl = (ChordLabel)obj;
653                         Chord chord = cl.chord.clone();
654                         if( (e.getModifiersEx() & InputEvent.BUTTON3_DOWN_MASK) != 0 ) {
655                                 if( e.isShiftDown() )
656                                         chord.set(Chord.Interval.MAJOR_SEVENTH);
657                                 else
658                                         chord.set(Chord.Interval.SEVENTH);
659                         }
660                         else if( e.isShiftDown() )
661                                 chord.set(Chord.Interval.SIXTH);
662                         if( e.isControlDown() )
663                                 chord.set(Chord.Interval.NINTH);
664                         else
665                                 chord.clear(Chord.OffsetIndex.NINTH);
666
667                         if( e.isAltDown() ) {
668                                 if( cl.isSus4 ) {
669                                         chord.set(Chord.Interval.MAJOR); // To cancel sus4
670                                         chord.set(Chord.Interval.SHARP5);
671                                 }
672                                 else chord.set(Chord.Interval.FLAT5);
673                         }
674                         if( selectedChordLabel != null ) {
675                                 selectedChordLabel.setSelection(false);
676                         }
677                         (selectedChordLabel = cl).setSelection(true);
678                         setSelectedChord(chord);
679                 }
680                 else if( obj instanceof Co5Label ) {
681                         int v = ((Co5Label)obj).co5Value;
682                         if( (e.getModifiersEx() & InputEvent.BUTTON3_DOWN_MASK) != 0 ) {
683                                 setKeySignature( new Key(Music.oppositeCo5(v)) );
684                         }
685                         else if ( v == key.toCo5() ) {
686                                 //
687                                 // Cancel selected chord
688                                 //
689                                 setSelectedChord( (Chord)null );
690                         }
691                         else {
692                                 // Change key
693                                 setKeySignature( new Key(v) );
694                         }
695                 }
696                 requestFocusInWindow();
697                 repaint();
698         }
699         public void mouseReleased(MouseEvent e) { destinationChordLabel = null; }
700         public void mouseEntered(MouseEvent e) { }
701         public void mouseExited(MouseEvent e) { }
702         public void mouseClicked(MouseEvent e) { }
703         public void mouseDragged(MouseEvent e) {
704                 Component obj = e.getComponent();
705                 if( obj instanceof ChordLabel ) {
706                         ChordLabel l_src = (ChordLabel)obj;
707                         Component obj2 = this.getComponentAt(
708                                 l_src.getX() + e.getX(),
709                                 l_src.getY() + e.getY()
710                         );
711                         if( obj2 == this ) {
712                                 //
713                                 // Entered gap between chord buttons - do nothing
714                                 //
715                                 return;
716                         }
717                         ChordLabel l_dst =
718                                 ( (obj2 instanceof ChordLabel ) ? (ChordLabel)obj2 : null );
719                         if( l_dst == l_src ) {
720                                 //
721                                 // Returned to original chord button
722                                 //
723                                 destinationChordLabel = null;
724                                 return;
725                         }
726                         if( destinationChordLabel != null ) {
727                                 //
728                                 // Already touched another chord button
729                                 //
730                                 return;
731                         }
732                         Chord chord = l_src.chord.clone();
733                         if( l_src.isMinor ) {
734                                 if( l_dst == null ) { // Out of chord buttons
735                                         // mM7
736                                         chord.set(Chord.Interval.MAJOR_SEVENTH);
737                                 }
738                                 else if( l_src.co5Value < l_dst.co5Value ) { // Right
739                                         // m6
740                                         chord.set(Chord.Interval.SIXTH);
741                                 }
742                                 else { // Left or up from minor to major
743                                         // m7
744                                         chord.set(Chord.Interval.SEVENTH);
745                                 }
746                         }
747                         else if( l_src.isSus4 ) {
748                                 if( l_dst == null ) { // Out of chord buttons
749                                         return;
750                                 }
751                                 else if( ! l_dst.isSus4 ) { // Down from sus4 to major
752                                         chord.set(Chord.Interval.MAJOR);
753                                 }
754                                 else if( l_src.co5Value < l_dst.co5Value ) { // Right
755                                         chord.set(Chord.Interval.NINTH);
756                                 }
757                                 else { // Left
758                                         // 7sus4
759                                         chord.set(Chord.Interval.SEVENTH);
760                                 }
761                         }
762                         else {
763                                 if( l_dst == null ) { // Out of chord buttons
764                                         return;
765                                 }
766                                 else if( l_dst.isSus4 ) { // Up from major to sus4
767                                         chord.set(Chord.Interval.NINTH);
768                                 }
769                                 else if( l_src.co5Value < l_dst.co5Value ) { // Right
770                                         // M7
771                                         chord.set(Chord.Interval.MAJOR_SEVENTH);
772                                 }
773                                 else if( l_dst.isMinor ) { // Down from major to minor
774                                         // 6
775                                         chord.set(Chord.Interval.SIXTH);
776                                 }
777                                 else { // Left
778                                         // 7
779                                         chord.set(Chord.Interval.SEVENTH);
780                                 }
781                         }
782                         if( chord.isSet(Chord.OffsetIndex.NINTH) || (l_src.isSus4 && (l_dst == null || ! l_dst.isSus4) ) ) {
783                                 if( (e.getModifiersEx() & InputEvent.BUTTON3_DOWN_MASK) != 0 ) {
784                                         if( e.isShiftDown() ) {
785                                                 chord.set(Chord.Interval.MAJOR_SEVENTH);
786                                         }
787                                         else {
788                                                 chord.set(Chord.Interval.SEVENTH);
789                                         }
790                                 }
791                                 else if( e.isShiftDown() ) {
792                                         chord.set(Chord.Interval.SIXTH);
793                                 }
794                         }
795                         else {
796                                 if( e.isControlDown() )
797                                         chord.set(Chord.Interval.NINTH);
798                                 else
799                                         chord.clear(Chord.OffsetIndex.NINTH);
800                         }
801                         if( e.isAltDown() ) {
802                                 if( l_src.isSus4 ) {
803                                         chord.set(Chord.Interval.MAJOR);
804                                         chord.set(Chord.Interval.SHARP5);
805                                 }
806                                 else {
807                                         chord.set(Chord.Interval.FLAT5);
808                                 }
809                         }
810                         setSelectedChord(chord);
811                         destinationChordLabel = (l_dst == null ? l_src : l_dst ) ;
812                 }
813                 else if( obj instanceof Co5Label ) {
814                         Co5Label l_src = (Co5Label)obj;
815                         Component obj2 = this.getComponentAt(
816                                 l_src.getX() + e.getX(),
817                                 l_src.getY() + e.getY()
818                         );
819                         if( !(obj2 instanceof Co5Label) ) {
820                                 return;
821                         }
822                         Co5Label l_dst = (Co5Label)obj2;
823                         int v = l_dst.co5Value;
824                         if( (e.getModifiersEx() & InputEvent.BUTTON3_DOWN_MASK) != 0 ) {
825                                 setKeySignature( new Key(Music.oppositeCo5(v)) );
826                         }
827                         else {
828                                 setKeySignature( new Key(v) );
829                         }
830                         repaint();
831                 }
832         }
833         public void mouseMoved(MouseEvent e) { }
834         public void mouseWheelMoved(MouseWheelEvent e) {
835                 if( selectedChord != null ) {
836                         if( e.getWheelRotation() > 0 ) { // Wheel moved down
837                                 if( --selectedNoteIndex < 0 ) {
838                                         selectedNoteIndex = selectedChord.numberOfNotes() - 1;
839                                 }
840                         }
841                         else { // Wheel moved up
842                                 if( ++selectedNoteIndex >= selectedChord.numberOfNotes() ) {
843                                         selectedNoteIndex = 0;
844                                 }
845                         }
846                         fireChordChanged();
847                 }
848         }
849         private Chord.Interval pcKeyNextShift7;
850         public void keyPressed(KeyEvent e) {
851                 int i = -1, i_col = -1, i_row = 1;
852                 boolean shiftPressed = false; // True if Shift-key pressed or CapsLocked
853                 char keyChar = e.getKeyChar();
854                 int keyCode = e.getKeyCode();
855                 ChordLabel cl = null;
856                 Chord chord = null;
857                 int key_co5 = key.toCo5();
858                 // System.out.println( keyChar + " Pressed on chord matrix" );
859                 //
860                 if( (i = "6 ".indexOf(keyChar)) >= 0 ) {
861                         selectedChord = selectedChordCapo = null;
862                         fireChordChanged();
863                         pcKeyNextShift7 = null;
864                         return;
865                 }
866                 else if( (i = "asdfghjkl;:]".indexOf(keyChar)) >= 0 ) {
867                         i_col = i + key_co5 + 7;
868                 }
869                 else if( (i = "ASDFGHJKL+*}".indexOf(keyChar)) >= 0 ) {
870                         i_col = i + key_co5 + 7;
871                         shiftPressed = true;
872                 }
873                 else if( (i = "zxcvbnm,./\\".indexOf(keyChar)) >=0 ) {
874                         i_col = i + key_co5 + 7;
875                         i_row = 2;
876                 }
877                 else if( (i = "ZXCVBNM<>?_".indexOf(keyChar)) >=0 ) {
878                         i_col = i + key_co5 + 7;
879                         i_row = 2;
880                         shiftPressed = true;
881                 }
882                 else if( (i = "qwertyuiop@[".indexOf(keyChar)) >= 0 ) {
883                         i_col = i + key_co5 + 7;
884                         i_row = 0;
885                 }
886                 else if( (i = "QWERTYUIOP`{".indexOf(keyChar)) >= 0 ) {
887                         i_col = i + key_co5 + 7;
888                         i_row = 0;
889                         shiftPressed = true;
890                 }
891                 else if( keyChar == '5' ) {
892                         pcKeyNextShift7 = Chord.Interval.MAJOR_SEVENTH; return;
893                 }
894                 else if( keyChar == '7' ) {
895                         pcKeyNextShift7 = Chord.Interval.SEVENTH; return;
896                 }
897                 // Shift current key-signature
898                 else if( keyCode == KeyEvent.VK_LEFT || keyCode == KeyEvent.VK_KP_LEFT ) {
899                         // Add a flat
900                         setKeySignature( new Key(key_co5-1) );
901                         return;
902                 }
903                 else if( keyCode == KeyEvent.VK_RIGHT || keyCode == KeyEvent.VK_KP_RIGHT ) {
904                         // Add a sharp
905                         setKeySignature( new Key(key_co5+1) );
906                         return;
907                 }
908                 else if( keyCode == KeyEvent.VK_DOWN || keyCode == KeyEvent.VK_KP_DOWN ) {
909                         // Semitone down
910                         Key key = new Key(key_co5);
911                         key.transpose(-1);
912                         setKeySignature(key);
913                         return;
914                 }
915                 else if( keyCode == KeyEvent.VK_UP || keyCode == KeyEvent.VK_KP_UP ) {
916                         // Semitone up
917                         Key key = new Key(key_co5);
918                         key.transpose(1);
919                         setKeySignature(key);
920                         return;
921                 }
922                 if( i < 0 ) // No key char found
923                         return;
924                 if( i_col < 0 ) i_col += 12; else if( i_col > N_COLUMNS ) i_col -= 12;
925                 cl = chordLabels[i_col + N_COLUMNS * i_row];
926                 chord = cl.chord.clone();
927                 if( shiftPressed ) {
928                         chord.set(Chord.Interval.SEVENTH);
929                 }
930                 // specify by previous key
931                 else if( pcKeyNextShift7 == null ) {
932                         chord.clear(Chord.OffsetIndex.SEVENTH);
933                 }
934                 else {
935                         chord.set(pcKeyNextShift7);
936                 }
937                 if( e.isAltDown() ) {
938                         if( cl.isSus4 ) {
939                                 chord.set(Chord.Interval.MAJOR); // To cancel sus4
940                                 chord.set(Chord.Interval.SHARP5);
941                         }
942                         else {
943                                 chord.set(Chord.Interval.FLAT5);
944                         }
945                 }
946                 if( e.isControlDown() ) { // Cannot use for ninth ?
947                         chord.set(Chord.Interval.NINTH);
948                 }
949                 if( selectedChordLabel != null ) clear();
950                 (selectedChordLabel = cl).setSelection(true);
951                 setSelectedChord(chord);
952                 pcKeyNextShift7 = null;
953                 return;
954         }
955         public void keyReleased(KeyEvent e) { }
956         public void keyTyped(KeyEvent e) { }
957
958         public void addChordMatrixListener(ChordMatrixListener l) {
959                 listenerList.add(ChordMatrixListener.class, l);
960         }
961         public void removeChordMatrixListener(ChordMatrixListener l) {
962                 listenerList.remove(ChordMatrixListener.class, l);
963         }
964         protected void fireChordChanged() {
965                 Object[] listeners = listenerList.getListenerList();
966                 for (int i = listeners.length-2; i>=0; i-=2) {
967                         if (listeners[i]==ChordMatrixListener.class) {
968                                 ((ChordMatrixListener)listeners[i+1]).chordChanged();
969                         }
970                 }
971                 if( selectedChord == null ) clearIndicators();
972         }
973         public void fireKeySignatureChanged() {
974                 Object[] listeners = listenerList.getListenerList();
975                 for (int i = listeners.length-2; i>=0; i-=2) {
976                         if (listeners[i]==ChordMatrixListener.class) {
977                                 ((ChordMatrixListener)listeners[i+1]).keySignatureChanged();
978                         }
979                 }
980         }
981         private Key key = null;
982         private Key capoKey = null;
983         public Key getKeySignature() { return key; }
984         public Key getKeySignatureCapo() { return capoKey; }
985         public void setKeySignature( Key key ) {
986                 if( key == null || this.key != null && key.equals(this.key) )
987                         return;
988                 int i;
989                 // Clear old value
990                 if( this.key == null ) {
991                         for( i = 0; i < keysigLabels.length; i++ ) {
992                                 keysigLabels[i].setBackground(false);
993                         }
994                 }
995                 else {
996                         keysigLabels[this.key.toCo5() + 12].setSelection(false);
997                         for( i = Music.mod12(this.key.toCo5()); i < N_COLUMNS; i+=12 ) {
998                                 keysigLabels[i].setBackground(false);
999                         }
1000                 }
1001                 // Set new value
1002                 keysigLabels[i = key.toCo5() + 12].setSelection(true);
1003                 for( i = Music.mod12(key.toCo5()); i < N_COLUMNS; i+=12 ) {
1004                         keysigLabels[i].setBackground(true);
1005                 }
1006                 // Change chord-label's color & font
1007                 int i_color, old_i_color;
1008                 for( ChordLabel cl : chordLabels ) {
1009                         i_color = ((cl.co5Value - key.toCo5() + 31)/3) & 3;
1010                         if( this.key != null ) {
1011                                 old_i_color = ((cl.co5Value - this.key.toCo5() + 31)/3) & 3;
1012                                 if( i_color != old_i_color ) {
1013                                         cl.setBackground(i_color);
1014                                 }
1015                         }
1016                         else cl.setBackground(i_color);
1017                         if( !(cl.isSus4) ) {
1018                                 if( this.key != null && Music.mod12(cl.co5Value - this.key.toCo5()) == 0)
1019                                         cl.setBold(false);
1020                                 if( Music.mod12( cl.co5Value - key.toCo5() ) == 0 )
1021                                         cl.setBold(true);
1022                         }
1023                 }
1024                 this.capoKey = (this.key = key).clone().transpose(capoSelecter.getCapo());
1025                 for( ChordLabel cl : chordLabels ) cl.keyChanged();
1026                 fireKeySignatureChanged();
1027         }
1028         private int capo = 0;
1029         /**
1030          * カポ位置の変更処理
1031          * @param newCapo 新しいカポ位置
1032          */
1033         protected void capoChanged(int newCapo) {
1034                 if( this.capo == newCapo )
1035                         return;
1036                 (this.capoKey = this.key.clone()).transpose(this.capo = newCapo);
1037                 selectedChordCapo = (
1038                         selectedChord == null ? null : selectedChord.clone().transpose(newCapo)
1039                 );
1040                 for( ChordLabel cl : chordLabels ) cl.keyChanged();
1041                 fireKeySignatureChanged();
1042         }
1043
1044         /**
1045          * コードサフィックスのヘルプ
1046          */
1047         public ChordGuide chordGuide = new ChordGuide(this);
1048
1049         /**
1050          * ドラッグ先コードボタン
1051          */
1052         private ChordLabel      destinationChordLabel = null;
1053         /**
1054          * ドラッグされたかどうか調べます。
1055          * @return ドラッグ先コードボタンがあればtrue
1056          */
1057         public boolean isDragged() {
1058                 return destinationChordLabel != null ;
1059         }
1060
1061         private boolean isDark = false;
1062         public void setDarkMode(boolean is_dark) {
1063                 this.isDark = is_dark;
1064                 currentColorset = (is_dark ? darkModeColorset : normalModeColorset);
1065                 setBackground( currentColorset.focus[0] );
1066                 Key prev_key = key;
1067                 key = null;
1068                 setKeySignature(prev_key);
1069                 for( int i=0; i < keysigLabels.length; i++ ) keysigLabels[i].setSelection();
1070                 for( int i=0; i <  chordLabels.length; i++ ) chordLabels[i].setSelection();
1071                 chordGuide.setDarkMode( is_dark );
1072                 chordDisplay.setDarkMode( is_dark );
1073                 Color col = is_dark ? Color.black : null;
1074                 capoSelecter.setBackground( col );
1075                 capoSelecter.valueSelecter.setBackground( col );
1076         }
1077
1078         private boolean isPlaying = false;
1079         public boolean isPlaying() { return isPlaying; }
1080         public void setPlaying(boolean is_playing) {
1081                 this.isPlaying = is_playing;
1082                 repaint();
1083         }
1084
1085         private byte currentBeat = 0;
1086         private byte timesigUpper = 4;
1087         public void setBeat(SequenceTickIndex sequenceTickIndex) {
1088                 byte beat = (byte)(sequenceTickIndex.lastBeat);
1089                 byte tsu = sequenceTickIndex.timesigUpper;
1090                 if( currentBeat == beat && timesigUpper == tsu )
1091                         return;
1092                 timesigUpper = tsu;
1093                 currentBeat = beat;
1094                 keysigLabels[ key.toCo5() + 12 ].repaint();
1095         }
1096
1097         private ChordLabel      selectedChordLabel = null;
1098         public JComponent getSelectedButton() {
1099                 return selectedChordLabel;
1100         }
1101         private Chord   selectedChord = null;
1102         public Chord getSelectedChord() {
1103                 return selectedChord;
1104         }
1105         private Chord   selectedChordCapo = null;
1106         public Chord getSelectedChordCapo() {
1107                 return selectedChordCapo;
1108         }
1109         public void setSelectedChordCapo( Chord chord ) {
1110                 setNoteIndex(-1); // Cancel arpeggio mode
1111                 selectedChord = (chord == null ? null : chord.clone().transpose(-capo,capoKey));
1112                 selectedChordCapo = chord;
1113                 fireChordChanged();
1114         }
1115         public void setSelectedChord( Chord chord ) {
1116                 setNoteIndex(-1); // Cancel arpeggio mode
1117                 selectedChord = chord;
1118                 selectedChordCapo = (chord == null ? null : chord.clone().transpose(capo,key));
1119                 fireChordChanged();
1120         }
1121         /**
1122          * コードを文字列で設定します。
1123          * @param chordSymbol コード名
1124          */
1125         public void setSelectedChord(String chordSymbol) throws IllegalArgumentException {
1126                 Chord chord = null;
1127                 if( chordSymbol != null && ! chordSymbol.isEmpty() ) {
1128                         try {
1129                                 chord = new Chord(chordSymbol);
1130                         } catch( IllegalArgumentException e ) {
1131                                 JOptionPane.showMessageDialog(
1132                                         null, e.getMessage(), "Input error",
1133                                         JOptionPane.ERROR_MESSAGE
1134                                 );
1135                                 return;
1136                         }
1137                 }
1138                 setSelectedChord(chord);
1139         }
1140
1141         private int selectedNoteIndex = -1;
1142         public int getNoteIndex() {
1143                 return selectedChord == null || selectedNoteIndex < 0 ? -1 : selectedNoteIndex;
1144         }
1145         public void setNoteIndex(int noteIndex) {
1146                 selectedNoteIndex = noteIndex;
1147         }
1148         public void clear() {
1149                 if( selectedChordLabel != null ) {
1150                         selectedChordLabel.setSelection(false);
1151                         selectedChordLabel = null;
1152                 }
1153                 selectedChord = null; selectedNoteIndex = -1;
1154         }
1155
1156 }