KeyMapEditor.java
     1: //========================================================================================
     2: //  KeyMapEditor.java
     3: //    en:Key map editor
     4: //    ja:キーマップエディタ
     5: //  Copyright (C) 2003-2024 Makoto Kamada
     6: //
     7: //  This file is part of the XEiJ (X68000 Emulator in Java).
     8: //  You can use, modify and redistribute the XEiJ if the conditions are met.
     9: //  Read the XEiJ License for more details.
    10: //  https://stdkmd.net/xeij/
    11: //========================================================================================
    12: 
    13: package xeij;
    14: 
    15: import java.awt.*;
    16: import java.awt.event.*;
    17: import java.awt.im.*;  //InputContext
    18: import java.io.*;
    19: import java.util.*;
    20: import javax.swing.*;
    21: 
    22: public class KeyMapEditor implements KeyListener {
    23: 
    24:   //定数
    25:   public static final Font FONT = new Font ("SansSerif", Font.PLAIN, 12);  //10
    26:   public static final int LABEL_HEIGHT = 14;  //12
    27:   public static final int COL_WIDTH = 14;  //12  列の幅(px)。可変キーの幅の1/4
    28:   public static final int ROW_HEIGHT = 18;  //15  15*4=12+12*4  行の高さ(px)。可変キーの高さの1/4
    29:   public static final int COLS = 94;  //列数
    30:   public static final int ROWS = 25;  //行数
    31:   public static final int PADDING_TOP = 10;  //パディング(px)
    32:   public static final int PADDING_BOTTOM = 10;
    33:   public static final int PADDING_LEFT = 10;
    34:   public static final int PADDING_RIGHT = 10;
    35:   public static final int KEYBOARD_WIDTH = PADDING_LEFT + COL_WIDTH * COLS + PADDING_RIGHT;
    36:   public static final int KEYBOARD_HEIGHT = PADDING_TOP + ROW_HEIGHT * ROWS + PADDING_BOTTOM;
    37:   public static final int KEYS = 113;  //キーの数
    38:   public static final int BOXES = 114;  //箱の数。右SHIFTを追加
    39:   public static final int TAB = 15;  //TAB
    40:   public static final int LEFT_SHIFT = 108;  //左SHIFT
    41:   public static final int RIGHT_SHIFT = 113;  //右SHIFT
    42: 
    43:   //色
    44:   public static Color backgroundColor;  //キーの背景色
    45:   public static Color assignedColor;  //文字が割り当てられたキーの背景色
    46:   public static Color focusedColor;  //フォーカスされたキーの背景色
    47:   public static Color foregroundColor;  //キーの文字色
    48: 
    49:   //パネル
    50:   public JPanel keyboardPanel;
    51:   public JComponent mainPanel;
    52: 
    53:   //マップ
    54:   public int[] currentMap;  //現在のキーマップ。3*KEYS個
    55:   public int focusedBox;  //フォーカスが当たっている箱
    56:   public JTextArea[] textAreaArray;  //箱のテキストエリア
    57: 
    58:   //保存と復元
    59:   public static javax.swing.filechooser.FileFilter txtFileFilter;
    60:   public static File lastFile;
    61: 
    62:   //取り消しとやり直し
    63:   public static LinkedList<int[]> undoList;  //取り消しリスト
    64:   public static LinkedList<int[]> redoList;  //やり直しリスト
    65:   public static final int UNDO_LIST_MAX_LENGTH = 1000;  //取り消しリストの長さの上限
    66: 
    67:   //コンストラクタ
    68:   @SuppressWarnings ("this-escape") public KeyMapEditor (int[] map) {
    69: 
    70:     //マップ
    71:     currentMap = map;
    72: 
    73:     //保存と復元
    74:     txtFileFilter = new javax.swing.filechooser.FileFilter () {  //java.io.FileFilterと紛らわしい
    75:       @Override public boolean accept (File file) {
    76:         String name = file.getName ();
    77:         String lowerName = name.toLowerCase ();
    78:         return (file.isDirectory () ||
    79:                 (file.isFile () &&
    80:                  (lowerName.endsWith (".csv") ||
    81:                   lowerName.endsWith (".txt"))));
    82:       }
    83:       @Override public String getDescription () {
    84:         return (Multilingual.mlnJapanese ?
    85:                 "CSV またはテキストファイル (*.csv,*.txt)" :
    86:                 "CSV or text file (*.csv,*.txt)");
    87:       }
    88:     };
    89:     lastFile = new File ("keymap.csv").getAbsoluteFile ();
    90: 
    91:     //取り消しとやり直し
    92:     undoList = new LinkedList<int[]> ();
    93:     redoList = new LinkedList<int[]> ();
    94: 
    95:     //色
    96:     backgroundColor = new Color (LnF.lnfRGB[0]);
    97:     assignedColor = new Color (LnF.lnfRGB[4]);
    98:     focusedColor = new Color (LnF.lnfRGB[8]);
    99:     foregroundColor = Color.white;
   100: 
   101:     //パネル
   102:     keyboardPanel = new JPanel ();
   103:     keyboardPanel.setLayout (null);
   104:     keyboardPanel.setPreferredSize (new Dimension (KEYBOARD_WIDTH, KEYBOARD_HEIGHT));
   105: 
   106:     //フォーカス
   107:     focusedBox = -1;
   108:     FocusListener focusListener = new FocusAdapter () {
   109:       @Override public void focusGained (FocusEvent fe) {
   110:         int xo = Integer.parseInt (fe.getComponent ().getName ());
   111:         if (focusedBox != -1) {
   112:           Color color = currentMap[3 * xo] != 0 ? assignedColor : backgroundColor;
   113:           textAreaArray[focusedBox].setBackground (color);
   114:           if (focusedBox == LEFT_SHIFT) {
   115:             textAreaArray[RIGHT_SHIFT].setBackground (color);
   116:           }
   117:           //focusedBox = -1;
   118:         }
   119:         focusedBox = xo;
   120:         textAreaArray[focusedBox].setBackground (focusedColor);
   121:         if (focusedBox == LEFT_SHIFT) {
   122:           textAreaArray[RIGHT_SHIFT].setBackground (focusedColor);
   123:         }
   124:       }
   125:       @Override public void focusLost (FocusEvent fe) {
   126:         int xo = Integer.parseInt (fe.getComponent ().getName ());
   127:         if (focusedBox != -1) {
   128:           Color color = currentMap[3 * xo] != 0 ? assignedColor : backgroundColor;
   129:           textAreaArray[focusedBox].setBackground (color);
   130:           if (focusedBox == LEFT_SHIFT) {
   131:             textAreaArray[RIGHT_SHIFT].setBackground (color);
   132:           }
   133:           focusedBox = -1;
   134:         }
   135:       }
   136:     };
   137: 
   138:     //キーマップ
   139:     textAreaArray = new JTextArea[BOXES];
   140:     for (int xo = 0; xo < BOXES; xo++) {
   141:       textAreaArray[xo] = null;
   142:       int[] bounds = BOUNDS_ARRAY[xo];
   143:       JLabel label = ComponentFactory.createLabel (TEXT_ARRAY[xo]);
   144:       label.setFont (FONT);
   145:       label.setBounds (PADDING_LEFT + COL_WIDTH * bounds[0],
   146:                        PADDING_TOP + ROW_HEIGHT * bounds[1],
   147:                        COL_WIDTH * bounds[2],
   148:                        LABEL_HEIGHT);
   149:       label.setHorizontalAlignment (SwingConstants.CENTER);
   150:       keyboardPanel.add (label);
   151:       JTextArea textArea = new JTextArea ();
   152:       textAreaArray[xo] = textArea;
   153:       textArea.setFont (FONT);
   154:       ComponentFactory.setEtchedBorder (textArea);  //枠を描く
   155:       textArea.setLineWrap (true);  //折り返す
   156:       //textArea.setEditable (false);  //編集不可。キャレットを表示しない。キーイベントが発生しなくなる環境がある?
   157:       textArea.setBackground (backgroundColor);
   158:       textArea.setForeground (foregroundColor);
   159:       textArea.setCursor (Cursor.getPredefinedCursor (Cursor.HAND_CURSOR));
   160:       textArea.setName (String.valueOf (xo == RIGHT_SHIFT ? LEFT_SHIFT : xo));  //右SHIFTに左SHIFTの番号を入れる
   161:       textArea.setBounds (PADDING_LEFT + COL_WIDTH * bounds[0],
   162:                           PADDING_TOP + ROW_HEIGHT * bounds[1] + LABEL_HEIGHT,
   163:                           COL_WIDTH * bounds[2],
   164:                           ROW_HEIGHT * bounds[3] - LABEL_HEIGHT);
   165:       if (xo == TAB) {
   166:         textArea.setFocusTraversalKeysEnabled (false);  //Tabを入力できる
   167:       } else {
   168:         textArea.setFocusTraversalKeysEnabled (true);  //Tabで次のキーに移る
   169:       }
   170:       textArea.addKeyListener (this);  //[this-escape]
   171:       textArea.addFocusListener (focusListener);
   172:       keyboardPanel.add (textArea);
   173:     }  //for xo
   174:     updateTextAll ();
   175: 
   176:     //パネル
   177:     mainPanel = new JScrollPane (keyboardPanel);
   178:     //mainPanel.setPreferredSize (new Dimension (KEYBOARD_WIDTH + 3, KEYBOARD_HEIGHT + 3));
   179:     mainPanel.setPreferredSize (new Dimension (Math.min (700, KEYBOARD_WIDTH + 20), KEYBOARD_HEIGHT + 20));
   180: 
   181:   }  //コンストラクタ
   182: 
   183:   //パネルを取得する
   184:   public JComponent getPanel () {
   185:     return mainPanel;
   186:   }  //getPanel
   187: 
   188:   //白紙にする
   189:   public void blank () {
   190:     if (JOptionPane.showConfirmDialog (
   191:       null,
   192:       Multilingual.mlnJapanese ? "キー割り当てを白紙にしますか?" : "Do you want to blank the key assignments?",
   193:       Multilingual.mlnJapanese ? "確認" : "Confirmation",
   194:       JOptionPane.YES_NO_OPTION,
   195:       JOptionPane.PLAIN_MESSAGE) != JOptionPane.YES_OPTION) {
   196:       return;
   197:     }
   198:     beforeChange ();
   199:     Arrays.fill (currentMap, 0);  //array,value
   200:     updateTextAll ();
   201:   }  //blank
   202: 
   203:   //初期値に戻す
   204:   public void reset (int[] map) {
   205:     if (JOptionPane.showConfirmDialog (
   206:       null,
   207:       Multilingual.mlnJapanese ? "キー割り当てを初期値に戻しますか?" : "Do you want to reset the key assignments to default?",
   208:       Multilingual.mlnJapanese ? "確認" : "Confirmation",
   209:       JOptionPane.YES_NO_OPTION,
   210:       JOptionPane.PLAIN_MESSAGE) != JOptionPane.YES_OPTION) {
   211:       return;
   212:     }
   213:     beforeChange ();
   214:     System.arraycopy (map, 0,  //from
   215:                       currentMap, 0,  //to
   216:                       currentMap.length);  //length
   217:     updateTextAll ();
   218:   }  //reset
   219: 
   220:   //保存する
   221:   public void save () {
   222:     JFileChooser2 fileChooser = new JFileChooser2 (lastFile);
   223:     fileChooser.setFileFilter (txtFileFilter);
   224:     if (fileChooser.showSaveDialog (null) == JFileChooser.APPROVE_OPTION) {
   225:       File file = fileChooser.getSelectedFile ();
   226:       String path = file.getPath ();
   227:       String lowerPath = path.toLowerCase ();
   228:       if (lowerPath.endsWith (".csv")) {
   229:         saveCSV (path);
   230:         file = lastFile;
   231:       } else if (lowerPath.endsWith (".txt")) {
   232:         saveText (path);
   233:         file = lastFile;
   234:       }
   235:     }
   236:   }  //save
   237: 
   238:   //CSV形式で保存する
   239:   public void saveCSV (String path) {
   240:     StringBuilder sb = new StringBuilder ();
   241:     sb.append (XEiJ.prgIsMac ?
   242:                "X68000,keyCode,extendedKeyCode,keyLocation,keytop\r\n" :
   243:                "X68000,keyCode,rawCode,keyLocation,keytop\r\n");
   244:     for (int xo = 0; xo < KEYS; xo++) {
   245:       for (int i = 0; i < 3; i++) {
   246:         int t = currentMap[3 * xo + i];
   247:         if (t == 0) {
   248:           break;
   249:         }
   250:         sb.append (xo + (xo < 108 ? 1 : 4));
   251:         sb.append (",");
   252:         sb.append ((t >> 16) & (XEiJ.prgIsMac ? 0x00000fff : 0x0000ffff));
   253:         sb.append (",");
   254:         sb.append ((t >> 4) & (XEiJ.prgIsMac ? 0x0f000fff : 0x00000fff));
   255:         sb.append (",");
   256:         sb.append (t & 0xf);
   257:         sb.append (",【");  //全角でも"~"で囲んでも数字は数値と見なされる
   258:         sb.append (TEXT_ARRAY[xo]);
   259:         sb.append ("】\r\n");
   260:       }
   261:     }
   262:     XEiJ.rscPutTextFile (path, sb.toString (), "cp932");  //Shift_JISは不可
   263:   }  //saveCSV
   264: 
   265:   //テキスト形式で保存する
   266:   public void saveText (String path) {
   267:     StringBuilder sb = new StringBuilder ();
   268:     int length = currentMap.length;
   269:     while (0 < length && currentMap[length - 1] == 0) {
   270:       length--;
   271:     }
   272:     sb.append ("keymap=-3");
   273:     for (int i = 0; i < length; i++) {
   274:       sb.append (',');
   275:       if (currentMap[i] != 0) {
   276:         sb.append (currentMap[i]);
   277:       }
   278:     }
   279:     sb.append ("\n");
   280:     XEiJ.rscPutTextFile (path, sb.toString ());
   281:   }  //saveText
   282: 
   283:   //復元する
   284:   public void restore () {
   285:     JFileChooser2 fileChooser = new JFileChooser2 (lastFile);
   286:     fileChooser.setFileFilter (txtFileFilter);
   287:     if (fileChooser.showOpenDialog (null) != JFileChooser.APPROVE_OPTION) {
   288:       return;
   289:     }
   290:     File file = fileChooser.getSelectedFile ();
   291:     String path = file.getPath ();
   292:     String lowerPath = path.toLowerCase ();
   293:     if (lowerPath.endsWith (".csv")) {
   294:       if (restoreCSV (path)) {
   295:         lastFile = file;
   296:       }
   297:     } else if (lowerPath.endsWith (".txt")) {
   298:       if (restoreText (path)) {
   299:         lastFile = file;
   300:       }
   301:     }
   302:   }  //restore
   303: 
   304:   //CSV形式で復元する
   305:   public boolean restoreCSV (String path) {
   306:     String string = XEiJ.rscGetTextFile (path, "cp932");  //Shift_JISは不可
   307:     if (string.length () == 0) {  //読み込めないまたは空
   308:       return false;
   309:     }
   310:     String[] lines;
   311:     if (0 <= string.indexOf ("\r\n")) {
   312:       lines = string.split ("\r\n");
   313:     } else if (0 <= string.indexOf ("\n")) {
   314:       lines = string.split ("\n");
   315:     } else if (0 <= string.indexOf ("\r")) {
   316:       lines = string.split ("\r");
   317:     } else {
   318:       lines = new String[] { string };
   319:     }
   320:     int rows = lines.length;
   321:     if (rows < 1) {  //ヘッダがない
   322:       return false;
   323:     }
   324:     int[] map = new int[3 * KEYS];
   325:     Arrays.fill (map, 0);
   326:     for (int row = 0; row < rows; row++) {
   327:       String[] line = lines[row].split (",");  //!!!"~"で囲まれた","があると分割してしまう
   328:       int cols = line.length;
   329:       if (cols < 4) {  //列数が足りない
   330:         return false;
   331:       }
   332:       if (row == 0) {  //ヘッダ
   333:         String cell = line[0];
   334:         if (cell.startsWith ("\"") && cell.endsWith ("\"")) {  //"~"で囲まれている。!!!"\"\""→"\""が必要
   335:           cell = cell.substring (1, cell.length () - 1);  //start,end
   336:         }
   337:         cell = cell.trim ();
   338:         if (!cell.equals ("X68000")) {  //ヘッダの1列目がX68000でない
   339:           return false;
   340:         }
   341:         continue;
   342:       }
   343:       int[] va = new int[4];
   344:       for (int col = 0; col < 4; col++) {
   345:         String cell = line[col];
   346:         if (cell.startsWith ("\"") && cell.endsWith ("\"")) {  //"~"で囲まれている。!!!"\"\""→"\""が必要
   347:           cell = cell.substring (1, cell.length () - 1);  //start,end
   348:         }
   349:         cell = cell.trim ();
   350:         try {
   351:           va[col] = Integer.parseInt (cell, 10);
   352:         } catch (NumberFormatException nfe) {  //intに変換できない
   353:           return false;
   354:         }
   355:       }
   356:       int v0 = va[0];
   357:       int v1 = va[1];
   358:       int v2 = va[2];
   359:       int v3 = va[3];
   360:       if (!(((1 <= v0 && v0 <= 108) || (112 <= v0 && v0 <= 116)) &&
   361:             (v1 & 0xfff) == v1 &&
   362:             (v2 & 0xf000fff) == v2 &&
   363:             (v3 & 0xf) == v3)) {  //値が範囲外
   364:         return false;
   365:       }
   366:       int t = v1 << 16 | v2 << 4 | v3;
   367:       if (t == 0) {  //0は割り当てられない
   368:         return false;
   369:       }
   370:       int xo = v0 - (v0 < 112 ? 1 : 4);
   371:       if (map[3 * xo] == 0) {  //1個目
   372:         map[3 * xo] = t;
   373:       } else if (map[3 * xo + 1] == 0) {  //2個目
   374:         map[3 * xo + 1] = t;
   375:       } else if (map[3 * xo + 2] == 0) {  //3個目
   376:         map[3 * xo + 2] = t;
   377:       } else {  //同じキーの割り当てが多すぎる
   378:         return false;
   379:       }
   380:     }
   381:     beforeChange ();
   382:     System.arraycopy (map, 0, currentMap, 0, map.length);
   383:     updateTextAll ();
   384:     return true;
   385:   }  //restoreCSV
   386: 
   387:   //テキスト形式で復元する
   388:   public boolean restoreText (String path) {
   389:     String string = XEiJ.rscGetTextFile (path);
   390:     if (string.length () == 0) {  //読み込めないか空
   391:       return false;
   392:     }
   393:     String[] lr = string.split ("=");
   394:     if (lr.length != 2) {  //左辺=右辺でない
   395:       return false;
   396:     }
   397:     String l = lr[0].trim ();
   398:     String r = lr[1].trim ();
   399:     if (!l.equals ("keymap")) {  //左辺がkeymapでない
   400:       return false;
   401:     }
   402:     String[] sa = r.split (",");
   403:     int[] ia = new int[sa.length];
   404:     for (int i = 0; i < sa.length; i++) {
   405:       String s = sa[i].trim ();  //前後の空白を取り除く
   406:       if (s.length () == 0) {  //""は0と見なす
   407:         ia[i] = 0;
   408:       } else {
   409:         try {
   410:           ia[i] = Integer.parseInt (s, 10);
   411:         } catch (NumberFormatException nfe) {  //intに変換できない
   412:           return false;
   413:         }
   414:       }
   415:     }
   416:     if (ia.length < 1 ||  //要素が足りないか
   417:         1 + 3 * KEYS < ia.length ||  //多すぎるか
   418:         ia[0] != -3) {  //-3で始まっていない
   419:       return false;
   420:     }
   421:     int[] map = new int[3 * KEYS];
   422:     Arrays.fill (map, 0);
   423:     for (int i = 0; i < 3 * KEYS; i++) {
   424:       if (1 + i < ia.length) {
   425:         map[i] = ia[1 + i];
   426:       }
   427:     }
   428:     beforeChange ();
   429:     System.arraycopy (map, 0, currentMap, 0, map.length);
   430:     updateTextAll ();
   431:     return true;
   432:   }  //restoreText
   433: 
   434:   //取り消す
   435:   public void undo () {
   436:     if (!undoList.isEmpty ()) {  //取り消しリストが空でないとき
   437:       int[] map = new int[currentMap.length];
   438:       System.arraycopy (currentMap, 0, map, 0, currentMap.length);  //現在のマップをコピーして
   439:       redoList.addFirst (map);  //やり直しリストの先頭に追加する
   440:       map = undoList.removeLast ();  //取り消しリストの末尾を削除して
   441:       System.arraycopy (map, 0, currentMap, 0, currentMap.length);  //現在のマップにコピーする
   442:       updateTextAll ();
   443:     }
   444:   }  //undo
   445: 
   446:   //やり直す
   447:   public void redo () {
   448:     if (!redoList.isEmpty ()) {  //やり直しリストが空でないとき
   449:       int[] map = new int[currentMap.length];
   450:       System.arraycopy (currentMap, 0, map, 0, currentMap.length);  //現在のマップをコピーして
   451:       undoList.addLast (map);  //取り消しリストの末尾に追加する
   452:       map = redoList.removeFirst ();  //やり直しリストの先頭を削除して
   453:       System.arraycopy (map, 0, currentMap, 0, currentMap.length);  //現在のマップにコピーする
   454:       updateTextAll ();
   455:     }
   456:   }  //redo
   457: 
   458: 
   459: 
   460:   //変更前
   461:   public void beforeChange () {
   462:     if (undoList.size () == UNDO_LIST_MAX_LENGTH) {  //取り消しリストの長さが上限のとき
   463:       undoList.removeFirst (); //取り消しリストの先頭を削除する
   464:     }
   465:     redoList.clear ();  //やり直しリストを空にする
   466:     int[] map = new int[currentMap.length];
   467:     System.arraycopy (currentMap, 0, map, 0, currentMap.length);  //現在のマップをコピーして
   468:     undoList.addLast (map);  //取り消しリストの末尾に追加する
   469:   }  //beforeChange
   470: 
   471: 
   472: 
   473:   //キーリスナー
   474:   @Override public void keyPressed (KeyEvent ke) {
   475:     closeIME (ke);
   476:   pressed:
   477:     {
   478:       beforeChange ();
   479:       int xo = Integer.parseInt (ke.getComponent ().getName ());
   480:       int keyCode = ke.getKeyCode ();
   481:       if (true) {
   482:         if (xo != 0 && keyCode == KeyEvent.VK_ESCAPE) {  //ESC以外でEscが押された
   483:           currentMap[3 * xo] = 0;  //全部消す
   484:           currentMap[3 * xo + 1] = 0;
   485:           currentMap[3 * xo + 2] = 0;
   486:           updateText (xo);
   487:           break pressed;
   488:         }
   489:       }
   490:       if ((xo != 108 && keyCode == KeyEvent.VK_SHIFT) ||  //SHIFT以外でShiftが押された
   491:           (xo != 109 && keyCode == KeyEvent.VK_CONTROL)) {  //CTRL以外でCtrlが押された
   492:         break pressed;
   493:       }
   494:       int keyLocation = ke.getKeyLocation ();
   495:       int extendedOrRaw = XEiJ.prgIsMac ? ke.getExtendedKeyCode () : getRawCode (ke);
   496:       int intCode = keyCode << 16 | extendedOrRaw << 4 | keyLocation;
   497:       if ((keyCode & (XEiJ.prgIsMac ? 0x00000fff : 0x0000ffff)) != keyCode ||
   498:           (extendedOrRaw & (XEiJ.prgIsMac ? 0x0f000fff : 0x00000fff)) != extendedOrRaw ||
   499:           (keyLocation & 0x0000000f) != keyLocation ||
   500:           intCode == 0) {  //範囲外
   501:         System.out.printf ("KeyEvent: keyCode=0x%08x, extendedOrRaw=0x%08x, keyLocation=0x%08x\n",
   502:                            keyCode, extendedOrRaw, keyLocation);
   503:         break pressed;
   504:       }
   505:       if (false) {
   506:         if (currentMap[3 * xo] == intCode &&  //1個目にある
   507:             currentMap[3 * xo + 1] == 0) {  //2個目がない
   508:           currentMap[3 * xo] = 0;  //1個目を消す
   509:           updateText (xo);
   510:           break pressed;
   511:         }
   512:       }
   513:       if (currentMap[3 * xo] == intCode ||  //1個目にある
   514:           currentMap[3 * xo + 1] == intCode ||  //2個目にある
   515:           currentMap[3 * xo + 2] == intCode) {  //3個目にある
   516:         currentMap[3 * xo] = intCode;  //1個目にする
   517:         currentMap[3 * xo + 1] = 0;  //2個目を消す
   518:         currentMap[3 * xo + 2] = 0;  //3個目を消す
   519:         updateText (xo);
   520:         break pressed;
   521:       }
   522:       if (currentMap[3 * xo + 2] != 0) {  //3個目があるとき1個目を消して詰める
   523:         currentMap[3 * xo] = currentMap[3 * xo + 1];
   524:         currentMap[3 * xo + 1] = currentMap[3 * xo + 2];
   525:         currentMap[3 * xo + 2] = 0;
   526:       }
   527:       if (currentMap[3 * xo] == 0) {  //1個目がないとき1個目にする
   528:         currentMap[3 * xo] = intCode;
   529:       } else if (currentMap[3 * xo + 1] == 0) {  //2個目がないとき2個目にする
   530:         currentMap[3 * xo + 1] = intCode;
   531:       } else {  //3個目にする
   532:         currentMap[3 * xo + 2] = intCode;
   533:       }
   534:       updateText (xo);
   535:       for (int xp = 0; xp < KEYS; xp++) {
   536:         if (xp != xo) {  //他のキーについて
   537:           if (currentMap[3 * xp] == intCode) {  //1個目にあるとき1個目を消して詰める
   538:             currentMap[3 * xp] = currentMap[3 * xp + 1];
   539:             currentMap[3 * xp + 1] = currentMap[3 * xp + 2];
   540:             currentMap[3 * xp + 2] = 0;
   541:             updateText (xp);
   542:             break;
   543:           }
   544:           if (currentMap[3 * xp + 1] == intCode) {  //2個目にあるとき2個目を消して詰める
   545:             currentMap[3 * xp + 1] = currentMap[3 * xp + 2];
   546:             currentMap[3 * xp + 2] = 0;
   547:             updateText (xp);
   548:             break;
   549:           }
   550:           if (currentMap[3 * xp + 2] == intCode) {  //3個目にあるとき3個目を消す
   551:             currentMap[3 * xp + 2] = 0;
   552:             updateText (xp);
   553:             break;
   554:           }
   555:         }
   556:       }  //for xp
   557:     }  //pressed
   558:     ke.consume ();
   559:   }  //keyPressed
   560: 
   561:   @Override public void keyReleased (KeyEvent ke) {
   562:     closeIME (ke);
   563:     ke.consume ();
   564:   }  //keyReleased
   565: 
   566:   @Override public void keyTyped (KeyEvent ke) {
   567:     closeIME (ke);
   568:     ke.consume ();
   569:   }  //keyTyped
   570: 
   571:   public void closeIME (KeyEvent ke) {
   572:     JTextArea textArea = (JTextArea) ke.getComponent ();
   573:     try {
   574:       InputContext context = textArea.getInputContext ();
   575:       if (context != null && context.isCompositionEnabled ()) {
   576:         context.setCompositionEnabled (false);
   577:         //context.setCharacterSubsets (null);
   578:       }
   579:     } catch (UnsupportedOperationException uoe) {
   580:     }
   581:   }
   582: 
   583:   public void updateTextAll () {
   584:     for (int xo = 0; xo < KEYS; xo++) {
   585:       updateText (xo);
   586:     }
   587:   }  //updateTextAll
   588: 
   589:   //キーの文字列を更新する
   590:   public void updateText (int xo) {
   591:     StringBuilder sb = new StringBuilder ();
   592:     for (int i = 0; i < 3; i++) {
   593:       int intCode = currentMap[3 * xo + i];
   594:       if (intCode == 0) {
   595:         if (i == 0) {
   596:           //sb.append (Multilingual.mlnJapanese ? "なし" : "none");
   597:         }
   598:         break;
   599:       }
   600:       if (i != 0) {
   601:         //sb.append (Multilingual.mlnJapanese ? " または " : " or ");
   602:         //sb.append ("\n");
   603:         sb.append (" ");
   604:       }
   605:       int keyCode = (intCode >> 16) & (XEiJ.prgIsMac ? 0x00000fff : 0x0000ffff);
   606:       int extendedOrRaw = (intCode >> 4) & (XEiJ.prgIsMac ? 0x0f000fff : 0x00000fff);
   607:       int keyLocation = intCode & 0x0000000f;
   608:       switch (keyLocation) {
   609:       case 2:  //LEFT
   610:         sb.append (Multilingual.mlnJapanese ? "左" : "Left ");
   611:         break;
   612:       case 3:  //RIGHT
   613:         sb.append (Multilingual.mlnJapanese ? "右" : "Right ");
   614:         break;
   615:       case 4:  //NUMPAD
   616:         //sb.append (Multilingual.mlnJapanese ? "テンキー" : "Numpad ");
   617:         sb.append ("#");
   618:         break;
   619:       }
   620:       sb.append (KeyEvent.getKeyText (XEiJ.prgIsMac && extendedOrRaw != 0 ? extendedOrRaw : keyCode));
   621:     }
   622:     String text = sb.toString ();
   623:     if (!text.equals (textAreaArray[xo].getText ())) {
   624:       Color color = xo == focusedBox ? focusedColor : currentMap[3 * xo] != 0 ? assignedColor : backgroundColor;
   625:       textAreaArray[xo].setText (text);
   626:       textAreaArray[xo].setBackground (color);
   627:       if (xo == LEFT_SHIFT) {  //左SHIFTを更新するとき右SHIFTも更新する
   628:         textAreaArray[RIGHT_SHIFT].setText (text);
   629:         textAreaArray[RIGHT_SHIFT].setBackground (color);
   630:       }
   631:     }
   632:   }  //updateText
   633: 
   634:   //rawCode = getRawCode (ke)
   635:   //  KeyEventからrawCodeを取り出す
   636:   public int getRawCode (KeyEvent ke) {
   637:     int rawCode = 0;
   638:     //KeyEvent.paramString()で出力される文字列の中からrawCode=~を取り出す
   639:     String s = ke.paramString ();
   640:     int i = s.indexOf ("rawCode=");
   641:     if (0 <= i) {
   642:       i += 8;
   643:       for (int k = s.length (); i < k; i++) {
   644:         char c = s.charAt (i);
   645:         if (c < '0' || '9' < c) {
   646:           break;
   647:         }
   648:         rawCode = rawCode * 10 + (c - '0');
   649:       }
   650:     }
   651:     return rawCode;
   652:   }  //getRawCode
   653: 
   654:   //位置
   655:   public static final int[][] BOUNDS_ARRAY = {
   656:     {  0,  5,  4, 4 },  //  0  0x01  ESC
   657:     {  4,  5,  4, 4 },  //  1  0x02  1!ぬ 
   658:     {  8,  5,  4, 4 },  //  2  0x03  2"ふ 
   659:     { 12,  5,  4, 4 },  //  3  0x04  3#あぁ
   660:     { 16,  5,  4, 4 },  //  4  0x05  4$うぅ
   661:     { 20,  5,  4, 4 },  //  5  0x06  5%えぇ
   662:     { 24,  5,  4, 4 },  //  6  0x07  6&おぉ
   663:     { 28,  5,  4, 4 },  //  7  0x08  7'やゃ
   664:     { 32,  5,  4, 4 },  //  8  0x09  8(ゆゅ
   665:     { 36,  5,  4, 4 },  //  9  0x0a  9)よょ
   666:     { 40,  5,  4, 4 },  // 10  0x0b  0 わを
   667:     { 44,  5,  4, 4 },  // 11  0x0c  -=ほ 
   668:     { 48,  5,  4, 4 },  // 12  0x0d  ^~へ 
   669:     { 52,  5,  4, 4 },  // 13  0x0e  ¥|ー 
   670:     { 56,  5,  6, 4 },  // 14  0x0f  BS
   671:     {  0,  9,  6, 4 },  // 15  0x10  TAB
   672:     {  6,  9,  4, 4 },  // 16  0x11  Q た 
   673:     { 10,  9,  4, 4 },  // 17  0x12  W て 
   674:     { 14,  9,  4, 4 },  // 18  0x13  E いぃ
   675:     { 18,  9,  4, 4 },  // 19  0x14  R す 
   676:     { 22,  9,  4, 4 },  // 20  0x15  T か 
   677:     { 26,  9,  4, 4 },  // 21  0x16  Y ん 
   678:     { 30,  9,  4, 4 },  // 22  0x17  U な 
   679:     { 34,  9,  4, 4 },  // 23  0x18  I に 
   680:     { 38,  9,  4, 4 },  // 24  0x19  O ら 
   681:     { 42,  9,  4, 4 },  // 25  0x1a  P せ 
   682:     { 46,  9,  4, 4 },  // 26  0x1b  @`゛ 
   683:     { 50,  9,  4, 4 },  // 27  0x1c  [{゜「
   684:     { 55,  9,  7, 8 },  // 28  0x1d  リターン
   685:     {  7, 13,  4, 4 },  // 29  0x1e  A ち 
   686:     { 11, 13,  4, 4 },  // 30  0x1f  S と 
   687:     { 15, 13,  4, 4 },  // 31  0x20  D し 
   688:     { 19, 13,  4, 4 },  // 32  0x21  F は 
   689:     { 23, 13,  4, 4 },  // 33  0x22  G き 
   690:     { 27, 13,  4, 4 },  // 34  0x23  H く 
   691:     { 31, 13,  4, 4 },  // 35  0x24  J ま 
   692:     { 35, 13,  4, 4 },  // 36  0x25  K の 
   693:     { 39, 13,  4, 4 },  // 37  0x26  L り 
   694:     { 43, 13,  4, 4 },  // 38  0x27  ;+れ 
   695:     { 47, 13,  4, 4 },  // 39  0x28  :*け 
   696:     { 51, 13,  4, 4 },  // 40  0x29  ]}む」
   697:     {  9, 17,  4, 4 },  // 41  0x2a  Z つっ
   698:     { 13, 17,  4, 4 },  // 42  0x2b  X さ 
   699:     { 17, 17,  4, 4 },  // 43  0x2c  C そ 
   700:     { 21, 17,  4, 4 },  // 44  0x2d  V ひ 
   701:     { 25, 17,  4, 4 },  // 45  0x2e  B こ 
   702:     { 29, 17,  4, 4 },  // 46  0x2f  N み 
   703:     { 33, 17,  4, 4 },  // 47  0x30  M も 
   704:     { 37, 17,  4, 4 },  // 48  0x31  ,<ね、
   705:     { 41, 17,  4, 4 },  // 49  0x32  .>る。
   706:     { 45, 17,  4, 4 },  // 50  0x33  /?め・
   707:     { 49, 17,  4, 4 },  // 51  0x34   _ろ□
   708:     { 21, 21, 14, 4 },  // 52  0x35  スペース
   709:     { 64,  5,  4, 4 },  // 53  0x36  HOME
   710:     { 72,  5,  4, 4 },  // 54  0x37  DEL
   711:     { 64,  9,  4, 4 },  // 55  0x38  ROLLUP
   712:     { 68,  9,  4, 4 },  // 56  0x39  ROLLDOWN
   713:     { 72,  9,  4, 4 },  // 57  0x3a  UNDO
   714:     { 64, 13,  4, 8 },  // 58  0x3b  ←
   715:     { 68, 13,  4, 4 },  // 59  0x3c  ↑
   716:     { 72, 13,  4, 8 },  // 60  0x3d  →
   717:     { 68, 17,  4, 4 },  // 61  0x3e  ↓
   718:     { 78,  5,  4, 4 },  // 62  0x3f  CLR
   719:     { 82,  5,  4, 4 },  // 63  0x40  /
   720:     { 86,  5,  4, 4 },  // 64  0x41  *
   721:     { 90,  5,  4, 4 },  // 65  0x42  -
   722:     { 78,  9,  4, 4 },  // 66  0x43  7
   723:     { 82,  9,  4, 4 },  // 67  0x44  8
   724:     { 86,  9,  4, 4 },  // 68  0x45  9
   725:     { 90,  9,  4, 4 },  // 69  0x46  +
   726:     { 78, 13,  4, 4 },  // 70  0x47  4
   727:     { 82, 13,  4, 4 },  // 71  0x48  5
   728:     { 86, 13,  4, 4 },  // 72  0x49  6
   729:     { 90, 13,  4, 4 },  // 73  0x4a  =
   730:     { 78, 17,  4, 4 },  // 74  0x4b  1
   731:     { 82, 17,  4, 4 },  // 75  0x4c  2
   732:     { 86, 17,  4, 4 },  // 76  0x4d  3
   733:     { 90, 17,  4, 8 },  // 77  0x4e  ENTER
   734:     { 78, 21,  4, 4 },  // 78  0x4f  0
   735:     { 82, 21,  4, 4 },  // 79  0x50  ,
   736:     { 86, 21,  4, 4 },  // 80  0x51  .
   737:     { 82,  0,  4, 4 },  // 81  0x52  記号入力
   738:     { 86,  0,  4, 4 },  // 82  0x53  登録
   739:     { 90,  0,  4, 4 },  // 83  0x54  HELP
   740:     { 11, 21,  5, 4 },  // 84  0x55  XF1
   741:     { 16, 21,  5, 4 },  // 85  0x56  XF2
   742:     { 35, 21,  6, 4 },  // 86  0x57  XF3
   743:     { 41, 21,  5, 4 },  // 87  0x58  XF4
   744:     { 46, 21,  5, 4 },  // 88  0x59  XF5
   745:     { 64,  0,  4, 4 },  // 89  0x5a  かな
   746:     { 68,  0,  4, 4 },  // 90  0x5b  ローマ字
   747:     { 72,  0,  4, 4 },  // 91  0x5c  コード入力
   748:     { 78,  0,  4, 4 },  // 92  0x5d  CAPS
   749:     { 68,  5,  4, 4 },  // 93  0x5e  INS
   750:     {  7, 21,  4, 4 },  // 94  0x5f  ひらがな
   751:     { 51, 21,  4, 4 },  // 95  0x60  全角
   752:     {  0,  0,  4, 4 },  // 96  0x61  BREAK
   753:     {  5,  0,  4, 4 },  // 97  0x62  COPY
   754:     { 11,  1,  5, 3 },  // 98  0x63  F1
   755:     { 16,  1,  5, 3 },  // 99  0x64  F2
   756:     { 21,  1,  5, 3 },  //100  0x65  F3
   757:     { 26,  1,  5, 3 },  //101  0x66  F4
   758:     { 31,  1,  5, 3 },  //102  0x67  F5
   759:     { 37,  1,  5, 3 },  //103  0x68  F6
   760:     { 42,  1,  5, 3 },  //104  0x69  F7
   761:     { 47,  1,  5, 3 },  //105  0x6a  F8
   762:     { 52,  1,  5, 3 },  //106  0x6b  F9
   763:     { 57,  1,  5, 3 },  //107  0x6c  F10
   764:     {  0, 17,  9, 4 },  //108  0x70  SHIFT
   765:     {  0, 13,  7, 4 },  //109  0x71  CTRL
   766:     { 64, 21,  6, 4 },  //110  0x72  OPT.1
   767:     { 70, 21,  6, 4 },  //111  0x73  OPT.2
   768:     {  0, 21,  4, 4 },  //112  0x74  NUM
   769:     //
   770:     { 53, 17,  9, 4 },  //113  0x70  右SHIFT
   771:   };
   772: 
   773:   //文字
   774:   public static final String[] TEXT_ARRAY = (
   775:     "ESC,"        +  //  0  0x01
   776:     "1!ぬ ,"   +  //  1  0x02
   777:     "2"ふ ,"   +  //  2  0x03
   778:     "3#あぁ,"   +  //  3  0x04
   779:     "4$うぅ,"   +  //  4  0x05
   780:     "5%えぇ,"   +  //  5  0x06
   781:     "6&おぉ,"   +  //  6  0x07
   782:     "7'やゃ,"   +  //  7  0x08
   783:     "8(ゆゅ,"   +  //  8  0x09
   784:     "9)よょ,"   +  //  9  0x0a
   785:     "0 わを,"   +  // 10  0x0b
   786:     "-=ほ ,"   +  // 11  0x0c
   787:     "^~へ ,"   +  // 12  0x0d
   788:     "¥|ー ,"   +  // 13  0x0e
   789:     "BS,"         +  // 14  0x0f
   790:     "TAB,"        +  // 15  0x10
   791:     "Q た ,"   +  // 16  0x11
   792:     "W て ,"   +  // 17  0x12
   793:     "E いぃ,"   +  // 18  0x13
   794:     "R す ,"   +  // 19  0x14
   795:     "T か ,"   +  // 20  0x15
   796:     "Y ん ,"   +  // 21  0x16
   797:     "U な ,"   +  // 22  0x17
   798:     "I に ,"   +  // 23  0x18
   799:     "O ら ,"   +  // 24  0x19
   800:     "P せ ,"   +  // 25  0x1a
   801:     "@`゛ ,"   +  // 26  0x1b
   802:     "[{゜「,"   +  // 27  0x1c
   803:     "リターン,"   +  // 28  0x1d
   804:     "A ち ,"   +  // 29  0x1e
   805:     "S と ,"   +  // 30  0x1f
   806:     "D し ,"   +  // 31  0x20
   807:     "F は ,"   +  // 32  0x21
   808:     "G き ,"   +  // 33  0x22
   809:     "H く ,"   +  // 34  0x23
   810:     "J ま ,"   +  // 35  0x24
   811:     "K の ,"   +  // 36  0x25
   812:     "L り ,"   +  // 37  0x26
   813:     ";+れ ,"   +  // 38  0x27
   814:     ":*け ,"   +  // 39  0x28
   815:     "]}む」,"   +  // 40  0x29
   816:     "Z つっ,"   +  // 41  0x2a
   817:     "X さ ,"   +  // 42  0x2b
   818:     "C そ ,"   +  // 43  0x2c
   819:     "V ひ ,"   +  // 44  0x2d
   820:     "B こ ,"   +  // 45  0x2e
   821:     "N み ,"   +  // 46  0x2f
   822:     "M も ,"   +  // 47  0x30
   823:     ",<ね、,"   +  // 48  0x31
   824:     ".>る。,"   +  // 49  0x32
   825:     "/?め・,"   +  // 50  0x33
   826:     " _ろ□,"   +  // 51  0x34
   827:     "スペース,"   +  // 52  0x35
   828:     "HOME,"       +  // 53  0x36
   829:     "DEL,"        +  // 54  0x37
   830:     "ROLLUP,"     +  // 55  0x38
   831:     "ROLLDOWN,"   +  // 56  0x39
   832:     "UNDO,"       +  // 57  0x3a
   833:     "←,"         +  // 58  0x3b
   834:     "↑,"         +  // 59  0x3c
   835:     "→,"         +  // 60  0x3d
   836:     "↓,"         +  // 61  0x3e
   837:     "CLR,"        +  // 62  0x3f
   838:     "/,"         +  // 63  0x40
   839:     "*,"         +  // 64  0x41
   840:     "-,"         +  // 65  0x42
   841:     "7,"         +  // 66  0x43
   842:     "8,"         +  // 67  0x44
   843:     "9,"         +  // 68  0x45
   844:     "+,"         +  // 69  0x46
   845:     "4,"         +  // 70  0x47
   846:     "5,"         +  // 71  0x48
   847:     "6,"         +  // 72  0x49
   848:     "=,"         +  // 73  0x4a
   849:     "1,"         +  // 74  0x4b
   850:     "2,"         +  // 75  0x4c
   851:     "3,"         +  // 76  0x4d
   852:     "ENTER,"      +  // 77  0x4e
   853:     "0,"         +  // 78  0x4f
   854:     ",,"         +  // 79  0x50
   855:     ".,"         +  // 80  0x51
   856:     "記号入力,"   +  // 81  0x52
   857:     "登録,"       +  // 82  0x53
   858:     "HELP,"       +  // 83  0x54
   859:     "XF1,"        +  // 84  0x55
   860:     "XF2,"        +  // 85  0x56
   861:     "XF3,"        +  // 86  0x57
   862:     "XF4,"        +  // 87  0x58
   863:     "XF5,"        +  // 88  0x59
   864:     "かな,"       +  // 89  0x5a
   865:     "ローマ字,"   +  // 90  0x5b
   866:     "コード入力," +  // 91  0x5c
   867:     "CAPS,"       +  // 92  0x5d
   868:     "INS,"        +  // 93  0x5e
   869:     "ひらがな,"   +  // 94  0x5f
   870:     "全角,"       +  // 95  0x60
   871:     "BREAK,"      +  // 96  0x61
   872:     "COPY,"       +  // 97  0x62
   873:     "F1,"         +  // 98  0x63
   874:     "F2,"         +  // 99  0x64
   875:     "F3,"         +  //100  0x65
   876:     "F4,"         +  //101  0x66
   877:     "F5,"         +  //102  0x67
   878:     "F6,"         +  //103  0x68
   879:     "F7,"         +  //104  0x69
   880:     "F8,"         +  //105  0x6a
   881:     "F9,"         +  //106  0x6b
   882:     "F10,"        +  //107  0x6c
   883:     "SHIFT,"      +  //108  0x70
   884:     "CTRL,"       +  //109  0x71
   885:     "OPT.1,"      +  //110  0x72
   886:     "OPT.2,"      +  //111  0x73
   887:     "NUM,"        +  //112  0x74
   888:     //
   889:     "SHIFT"          //113  0x70
   890:     ).split (",");
   891: 
   892: }  //class KeyMapEditor
   893: