xeij/KeyMapEditor.java
//========================================================================================
//  KeyMapEditor.java
//    en:Key map editor
//    ja:キーマップエディタ
//  Copyright (C) 2003-2024 Makoto Kamada
//
//  This file is part of the XEiJ (X68000 Emulator in Java).
//  You can use, modify and redistribute the XEiJ if the conditions are met.
//  Read the XEiJ License for more details.
//  https://stdkmd.net/xeij/
//========================================================================================

package xeij;

import java.awt.*;
import java.awt.event.*;
import java.awt.im.*;  //InputContext
import java.io.*;
import java.util.*;
import javax.swing.*;

public class KeyMapEditor implements KeyListener {

  //定数
  public static final Font FONT = new Font ("SansSerif", Font.PLAIN, 12);  //10
  public static final int LABEL_HEIGHT = 14;  //12
  public static final int COL_WIDTH = 14;  //12  列の幅(px)。可変キーの幅の1/4
  public static final int ROW_HEIGHT = 18;  //15  15*4=12+12*4  行の高さ(px)。可変キーの高さの1/4
  public static final int COLS = 94;  //列数
  public static final int ROWS = 25;  //行数
  public static final int PADDING_TOP = 10;  //パディング(px)
  public static final int PADDING_BOTTOM = 10;
  public static final int PADDING_LEFT = 10;
  public static final int PADDING_RIGHT = 10;
  public static final int KEYBOARD_WIDTH = PADDING_LEFT + COL_WIDTH * COLS + PADDING_RIGHT;
  public static final int KEYBOARD_HEIGHT = PADDING_TOP + ROW_HEIGHT * ROWS + PADDING_BOTTOM;
  public static final int KEYS = 113;  //キーの数
  public static final int BOXES = 114;  //箱の数。右SHIFTを追加
  public static final int TAB = 15;  //TAB
  public static final int LEFT_SHIFT = 108;  //左SHIFT
  public static final int RIGHT_SHIFT = 113;  //右SHIFT

  //色
  public static Color backgroundColor;  //キーの背景色
  public static Color assignedColor;  //文字が割り当てられたキーの背景色
  public static Color focusedColor;  //フォーカスされたキーの背景色
  public static Color foregroundColor;  //キーの文字色

  //パネル
  public JPanel keyboardPanel;
  public JComponent mainPanel;

  //マップ
  public int[] currentMap;  //現在のキーマップ。3*KEYS個
  public int focusedBox;  //フォーカスが当たっている箱
  public JTextArea[] textAreaArray;  //箱のテキストエリア

  //保存と復元
  public static javax.swing.filechooser.FileFilter txtFileFilter;
  public static File lastFile;

  //取り消しとやり直し
  public static LinkedList<int[]> undoList;  //取り消しリスト
  public static LinkedList<int[]> redoList;  //やり直しリスト
  public static final int UNDO_LIST_MAX_LENGTH = 1000;  //取り消しリストの長さの上限

  //コンストラクタ
  @SuppressWarnings ("this-escape") public KeyMapEditor (int[] map) {

    //マップ
    currentMap = map;

    //保存と復元
    txtFileFilter = new javax.swing.filechooser.FileFilter () {  //java.io.FileFilterと紛らわしい
      @Override public boolean accept (File file) {
        String name = file.getName ();
        String lowerName = name.toLowerCase ();
        return (file.isDirectory () ||
                (file.isFile () &&
                 (lowerName.endsWith (".csv") ||
                  lowerName.endsWith (".txt"))));
      }
      @Override public String getDescription () {
        return (Multilingual.mlnJapanese ?
                "CSV またはテキストファイル (*.csv,*.txt)" :
                "CSV or text file (*.csv,*.txt)");
      }
    };
    lastFile = new File ("keymap.csv").getAbsoluteFile ();

    //取り消しとやり直し
    undoList = new LinkedList<int[]> ();
    redoList = new LinkedList<int[]> ();

    //色
    backgroundColor = new Color (LnF.lnfRGB[0]);
    assignedColor = new Color (LnF.lnfRGB[4]);
    focusedColor = new Color (LnF.lnfRGB[8]);
    foregroundColor = Color.white;

    //パネル
    keyboardPanel = new JPanel ();
    keyboardPanel.setLayout (null);
    keyboardPanel.setPreferredSize (new Dimension (KEYBOARD_WIDTH, KEYBOARD_HEIGHT));

    //フォーカス
    focusedBox = -1;
    FocusListener focusListener = new FocusAdapter () {
      @Override public void focusGained (FocusEvent fe) {
        int xo = Integer.parseInt (fe.getComponent ().getName ());
        if (focusedBox != -1) {
          Color color = currentMap[3 * xo] != 0 ? assignedColor : backgroundColor;
          textAreaArray[focusedBox].setBackground (color);
          if (focusedBox == LEFT_SHIFT) {
            textAreaArray[RIGHT_SHIFT].setBackground (color);
          }
          //focusedBox = -1;
        }
        focusedBox = xo;
        textAreaArray[focusedBox].setBackground (focusedColor);
        if (focusedBox == LEFT_SHIFT) {
          textAreaArray[RIGHT_SHIFT].setBackground (focusedColor);
        }
      }
      @Override public void focusLost (FocusEvent fe) {
        int xo = Integer.parseInt (fe.getComponent ().getName ());
        if (focusedBox != -1) {
          Color color = currentMap[3 * xo] != 0 ? assignedColor : backgroundColor;
          textAreaArray[focusedBox].setBackground (color);
          if (focusedBox == LEFT_SHIFT) {
            textAreaArray[RIGHT_SHIFT].setBackground (color);
          }
          focusedBox = -1;
        }
      }
    };

    //キーマップ
    textAreaArray = new JTextArea[BOXES];
    for (int xo = 0; xo < BOXES; xo++) {
      textAreaArray[xo] = null;
      int[] bounds = BOUNDS_ARRAY[xo];
      JLabel label = ComponentFactory.createLabel (TEXT_ARRAY[xo]);
      label.setFont (FONT);
      label.setBounds (PADDING_LEFT + COL_WIDTH * bounds[0],
                       PADDING_TOP + ROW_HEIGHT * bounds[1],
                       COL_WIDTH * bounds[2],
                       LABEL_HEIGHT);
      label.setHorizontalAlignment (SwingConstants.CENTER);
      keyboardPanel.add (label);
      JTextArea textArea = new JTextArea ();
      textAreaArray[xo] = textArea;
      textArea.setFont (FONT);
      ComponentFactory.setEtchedBorder (textArea);  //枠を描く
      textArea.setLineWrap (true);  //折り返す
      //textArea.setEditable (false);  //編集不可。キャレットを表示しない。キーイベントが発生しなくなる環境がある?
      textArea.setBackground (backgroundColor);
      textArea.setForeground (foregroundColor);
      textArea.setCursor (Cursor.getPredefinedCursor (Cursor.HAND_CURSOR));
      textArea.setName (String.valueOf (xo == RIGHT_SHIFT ? LEFT_SHIFT : xo));  //右SHIFTに左SHIFTの番号を入れる
      textArea.setBounds (PADDING_LEFT + COL_WIDTH * bounds[0],
                          PADDING_TOP + ROW_HEIGHT * bounds[1] + LABEL_HEIGHT,
                          COL_WIDTH * bounds[2],
                          ROW_HEIGHT * bounds[3] - LABEL_HEIGHT);
      if (xo == TAB) {
        textArea.setFocusTraversalKeysEnabled (false);  //Tabを入力できる
      } else {
        textArea.setFocusTraversalKeysEnabled (true);  //Tabで次のキーに移る
      }
      textArea.addKeyListener (this);  //[this-escape]
      textArea.addFocusListener (focusListener);
      keyboardPanel.add (textArea);
    }  //for xo
    updateTextAll ();

    //パネル
    mainPanel = new JScrollPane (keyboardPanel);
    //mainPanel.setPreferredSize (new Dimension (KEYBOARD_WIDTH + 3, KEYBOARD_HEIGHT + 3));
    mainPanel.setPreferredSize (new Dimension (Math.min (700, KEYBOARD_WIDTH + 20), KEYBOARD_HEIGHT + 20));

  }  //コンストラクタ

  //パネルを取得する
  public JComponent getPanel () {
    return mainPanel;
  }  //getPanel

  //白紙にする
  public void blank () {
    if (JOptionPane.showConfirmDialog (
      null,
      Multilingual.mlnJapanese ? "キー割り当てを白紙にしますか?" : "Do you want to blank the key assignments?",
      Multilingual.mlnJapanese ? "確認" : "Confirmation",
      JOptionPane.YES_NO_OPTION,
      JOptionPane.PLAIN_MESSAGE) != JOptionPane.YES_OPTION) {
      return;
    }
    beforeChange ();
    Arrays.fill (currentMap, 0);  //array,value
    updateTextAll ();
  }  //blank

  //初期値に戻す
  public void reset (int[] map) {
    if (JOptionPane.showConfirmDialog (
      null,
      Multilingual.mlnJapanese ? "キー割り当てを初期値に戻しますか?" : "Do you want to reset the key assignments to default?",
      Multilingual.mlnJapanese ? "確認" : "Confirmation",
      JOptionPane.YES_NO_OPTION,
      JOptionPane.PLAIN_MESSAGE) != JOptionPane.YES_OPTION) {
      return;
    }
    beforeChange ();
    System.arraycopy (map, 0,  //from
                      currentMap, 0,  //to
                      currentMap.length);  //length
    updateTextAll ();
  }  //reset

  //保存する
  public void save () {
    JFileChooser2 fileChooser = new JFileChooser2 (lastFile);
    fileChooser.setFileFilter (txtFileFilter);
    if (fileChooser.showSaveDialog (null) == JFileChooser.APPROVE_OPTION) {
      File file = fileChooser.getSelectedFile ();
      String path = file.getPath ();
      String lowerPath = path.toLowerCase ();
      if (lowerPath.endsWith (".csv")) {
        saveCSV (path);
        file = lastFile;
      } else if (lowerPath.endsWith (".txt")) {
        saveText (path);
        file = lastFile;
      }
    }
  }  //save

  //CSV形式で保存する
  public void saveCSV (String path) {
    StringBuilder sb = new StringBuilder ();
    sb.append (XEiJ.prgIsMac ?
               "X68000,keyCode,extendedKeyCode,keyLocation,keytop\r\n" :
               "X68000,keyCode,rawCode,keyLocation,keytop\r\n");
    for (int xo = 0; xo < KEYS; xo++) {
      for (int i = 0; i < 3; i++) {
        int t = currentMap[3 * xo + i];
        if (t == 0) {
          break;
        }
        sb.append (xo + (xo < 108 ? 1 : 4));
        sb.append (",");
        sb.append ((t >> 16) & (XEiJ.prgIsMac ? 0x00000fff : 0x0000ffff));
        sb.append (",");
        sb.append ((t >> 4) & (XEiJ.prgIsMac ? 0x0f000fff : 0x00000fff));
        sb.append (",");
        sb.append (t & 0xf);
        sb.append (",【");  //全角でも"~"で囲んでも数字は数値と見なされる
        sb.append (TEXT_ARRAY[xo]);
        sb.append ("】\r\n");
      }
    }
    XEiJ.rscPutTextFile (path, sb.toString (), "cp932");  //Shift_JISは不可
  }  //saveCSV

  //テキスト形式で保存する
  public void saveText (String path) {
    StringBuilder sb = new StringBuilder ();
    int length = currentMap.length;
    while (0 < length && currentMap[length - 1] == 0) {
      length--;
    }
    sb.append ("keymap=-3");
    for (int i = 0; i < length; i++) {
      sb.append (',');
      if (currentMap[i] != 0) {
        sb.append (currentMap[i]);
      }
    }
    sb.append ("\n");
    XEiJ.rscPutTextFile (path, sb.toString ());
  }  //saveText

  //復元する
  public void restore () {
    JFileChooser2 fileChooser = new JFileChooser2 (lastFile);
    fileChooser.setFileFilter (txtFileFilter);
    if (fileChooser.showOpenDialog (null) != JFileChooser.APPROVE_OPTION) {
      return;
    }
    File file = fileChooser.getSelectedFile ();
    String path = file.getPath ();
    String lowerPath = path.toLowerCase ();
    if (lowerPath.endsWith (".csv")) {
      if (restoreCSV (path)) {
        lastFile = file;
      }
    } else if (lowerPath.endsWith (".txt")) {
      if (restoreText (path)) {
        lastFile = file;
      }
    }
  }  //restore

  //CSV形式で復元する
  public boolean restoreCSV (String path) {
    String string = XEiJ.rscGetTextFile (path, "cp932");  //Shift_JISは不可
    if (string.length () == 0) {  //読み込めないまたは空
      return false;
    }
    String[] lines;
    if (0 <= string.indexOf ("\r\n")) {
      lines = string.split ("\r\n");
    } else if (0 <= string.indexOf ("\n")) {
      lines = string.split ("\n");
    } else if (0 <= string.indexOf ("\r")) {
      lines = string.split ("\r");
    } else {
      lines = new String[] { string };
    }
    int rows = lines.length;
    if (rows < 1) {  //ヘッダがない
      return false;
    }
    int[] map = new int[3 * KEYS];
    Arrays.fill (map, 0);
    for (int row = 0; row < rows; row++) {
      String[] line = lines[row].split (",");  //!!!"~"で囲まれた","があると分割してしまう
      int cols = line.length;
      if (cols < 4) {  //列数が足りない
        return false;
      }
      if (row == 0) {  //ヘッダ
        String cell = line[0];
        if (cell.startsWith ("\"") && cell.endsWith ("\"")) {  //"~"で囲まれている。!!!"\"\""→"\""が必要
          cell = cell.substring (1, cell.length () - 1);  //start,end
        }
        cell = cell.trim ();
        if (!cell.equals ("X68000")) {  //ヘッダの1列目がX68000でない
          return false;
        }
        continue;
      }
      int[] va = new int[4];
      for (int col = 0; col < 4; col++) {
        String cell = line[col];
        if (cell.startsWith ("\"") && cell.endsWith ("\"")) {  //"~"で囲まれている。!!!"\"\""→"\""が必要
          cell = cell.substring (1, cell.length () - 1);  //start,end
        }
        cell = cell.trim ();
        try {
          va[col] = Integer.parseInt (cell, 10);
        } catch (NumberFormatException nfe) {  //intに変換できない
          return false;
        }
      }
      int v0 = va[0];
      int v1 = va[1];
      int v2 = va[2];
      int v3 = va[3];
      if (!(((1 <= v0 && v0 <= 108) || (112 <= v0 && v0 <= 116)) &&
            (v1 & (XEiJ.prgIsMac ? 0x00000fff : 0x0000ffff)) == v1 &&
            (v2 & (XEiJ.prgIsMac ? 0x0f000fff : 0x00000fff)) == v2 &&
            (v3 & 0xf) == v3)) {  //値が範囲外
        return false;
      }
      int t = v1 << 16 | v2 << 4 | v3;
      if (t == 0) {  //0は割り当てられない
        return false;
      }
      int xo = v0 - (v0 < 112 ? 1 : 4);
      if (map[3 * xo] == 0) {  //1個目
        map[3 * xo] = t;
      } else if (map[3 * xo + 1] == 0) {  //2個目
        map[3 * xo + 1] = t;
      } else if (map[3 * xo + 2] == 0) {  //3個目
        map[3 * xo + 2] = t;
      } else {  //同じキーの割り当てが多すぎる
        return false;
      }
    }
    beforeChange ();
    System.arraycopy (map, 0, currentMap, 0, map.length);
    updateTextAll ();
    return true;
  }  //restoreCSV

  //テキスト形式で復元する
  public boolean restoreText (String path) {
    String string = XEiJ.rscGetTextFile (path);
    if (string.length () == 0) {  //読み込めないか空
      return false;
    }
    String[] lr = string.split ("=");
    if (lr.length != 2) {  //左辺=右辺でない
      return false;
    }
    String l = lr[0].trim ();
    String r = lr[1].trim ();
    if (!l.equals ("keymap")) {  //左辺がkeymapでない
      return false;
    }
    String[] sa = r.split (",");
    int[] ia = new int[sa.length];
    for (int i = 0; i < sa.length; i++) {
      String s = sa[i].trim ();  //前後の空白を取り除く
      if (s.length () == 0) {  //""は0と見なす
        ia[i] = 0;
      } else {
        try {
          ia[i] = Integer.parseInt (s, 10);
        } catch (NumberFormatException nfe) {  //intに変換できない
          return false;
        }
      }
    }
    if (ia.length < 1 ||  //要素が足りないか
        1 + 3 * KEYS < ia.length ||  //多すぎるか
        ia[0] != -3) {  //-3で始まっていない
      return false;
    }
    int[] map = new int[3 * KEYS];
    Arrays.fill (map, 0);
    for (int i = 0; i < 3 * KEYS; i++) {
      if (1 + i < ia.length) {
        map[i] = ia[1 + i];
      }
    }
    beforeChange ();
    System.arraycopy (map, 0, currentMap, 0, map.length);
    updateTextAll ();
    return true;
  }  //restoreText

  //取り消す
  public void undo () {
    if (!undoList.isEmpty ()) {  //取り消しリストが空でないとき
      int[] map = new int[currentMap.length];
      System.arraycopy (currentMap, 0, map, 0, currentMap.length);  //現在のマップをコピーして
      redoList.addFirst (map);  //やり直しリストの先頭に追加する
      map = undoList.removeLast ();  //取り消しリストの末尾を削除して
      System.arraycopy (map, 0, currentMap, 0, currentMap.length);  //現在のマップにコピーする
      updateTextAll ();
    }
  }  //undo

  //やり直す
  public void redo () {
    if (!redoList.isEmpty ()) {  //やり直しリストが空でないとき
      int[] map = new int[currentMap.length];
      System.arraycopy (currentMap, 0, map, 0, currentMap.length);  //現在のマップをコピーして
      undoList.addLast (map);  //取り消しリストの末尾に追加する
      map = redoList.removeFirst ();  //やり直しリストの先頭を削除して
      System.arraycopy (map, 0, currentMap, 0, currentMap.length);  //現在のマップにコピーする
      updateTextAll ();
    }
  }  //redo



  //変更前
  public void beforeChange () {
    if (undoList.size () == UNDO_LIST_MAX_LENGTH) {  //取り消しリストの長さが上限のとき
      undoList.removeFirst (); //取り消しリストの先頭を削除する
    }
    redoList.clear ();  //やり直しリストを空にする
    int[] map = new int[currentMap.length];
    System.arraycopy (currentMap, 0, map, 0, currentMap.length);  //現在のマップをコピーして
    undoList.addLast (map);  //取り消しリストの末尾に追加する
  }  //beforeChange



  //キーリスナー
  @Override public void keyPressed (KeyEvent ke) {
    closeIME (ke);
  pressed:
    {
      beforeChange ();
      int xo = Integer.parseInt (ke.getComponent ().getName ());
      int keyCode = ke.getKeyCode ();
      if (true) {
        if (xo != 0 && keyCode == KeyEvent.VK_ESCAPE) {  //ESC以外でEscが押された
          currentMap[3 * xo] = 0;  //全部消す
          currentMap[3 * xo + 1] = 0;
          currentMap[3 * xo + 2] = 0;
          updateText (xo);
          break pressed;
        }
      }
      if ((xo != 108 && keyCode == KeyEvent.VK_SHIFT) ||  //SHIFT以外でShiftが押された
          (xo != 109 && keyCode == KeyEvent.VK_CONTROL)) {  //CTRL以外でCtrlが押された
        break pressed;
      }
      int keyLocation = ke.getKeyLocation ();
      int extendedOrRaw = XEiJ.prgIsMac ? ke.getExtendedKeyCode () : getRawCode (ke);
      int intCode = keyCode << 16 | extendedOrRaw << 4 | keyLocation;
      if ((keyCode & (XEiJ.prgIsMac ? 0x00000fff : 0x0000ffff)) != keyCode ||
          (extendedOrRaw & (XEiJ.prgIsMac ? 0x0f000fff : 0x00000fff)) != extendedOrRaw ||
          (keyLocation & 0x0000000f) != keyLocation ||
          intCode == 0) {  //範囲外
        System.out.printf ("KeyEvent: keyCode=0x%08x, extendedOrRaw=0x%08x, keyLocation=0x%08x\n",
                           keyCode, extendedOrRaw, keyLocation);
        break pressed;
      }
      if (false) {
        if (currentMap[3 * xo] == intCode &&  //1個目にある
            currentMap[3 * xo + 1] == 0) {  //2個目がない
          currentMap[3 * xo] = 0;  //1個目を消す
          updateText (xo);
          break pressed;
        }
      }
      if (currentMap[3 * xo] == intCode ||  //1個目にある
          currentMap[3 * xo + 1] == intCode ||  //2個目にある
          currentMap[3 * xo + 2] == intCode) {  //3個目にある
        currentMap[3 * xo] = intCode;  //1個目にする
        currentMap[3 * xo + 1] = 0;  //2個目を消す
        currentMap[3 * xo + 2] = 0;  //3個目を消す
        updateText (xo);
        break pressed;
      }
      if (currentMap[3 * xo + 2] != 0) {  //3個目があるとき1個目を消して詰める
        currentMap[3 * xo] = currentMap[3 * xo + 1];
        currentMap[3 * xo + 1] = currentMap[3 * xo + 2];
        currentMap[3 * xo + 2] = 0;
      }
      if (currentMap[3 * xo] == 0) {  //1個目がないとき1個目にする
        currentMap[3 * xo] = intCode;
      } else if (currentMap[3 * xo + 1] == 0) {  //2個目がないとき2個目にする
        currentMap[3 * xo + 1] = intCode;
      } else {  //3個目にする
        currentMap[3 * xo + 2] = intCode;
      }
      updateText (xo);
      for (int xp = 0; xp < KEYS; xp++) {
        if (xp != xo) {  //他のキーについて
          if (currentMap[3 * xp] == intCode) {  //1個目にあるとき1個目を消して詰める
            currentMap[3 * xp] = currentMap[3 * xp + 1];
            currentMap[3 * xp + 1] = currentMap[3 * xp + 2];
            currentMap[3 * xp + 2] = 0;
            updateText (xp);
            break;
          }
          if (currentMap[3 * xp + 1] == intCode) {  //2個目にあるとき2個目を消して詰める
            currentMap[3 * xp + 1] = currentMap[3 * xp + 2];
            currentMap[3 * xp + 2] = 0;
            updateText (xp);
            break;
          }
          if (currentMap[3 * xp + 2] == intCode) {  //3個目にあるとき3個目を消す
            currentMap[3 * xp + 2] = 0;
            updateText (xp);
            break;
          }
        }
      }  //for xp
    }  //pressed
    ke.consume ();
  }  //keyPressed

  @Override public void keyReleased (KeyEvent ke) {
    closeIME (ke);
    ke.consume ();
  }  //keyReleased

  @Override public void keyTyped (KeyEvent ke) {
    closeIME (ke);
    ke.consume ();
  }  //keyTyped

  public void closeIME (KeyEvent ke) {
    JTextArea textArea = (JTextArea) ke.getComponent ();
    try {
      InputContext context = textArea.getInputContext ();
      if (context != null && context.isCompositionEnabled ()) {
        context.setCompositionEnabled (false);
        //context.setCharacterSubsets (null);
      }
    } catch (UnsupportedOperationException uoe) {
    }
  }

  public void updateTextAll () {
    for (int xo = 0; xo < KEYS; xo++) {
      updateText (xo);
    }
  }  //updateTextAll

  //キーの文字列を更新する
  public void updateText (int xo) {
    StringBuilder sb = new StringBuilder ();
    for (int i = 0; i < 3; i++) {
      int intCode = currentMap[3 * xo + i];
      if (intCode == 0) {
        if (i == 0) {
          //sb.append (Multilingual.mlnJapanese ? "なし" : "none");
        }
        break;
      }
      if (i != 0) {
        //sb.append (Multilingual.mlnJapanese ? " または " : " or ");
        //sb.append ("\n");
        sb.append (" ");
      }
      int keyCode = (intCode >> 16) & (XEiJ.prgIsMac ? 0x00000fff : 0x0000ffff);
      int extendedOrRaw = (intCode >> 4) & (XEiJ.prgIsMac ? 0x0f000fff : 0x00000fff);
      int keyLocation = intCode & 0x0000000f;
      switch (keyLocation) {
      case 2:  //LEFT
        sb.append (Multilingual.mlnJapanese ? "左" : "Left ");
        break;
      case 3:  //RIGHT
        sb.append (Multilingual.mlnJapanese ? "右" : "Right ");
        break;
      case 4:  //NUMPAD
        //sb.append (Multilingual.mlnJapanese ? "テンキー" : "Numpad ");
        sb.append ("#");
        break;
      }
      sb.append (KeyEvent.getKeyText (XEiJ.prgIsMac && extendedOrRaw != 0 ? extendedOrRaw : keyCode));
    }
    String text = sb.toString ();
    if (!text.equals (textAreaArray[xo].getText ())) {
      Color color = xo == focusedBox ? focusedColor : currentMap[3 * xo] != 0 ? assignedColor : backgroundColor;
      textAreaArray[xo].setText (text);
      textAreaArray[xo].setBackground (color);
      if (xo == LEFT_SHIFT) {  //左SHIFTを更新するとき右SHIFTも更新する
        textAreaArray[RIGHT_SHIFT].setText (text);
        textAreaArray[RIGHT_SHIFT].setBackground (color);
      }
    }
  }  //updateText

  //rawCode = getRawCode (ke)
  //  KeyEventからrawCodeを取り出す
  public int getRawCode (KeyEvent ke) {
    int rawCode = 0;
    //KeyEvent.paramString()で出力される文字列の中からrawCode=~を取り出す
    String s = ke.paramString ();
    int i = s.indexOf ("rawCode=");
    if (0 <= i) {
      i += 8;
      for (int k = s.length (); i < k; i++) {
        char c = s.charAt (i);
        if (c < '0' || '9' < c) {
          break;
        }
        rawCode = rawCode * 10 + (c - '0');
      }
    }
    return rawCode;
  }  //getRawCode

  //位置
  public static final int[][] BOUNDS_ARRAY = {
    {  0,  5,  4, 4 },  //  0  0x01  ESC
    {  4,  5,  4, 4 },  //  1  0x02  1!ぬ 
    {  8,  5,  4, 4 },  //  2  0x03  2"ふ 
    { 12,  5,  4, 4 },  //  3  0x04  3#あぁ
    { 16,  5,  4, 4 },  //  4  0x05  4$うぅ
    { 20,  5,  4, 4 },  //  5  0x06  5%えぇ
    { 24,  5,  4, 4 },  //  6  0x07  6&おぉ
    { 28,  5,  4, 4 },  //  7  0x08  7'やゃ
    { 32,  5,  4, 4 },  //  8  0x09  8(ゆゅ
    { 36,  5,  4, 4 },  //  9  0x0a  9)よょ
    { 40,  5,  4, 4 },  // 10  0x0b  0 わを
    { 44,  5,  4, 4 },  // 11  0x0c  -=ほ 
    { 48,  5,  4, 4 },  // 12  0x0d  ^~へ 
    { 52,  5,  4, 4 },  // 13  0x0e  ¥|ー 
    { 56,  5,  6, 4 },  // 14  0x0f  BS
    {  0,  9,  6, 4 },  // 15  0x10  TAB
    {  6,  9,  4, 4 },  // 16  0x11  Q た 
    { 10,  9,  4, 4 },  // 17  0x12  W て 
    { 14,  9,  4, 4 },  // 18  0x13  E いぃ
    { 18,  9,  4, 4 },  // 19  0x14  R す 
    { 22,  9,  4, 4 },  // 20  0x15  T か 
    { 26,  9,  4, 4 },  // 21  0x16  Y ん 
    { 30,  9,  4, 4 },  // 22  0x17  U な 
    { 34,  9,  4, 4 },  // 23  0x18  I に 
    { 38,  9,  4, 4 },  // 24  0x19  O ら 
    { 42,  9,  4, 4 },  // 25  0x1a  P せ 
    { 46,  9,  4, 4 },  // 26  0x1b  @`゛ 
    { 50,  9,  4, 4 },  // 27  0x1c  [{゜「
    { 55,  9,  7, 8 },  // 28  0x1d  リターン
    {  7, 13,  4, 4 },  // 29  0x1e  A ち 
    { 11, 13,  4, 4 },  // 30  0x1f  S と 
    { 15, 13,  4, 4 },  // 31  0x20  D し 
    { 19, 13,  4, 4 },  // 32  0x21  F は 
    { 23, 13,  4, 4 },  // 33  0x22  G き 
    { 27, 13,  4, 4 },  // 34  0x23  H く 
    { 31, 13,  4, 4 },  // 35  0x24  J ま 
    { 35, 13,  4, 4 },  // 36  0x25  K の 
    { 39, 13,  4, 4 },  // 37  0x26  L り 
    { 43, 13,  4, 4 },  // 38  0x27  ;+れ 
    { 47, 13,  4, 4 },  // 39  0x28  :*け 
    { 51, 13,  4, 4 },  // 40  0x29  ]}む」
    {  9, 17,  4, 4 },  // 41  0x2a  Z つっ
    { 13, 17,  4, 4 },  // 42  0x2b  X さ 
    { 17, 17,  4, 4 },  // 43  0x2c  C そ 
    { 21, 17,  4, 4 },  // 44  0x2d  V ひ 
    { 25, 17,  4, 4 },  // 45  0x2e  B こ 
    { 29, 17,  4, 4 },  // 46  0x2f  N み 
    { 33, 17,  4, 4 },  // 47  0x30  M も 
    { 37, 17,  4, 4 },  // 48  0x31  ,<ね、
    { 41, 17,  4, 4 },  // 49  0x32  .>る。
    { 45, 17,  4, 4 },  // 50  0x33  /?め・
    { 49, 17,  4, 4 },  // 51  0x34   _ろ□
    { 21, 21, 14, 4 },  // 52  0x35  スペース
    { 64,  5,  4, 4 },  // 53  0x36  HOME
    { 72,  5,  4, 4 },  // 54  0x37  DEL
    { 64,  9,  4, 4 },  // 55  0x38  ROLLUP
    { 68,  9,  4, 4 },  // 56  0x39  ROLLDOWN
    { 72,  9,  4, 4 },  // 57  0x3a  UNDO
    { 64, 13,  4, 8 },  // 58  0x3b  ←
    { 68, 13,  4, 4 },  // 59  0x3c  ↑
    { 72, 13,  4, 8 },  // 60  0x3d  →
    { 68, 17,  4, 4 },  // 61  0x3e  ↓
    { 78,  5,  4, 4 },  // 62  0x3f  CLR
    { 82,  5,  4, 4 },  // 63  0x40  /
    { 86,  5,  4, 4 },  // 64  0x41  *
    { 90,  5,  4, 4 },  // 65  0x42  -
    { 78,  9,  4, 4 },  // 66  0x43  7
    { 82,  9,  4, 4 },  // 67  0x44  8
    { 86,  9,  4, 4 },  // 68  0x45  9
    { 90,  9,  4, 4 },  // 69  0x46  +
    { 78, 13,  4, 4 },  // 70  0x47  4
    { 82, 13,  4, 4 },  // 71  0x48  5
    { 86, 13,  4, 4 },  // 72  0x49  6
    { 90, 13,  4, 4 },  // 73  0x4a  =
    { 78, 17,  4, 4 },  // 74  0x4b  1
    { 82, 17,  4, 4 },  // 75  0x4c  2
    { 86, 17,  4, 4 },  // 76  0x4d  3
    { 90, 17,  4, 8 },  // 77  0x4e  ENTER
    { 78, 21,  4, 4 },  // 78  0x4f  0
    { 82, 21,  4, 4 },  // 79  0x50  ,
    { 86, 21,  4, 4 },  // 80  0x51  .
    { 82,  0,  4, 4 },  // 81  0x52  記号入力
    { 86,  0,  4, 4 },  // 82  0x53  登録
    { 90,  0,  4, 4 },  // 83  0x54  HELP
    { 11, 21,  5, 4 },  // 84  0x55  XF1
    { 16, 21,  5, 4 },  // 85  0x56  XF2
    { 35, 21,  6, 4 },  // 86  0x57  XF3
    { 41, 21,  5, 4 },  // 87  0x58  XF4
    { 46, 21,  5, 4 },  // 88  0x59  XF5
    { 64,  0,  4, 4 },  // 89  0x5a  かな
    { 68,  0,  4, 4 },  // 90  0x5b  ローマ字
    { 72,  0,  4, 4 },  // 91  0x5c  コード入力
    { 78,  0,  4, 4 },  // 92  0x5d  CAPS
    { 68,  5,  4, 4 },  // 93  0x5e  INS
    {  7, 21,  4, 4 },  // 94  0x5f  ひらがな
    { 51, 21,  4, 4 },  // 95  0x60  全角
    {  0,  0,  4, 4 },  // 96  0x61  BREAK
    {  5,  0,  4, 4 },  // 97  0x62  COPY
    { 11,  1,  5, 3 },  // 98  0x63  F1
    { 16,  1,  5, 3 },  // 99  0x64  F2
    { 21,  1,  5, 3 },  //100  0x65  F3
    { 26,  1,  5, 3 },  //101  0x66  F4
    { 31,  1,  5, 3 },  //102  0x67  F5
    { 37,  1,  5, 3 },  //103  0x68  F6
    { 42,  1,  5, 3 },  //104  0x69  F7
    { 47,  1,  5, 3 },  //105  0x6a  F8
    { 52,  1,  5, 3 },  //106  0x6b  F9
    { 57,  1,  5, 3 },  //107  0x6c  F10
    {  0, 17,  9, 4 },  //108  0x70  SHIFT
    {  0, 13,  7, 4 },  //109  0x71  CTRL
    { 64, 21,  6, 4 },  //110  0x72  OPT.1
    { 70, 21,  6, 4 },  //111  0x73  OPT.2
    {  0, 21,  4, 4 },  //112  0x74  NUM
    //
    { 53, 17,  9, 4 },  //113  0x70  右SHIFT
  };

  //文字
  public static final String[] TEXT_ARRAY = (
    "ESC,"        +  //  0  0x01
    "1!ぬ ,"   +  //  1  0x02
    "2"ふ ,"   +  //  2  0x03
    "3#あぁ,"   +  //  3  0x04
    "4$うぅ,"   +  //  4  0x05
    "5%えぇ,"   +  //  5  0x06
    "6&おぉ,"   +  //  6  0x07
    "7'やゃ,"   +  //  7  0x08
    "8(ゆゅ,"   +  //  8  0x09
    "9)よょ,"   +  //  9  0x0a
    "0 わを,"   +  // 10  0x0b
    "-=ほ ,"   +  // 11  0x0c
    "^~へ ,"   +  // 12  0x0d
    "¥|ー ,"   +  // 13  0x0e
    "BS,"         +  // 14  0x0f
    "TAB,"        +  // 15  0x10
    "Q た ,"   +  // 16  0x11
    "W て ,"   +  // 17  0x12
    "E いぃ,"   +  // 18  0x13
    "R す ,"   +  // 19  0x14
    "T か ,"   +  // 20  0x15
    "Y ん ,"   +  // 21  0x16
    "U な ,"   +  // 22  0x17
    "I に ,"   +  // 23  0x18
    "O ら ,"   +  // 24  0x19
    "P せ ,"   +  // 25  0x1a
    "@`゛ ,"   +  // 26  0x1b
    "[{゜「,"   +  // 27  0x1c
    "リターン,"   +  // 28  0x1d
    "A ち ,"   +  // 29  0x1e
    "S と ,"   +  // 30  0x1f
    "D し ,"   +  // 31  0x20
    "F は ,"   +  // 32  0x21
    "G き ,"   +  // 33  0x22
    "H く ,"   +  // 34  0x23
    "J ま ,"   +  // 35  0x24
    "K の ,"   +  // 36  0x25
    "L り ,"   +  // 37  0x26
    ";+れ ,"   +  // 38  0x27
    ":*け ,"   +  // 39  0x28
    "]}む」,"   +  // 40  0x29
    "Z つっ,"   +  // 41  0x2a
    "X さ ,"   +  // 42  0x2b
    "C そ ,"   +  // 43  0x2c
    "V ひ ,"   +  // 44  0x2d
    "B こ ,"   +  // 45  0x2e
    "N み ,"   +  // 46  0x2f
    "M も ,"   +  // 47  0x30
    ",<ね、,"   +  // 48  0x31
    ".>る。,"   +  // 49  0x32
    "/?め・,"   +  // 50  0x33
    " _ろ□,"   +  // 51  0x34
    "スペース,"   +  // 52  0x35
    "HOME,"       +  // 53  0x36
    "DEL,"        +  // 54  0x37
    "ROLLUP,"     +  // 55  0x38
    "ROLLDOWN,"   +  // 56  0x39
    "UNDO,"       +  // 57  0x3a
    "←,"         +  // 58  0x3b
    "↑,"         +  // 59  0x3c
    "→,"         +  // 60  0x3d
    "↓,"         +  // 61  0x3e
    "CLR,"        +  // 62  0x3f
    "/,"         +  // 63  0x40
    "*,"         +  // 64  0x41
    "-,"         +  // 65  0x42
    "7,"         +  // 66  0x43
    "8,"         +  // 67  0x44
    "9,"         +  // 68  0x45
    "+,"         +  // 69  0x46
    "4,"         +  // 70  0x47
    "5,"         +  // 71  0x48
    "6,"         +  // 72  0x49
    "=,"         +  // 73  0x4a
    "1,"         +  // 74  0x4b
    "2,"         +  // 75  0x4c
    "3,"         +  // 76  0x4d
    "ENTER,"      +  // 77  0x4e
    "0,"         +  // 78  0x4f
    ",,"         +  // 79  0x50
    ".,"         +  // 80  0x51
    "記号入力,"   +  // 81  0x52
    "登録,"       +  // 82  0x53
    "HELP,"       +  // 83  0x54
    "XF1,"        +  // 84  0x55
    "XF2,"        +  // 85  0x56
    "XF3,"        +  // 86  0x57
    "XF4,"        +  // 87  0x58
    "XF5,"        +  // 88  0x59
    "かな,"       +  // 89  0x5a
    "ローマ字,"   +  // 90  0x5b
    "コード入力," +  // 91  0x5c
    "CAPS,"       +  // 92  0x5d
    "INS,"        +  // 93  0x5e
    "ひらがな,"   +  // 94  0x5f
    "全角,"       +  // 95  0x60
    "BREAK,"      +  // 96  0x61
    "COPY,"       +  // 97  0x62
    "F1,"         +  // 98  0x63
    "F2,"         +  // 99  0x64
    "F3,"         +  //100  0x65
    "F4,"         +  //101  0x66
    "F5,"         +  //102  0x67
    "F6,"         +  //103  0x68
    "F7,"         +  //104  0x69
    "F8,"         +  //105  0x6a
    "F9,"         +  //106  0x6b
    "F10,"        +  //107  0x6c
    "SHIFT,"      +  //108  0x70
    "CTRL,"       +  //109  0x71
    "OPT.1,"      +  //110  0x72
    "OPT.2,"      +  //111  0x73
    "NUM,"        +  //112  0x74
    //
    "SHIFT"          //113  0x70
    ).split (",");

}  //class KeyMapEditor